Browse code

add support for maximum log size, and max number of log files

Signed-off-by: wlan0 <sidharthamn@gmail.com>

wlan0 authored on 2015/07/01 09:40:13
Showing 16 changed files
... ...
@@ -19,7 +19,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
19 19
 	follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
20 20
 	since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
21 21
 	times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
22
-	tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
22
+	tail := cmd.String([]string{"-tail"}, "latest", "Number of lines to show from the end of the logs")
23 23
 	cmd.Require(flag.Exact, 1)
24 24
 
25 25
 	cmd.ParseFlags(args, true)
... ...
@@ -710,7 +710,10 @@ func (container *Container) SetHostConfig(hostConfig *runconfig.HostConfig) {
710 710
 
711 711
 func (container *Container) getLogConfig() runconfig.LogConfig {
712 712
 	cfg := container.hostConfig.LogConfig
713
-	if cfg.Type != "" { // container has log driver configured
713
+	if cfg.Type != "" || len(cfg.Config) > 0 { // container has log driver configured
714
+		if cfg.Type == "" {
715
+			cfg.Type = jsonfilelog.Name
716
+		}
714 717
 		return cfg
715 718
 	}
716 719
 	// Use daemon's default log config for containers
... ...
@@ -719,6 +722,9 @@ func (container *Container) getLogConfig() runconfig.LogConfig {
719 719
 
720 720
 func (container *Container) getLogger() (logger.Logger, error) {
721 721
 	cfg := container.getLogConfig()
722
+	if err := logger.ValidateLogOpts(cfg.Type, cfg.Config); err != nil {
723
+		return nil, err
724
+	}
722 725
 	c, err := logger.GetLogDriver(cfg.Type)
723 726
 	if err != nil {
724 727
 		return nil, fmt.Errorf("Failed to get logging factory: %v", err)
... ...
@@ -891,28 +897,32 @@ func (c *Container) AttachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer
891 891
 
892 892
 	if logs {
893 893
 		logDriver, err := c.getLogger()
894
-		cLog, err := logDriver.GetReader()
895
-
896 894
 		if err != nil {
897
-			logrus.Errorf("Error reading logs: %s", err)
898
-		} else if c.LogDriverType() != jsonfilelog.Name {
899
-			logrus.Errorf("Reading logs not implemented for driver %s", c.LogDriverType())
895
+			logrus.Errorf("Error obtaining the logger %v", err)
896
+			return err
897
+		}
898
+		if _, ok := logDriver.(logger.Reader); !ok {
899
+			logrus.Errorf("cannot read logs for [%s] driver", logDriver.Name())
900 900
 		} else {
901
-			dec := json.NewDecoder(cLog)
902
-			for {
903
-				l := &jsonlog.JSONLog{}
904
-
905
-				if err := dec.Decode(l); err == io.EOF {
906
-					break
907
-				} else if err != nil {
908
-					logrus.Errorf("Error streaming logs: %s", err)
909
-					break
910
-				}
911
-				if l.Stream == "stdout" && stdout != nil {
912
-					io.WriteString(stdout, l.Log)
913
-				}
914
-				if l.Stream == "stderr" && stderr != nil {
915
-					io.WriteString(stderr, l.Log)
901
+			if cLog, err := logDriver.(logger.Reader).ReadLog(); err != nil {
902
+				logrus.Errorf("Error reading logs %v", err)
903
+			} else {
904
+				dec := json.NewDecoder(cLog)
905
+				for {
906
+					l := &jsonlog.JSONLog{}
907
+
908
+					if err := dec.Decode(l); err == io.EOF {
909
+						break
910
+					} else if err != nil {
911
+						logrus.Errorf("Error streaming logs: %s", err)
912
+						break
913
+					}
914
+					if l.Stream == "stdout" && stdout != nil {
915
+						io.WriteString(stdout, l.Log)
916
+					}
917
+					if l.Stream == "stderr" && stderr != nil {
918
+						io.WriteString(stderr, l.Log)
919
+					}
916 920
 				}
917 921
 			}
918 922
 		}
... ...
@@ -3,7 +3,6 @@ package logger
3 3
 import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6
-	"errors"
7 6
 	"io"
8 7
 	"testing"
9 8
 	"time"
... ...
@@ -19,10 +18,6 @@ func (l *TestLoggerJSON) Close() error { return nil }
19 19
 
20 20
 func (l *TestLoggerJSON) Name() string { return "json" }
21 21
 
22
-func (l *TestLoggerJSON) GetReader() (io.Reader, error) {
23
-	return nil, errors.New("not used in the test")
24
-}
25
-
26 22
 type TestLoggerText struct {
27 23
 	*bytes.Buffer
28 24
 }
... ...
@@ -36,10 +31,6 @@ func (l *TestLoggerText) Close() error { return nil }
36 36
 
37 37
 func (l *TestLoggerText) Name() string { return "text" }
38 38
 
39
-func (l *TestLoggerText) GetReader() (io.Reader, error) {
40
-	return nil, errors.New("not used in the test")
41
-}
42
-
43 39
 func TestCopier(t *testing.T) {
44 40
 	stdoutLine := "Line that thinks that it is log line from docker stdout"
45 41
 	stderrLine := "Line that thinks that it is log line from docker stderr"
... ...
@@ -11,6 +11,9 @@ import (
11 11
 // Creator is a method that builds a logging driver instance with given context
12 12
 type Creator func(Context) (Logger, error)
13 13
 
14
+//LogOptValidator is a method that validates the log opts provided
15
+type LogOptValidator func(cfg map[string]string) error
16
+
14 17
 // Context provides enough information for a logging driver to do its function
15 18
 type Context struct {
16 19
 	Config              map[string]string
... ...
@@ -42,8 +45,9 @@ func (ctx *Context) Command() string {
42 42
 }
43 43
 
44 44
 type logdriverFactory struct {
45
-	registry map[string]Creator
46
-	m        sync.Mutex
45
+	registry     map[string]Creator
46
+	optValidator map[string]LogOptValidator
47
+	m            sync.Mutex
47 48
 }
48 49
 
49 50
 func (lf *logdriverFactory) register(name string, c Creator) error {
... ...
@@ -57,6 +61,17 @@ func (lf *logdriverFactory) register(name string, c Creator) error {
57 57
 	return nil
58 58
 }
59 59
 
60
+func (lf *logdriverFactory) registerLogOptValidator(name string, l LogOptValidator) error {
61
+	lf.m.Lock()
62
+	defer lf.m.Unlock()
63
+
64
+	if _, ok := lf.optValidator[name]; ok {
65
+		return fmt.Errorf("logger: log driver named '%s' is already registered", name)
66
+	}
67
+	lf.optValidator[name] = l
68
+	return nil
69
+}
70
+
60 71
 func (lf *logdriverFactory) get(name string) (Creator, error) {
61 72
 	lf.m.Lock()
62 73
 	defer lf.m.Unlock()
... ...
@@ -68,7 +83,15 @@ func (lf *logdriverFactory) get(name string) (Creator, error) {
68 68
 	return c, nil
69 69
 }
70 70
 
71
-var factory = &logdriverFactory{registry: make(map[string]Creator)} // global factory instance
71
+func (lf *logdriverFactory) getLogOptValidator(name string) LogOptValidator {
72
+	lf.m.Lock()
73
+	defer lf.m.Unlock()
74
+
75
+	c, _ := lf.optValidator[name]
76
+	return c
77
+}
78
+
79
+var factory = &logdriverFactory{registry: make(map[string]Creator), optValidator: make(map[string]LogOptValidator)} // global factory instance
72 80
 
73 81
 // RegisterLogDriver registers the given logging driver builder with given logging
74 82
 // driver name.
... ...
@@ -76,7 +99,19 @@ func RegisterLogDriver(name string, c Creator) error {
76 76
 	return factory.register(name, c)
77 77
 }
78 78
 
79
+func RegisterLogOptValidator(name string, l LogOptValidator) error {
80
+	return factory.registerLogOptValidator(name, l)
81
+}
82
+
79 83
 // GetLogDriver provides the logging driver builder for a logging driver name.
80 84
 func GetLogDriver(name string) (Creator, error) {
81 85
 	return factory.get(name)
82 86
 }
87
+
88
+func ValidateLogOpts(name string, cfg map[string]string) error {
89
+	l := factory.getLogOptValidator(name)
90
+	if l != nil {
91
+		return l(cfg)
92
+	}
93
+	return fmt.Errorf("Log Opts are not valid for [%s] driver", name)
94
+}
... ...
@@ -2,7 +2,7 @@ package fluentd
2 2
 
3 3
 import (
4 4
 	"bytes"
5
-	"io"
5
+	"fmt"
6 6
 	"math"
7 7
 	"net"
8 8
 	"strconv"
... ...
@@ -38,6 +38,9 @@ func init() {
38 38
 	if err := logger.RegisterLogDriver(name, New); err != nil {
39 39
 		logrus.Fatal(err)
40 40
 	}
41
+	if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
42
+		logrus.Fatal(err)
43
+	}
41 44
 }
42 45
 
43 46
 func parseConfig(ctx logger.Context) (string, int, string, error) {
... ...
@@ -116,6 +119,18 @@ func (f *Fluentd) Log(msg *logger.Message) error {
116 116
 	return f.writer.PostWithTime(f.tag, msg.Timestamp, data)
117 117
 }
118 118
 
119
+func ValidateLogOpt(cfg map[string]string) error {
120
+	for key := range cfg {
121
+		switch key {
122
+		case "fluentd-address":
123
+		case "fluentd-tag":
124
+		default:
125
+			return fmt.Errorf("unknown log opt '%s' for fluentd log driver", key)
126
+		}
127
+	}
128
+	return nil
129
+}
130
+
119 131
 func (f *Fluentd) Close() error {
120 132
 	return f.writer.Close()
121 133
 }
... ...
@@ -123,7 +138,3 @@ func (f *Fluentd) Close() error {
123 123
 func (f *Fluentd) Name() string {
124 124
 	return name
125 125
 }
126
-
127
-func (s *Fluentd) GetReader() (io.Reader, error) {
128
-	return nil, logger.ReadLogsNotSupported
129
-}
... ...
@@ -5,7 +5,6 @@ package gelf
5 5
 import (
6 6
 	"bytes"
7 7
 	"fmt"
8
-	"io"
9 8
 	"net"
10 9
 	"net/url"
11 10
 	"time"
... ...
@@ -39,6 +38,9 @@ func init() {
39 39
 	if err := logger.RegisterLogDriver(name, New); err != nil {
40 40
 		logrus.Fatal(err)
41 41
 	}
42
+	if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
43
+		logrus.Fatal(err)
44
+	}
42 45
 }
43 46
 
44 47
 func New(ctx logger.Context) (logger.Logger, error) {
... ...
@@ -113,10 +115,6 @@ func (s *GelfLogger) Log(msg *logger.Message) error {
113 113
 	return nil
114 114
 }
115 115
 
116
-func (s *GelfLogger) GetReader() (io.Reader, error) {
117
-	return nil, logger.ReadLogsNotSupported
118
-}
119
-
120 116
 func (s *GelfLogger) Close() error {
121 117
 	return s.writer.Close()
122 118
 }
... ...
@@ -125,6 +123,18 @@ func (s *GelfLogger) Name() string {
125 125
 	return name
126 126
 }
127 127
 
128
+func ValidateLogOpt(cfg map[string]string) error {
129
+	for key := range cfg {
130
+		switch key {
131
+		case "gelf-address":
132
+		case "gelf-tag":
133
+		default:
134
+			return fmt.Errorf("unknown log opt '%s' for gelf log driver", key)
135
+		}
136
+	}
137
+	return nil
138
+}
139
+
128 140
 func parseAddress(address string) (string, error) {
129 141
 	if urlutil.IsTransportURL(address) {
130 142
 		url, err := url.Parse(address)
... ...
@@ -4,7 +4,6 @@ package journald
4 4
 
5 5
 import (
6 6
 	"fmt"
7
-	"io"
8 7
 
9 8
 	"github.com/Sirupsen/logrus"
10 9
 	"github.com/coreos/go-systemd/journal"
... ...
@@ -54,7 +53,3 @@ func (s *Journald) Close() error {
54 54
 func (s *Journald) Name() string {
55 55
 	return name
56 56
 }
57
-
58
-func (s *Journald) GetReader() (io.Reader, error) {
59
-	return nil, logger.ReadLogsNotSupported
60
-}
... ...
@@ -2,14 +2,17 @@ package jsonfilelog
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"fmt"
5 6
 	"io"
6 7
 	"os"
8
+	"strconv"
7 9
 	"sync"
8 10
 
9 11
 	"github.com/Sirupsen/logrus"
10 12
 	"github.com/docker/docker/daemon/logger"
11 13
 	"github.com/docker/docker/pkg/jsonlog"
12 14
 	"github.com/docker/docker/pkg/timeutils"
15
+	"github.com/docker/docker/pkg/units"
13 16
 )
14 17
 
15 18
 const (
... ...
@@ -19,17 +22,21 @@ const (
19 19
 // JSONFileLogger is Logger implementation for default docker logging:
20 20
 // JSON objects to file
21 21
 type JSONFileLogger struct {
22
-	buf *bytes.Buffer
23
-	f   *os.File   // store for closing
24
-	mu  sync.Mutex // protects buffer
25
-
26
-	ctx logger.Context
22
+	buf      *bytes.Buffer
23
+	f        *os.File   // store for closing
24
+	mu       sync.Mutex // protects buffer
25
+	capacity int64      //maximum size of each file
26
+	n        int        //maximum number of files
27
+	ctx      logger.Context
27 28
 }
28 29
 
29 30
 func init() {
30 31
 	if err := logger.RegisterLogDriver(Name, New); err != nil {
31 32
 		logrus.Fatal(err)
32 33
 	}
34
+	if err := logger.RegisterLogOptValidator(Name, ValidateLogOpt); err != nil {
35
+		logrus.Fatal(err)
36
+	}
33 37
 }
34 38
 
35 39
 // New creates new JSONFileLogger which writes to filename
... ...
@@ -38,10 +45,30 @@ func New(ctx logger.Context) (logger.Logger, error) {
38 38
 	if err != nil {
39 39
 		return nil, err
40 40
 	}
41
+	var capval int64 = -1
42
+	if capacity, ok := ctx.Config["max-size"]; ok {
43
+		var err error
44
+		capval, err = units.FromHumanSize(capacity)
45
+		if err != nil {
46
+			return nil, err
47
+		}
48
+	}
49
+	var maxFiles int = 1
50
+	if maxFileString, ok := ctx.Config["max-file"]; ok {
51
+		maxFiles, err = strconv.Atoi(maxFileString)
52
+		if err != nil {
53
+			return nil, err
54
+		}
55
+		if maxFiles < 1 {
56
+			return nil, fmt.Errorf("max-files cannot be less than 1.")
57
+		}
58
+	}
41 59
 	return &JSONFileLogger{
42
-		f:   log,
43
-		buf: bytes.NewBuffer(nil),
44
-		ctx: ctx,
60
+		f:        log,
61
+		buf:      bytes.NewBuffer(nil),
62
+		ctx:      ctx,
63
+		capacity: capval,
64
+		n:        maxFiles,
45 65
 	}, nil
46 66
 }
47 67
 
... ...
@@ -59,17 +86,102 @@ func (l *JSONFileLogger) Log(msg *logger.Message) error {
59 59
 		return err
60 60
 	}
61 61
 	l.buf.WriteByte('\n')
62
-	_, err = l.buf.WriteTo(l.f)
62
+	_, err = writeLog(l)
63
+	return err
64
+}
65
+
66
+func writeLog(l *JSONFileLogger) (int64, error) {
67
+	if l.capacity == -1 {
68
+		return writeToBuf(l)
69
+	}
70
+	meta, err := l.f.Stat()
71
+	if err != nil {
72
+		return -1, err
73
+	}
74
+	if meta.Size() >= l.capacity {
75
+		name := l.f.Name()
76
+		if err := l.f.Close(); err != nil {
77
+			return -1, err
78
+		}
79
+		if err := rotate(name, l.n); err != nil {
80
+			return -1, err
81
+		}
82
+		file, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
83
+		if err != nil {
84
+			return -1, err
85
+		}
86
+		l.f = file
87
+	}
88
+	return writeToBuf(l)
89
+}
90
+
91
+func writeToBuf(l *JSONFileLogger) (int64, error) {
92
+	i, err := l.buf.WriteTo(l.f)
63 93
 	if err != nil {
64
-		// this buffer is screwed, replace it with another to avoid races
65 94
 		l.buf = bytes.NewBuffer(nil)
95
+	}
96
+	return i, err
97
+}
98
+
99
+func rotate(name string, n int) error {
100
+	if n < 2 {
101
+		return nil
102
+	}
103
+	for i := n - 1; i > 1; i-- {
104
+		oldFile := name + "." + strconv.Itoa(i)
105
+		replacingFile := name + "." + strconv.Itoa(i-1)
106
+		if err := backup(oldFile, replacingFile); err != nil {
107
+			return err
108
+		}
109
+	}
110
+	if err := backup(name+".1", name); err != nil {
66 111
 		return err
67 112
 	}
68 113
 	return nil
69 114
 }
70 115
 
71
-func (l *JSONFileLogger) GetReader() (io.Reader, error) {
72
-	return os.Open(l.ctx.LogPath)
116
+func backup(old, curr string) error {
117
+	if _, err := os.Stat(old); !os.IsNotExist(err) {
118
+		err := os.Remove(old)
119
+		if err != nil {
120
+			return err
121
+		}
122
+	}
123
+	if _, err := os.Stat(curr); os.IsNotExist(err) {
124
+		if f, err := os.Create(curr); err != nil {
125
+			return err
126
+		} else {
127
+			f.Close()
128
+		}
129
+	}
130
+	return os.Rename(curr, old)
131
+}
132
+
133
+func ValidateLogOpt(cfg map[string]string) error {
134
+	for key := range cfg {
135
+		switch key {
136
+		case "max-file":
137
+		case "max-size":
138
+		default:
139
+			return fmt.Errorf("unknown log opt '%s' for json-file log driver", key)
140
+		}
141
+	}
142
+	return nil
143
+}
144
+
145
+func (l *JSONFileLogger) ReadLog(args ...string) (io.Reader, error) {
146
+	pth := l.ctx.LogPath
147
+	if len(args) > 0 {
148
+		//check if args[0] is an integer index
149
+		index, err := strconv.ParseInt(args[0], 0, 0)
150
+		if err != nil {
151
+			return nil, err
152
+		}
153
+		if index > 0 {
154
+			pth = pth + "." + args[0]
155
+		}
156
+	}
157
+	return os.Open(pth)
73 158
 }
74 159
 
75 160
 func (l *JSONFileLogger) LogPath() string {
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"io/ioutil"
5 5
 	"os"
6 6
 	"path/filepath"
7
+	"strconv"
7 8
 	"testing"
8 9
 	"time"
9 10
 
... ...
@@ -84,3 +85,67 @@ func BenchmarkJSONFileLogger(b *testing.B) {
84 84
 		}
85 85
 	}
86 86
 }
87
+
88
+func TestJSONFileLoggerWithOpts(t *testing.T) {
89
+	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
90
+	tmp, err := ioutil.TempDir("", "docker-logger-")
91
+	if err != nil {
92
+		t.Fatal(err)
93
+	}
94
+	defer os.RemoveAll(tmp)
95
+	filename := filepath.Join(tmp, "container.log")
96
+	config := map[string]string{"max-file": "2", "max-size": "1k"}
97
+	l, err := New(logger.Context{
98
+		ContainerID: cid,
99
+		LogPath:     filename,
100
+		Config:      config,
101
+	})
102
+	if err != nil {
103
+		t.Fatal(err)
104
+	}
105
+	defer l.Close()
106
+	for i := 0; i < 20; i++ {
107
+		if err := l.Log(&logger.Message{ContainerID: cid, Line: []byte("line" + strconv.Itoa(i)), Source: "src1"}); err != nil {
108
+			t.Fatal(err)
109
+		}
110
+	}
111
+	res, err := ioutil.ReadFile(filename)
112
+	if err != nil {
113
+		t.Fatal(err)
114
+	}
115
+	penUlt, err := ioutil.ReadFile(filename + ".1")
116
+	if err != nil {
117
+		t.Fatal(err)
118
+	}
119
+
120
+	expectedPenultimate := `{"log":"line0\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
121
+{"log":"line1\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
122
+{"log":"line2\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
123
+{"log":"line3\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
124
+{"log":"line4\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
125
+{"log":"line5\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
126
+{"log":"line6\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
127
+{"log":"line7\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
128
+{"log":"line8\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
129
+{"log":"line9\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
130
+{"log":"line10\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
131
+{"log":"line11\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
132
+{"log":"line12\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
133
+{"log":"line13\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
134
+{"log":"line14\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
135
+{"log":"line15\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
136
+`
137
+	expected := `{"log":"line16\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
138
+{"log":"line17\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
139
+{"log":"line18\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
140
+{"log":"line19\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
141
+`
142
+
143
+	if string(res) != expected {
144
+		t.Fatalf("Wrong log content: %q, expected %q", res, expected)
145
+	}
146
+	if string(penUlt) != expectedPenultimate {
147
+		t.Fatalf("Wrong log content: %q, expected %q", penUlt, expectedPenultimate)
148
+	}
149
+
150
+}
... ...
@@ -21,5 +21,9 @@ type Logger interface {
21 21
 	Log(*Message) error
22 22
 	Name() string
23 23
 	Close() error
24
-	GetReader() (io.Reader, error)
24
+}
25
+
26
+//Reader is an interface for docker logging drivers that support reading
27
+type Reader interface {
28
+	ReadLog(args ...string) (io.Reader, error)
25 29
 }
... ...
@@ -4,7 +4,7 @@ package syslog
4 4
 
5 5
 import (
6 6
 	"errors"
7
-	"io"
7
+	"fmt"
8 8
 	"log/syslog"
9 9
 	"net"
10 10
 	"net/url"
... ...
@@ -51,6 +51,9 @@ func init() {
51 51
 	if err := logger.RegisterLogDriver(name, New); err != nil {
52 52
 		logrus.Fatal(err)
53 53
 	}
54
+	if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
55
+		logrus.Fatal(err)
56
+	}
54 57
 }
55 58
 
56 59
 func New(ctx logger.Context) (logger.Logger, error) {
... ...
@@ -99,10 +102,6 @@ func (s *Syslog) Name() string {
99 99
 	return name
100 100
 }
101 101
 
102
-func (s *Syslog) GetReader() (io.Reader, error) {
103
-	return nil, logger.ReadLogsNotSupported
104
-}
105
-
106 102
 func parseAddress(address string) (string, string, error) {
107 103
 	if urlutil.IsTransportURL(address) {
108 104
 		url, err := url.Parse(address)
... ...
@@ -133,6 +132,19 @@ func parseAddress(address string) (string, string, error) {
133 133
 	return "", "", nil
134 134
 }
135 135
 
136
+func ValidateLogOpt(cfg map[string]string) error {
137
+	for key := range cfg {
138
+		switch key {
139
+		case "syslog-address":
140
+		case "syslog-tag":
141
+		case "syslog-facility":
142
+		default:
143
+			return fmt.Errorf("unknown log opt '%s' for syslog log driver", key)
144
+		}
145
+	}
146
+	return nil
147
+}
148
+
136 149
 func parseFacility(facility string) (syslog.Priority, error) {
137 150
 	if facility == "" {
138 151
 		return syslog.LOG_DAEMON, nil
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	"time"
13 13
 
14 14
 	"github.com/Sirupsen/logrus"
15
+	"github.com/docker/docker/daemon/logger"
15 16
 	"github.com/docker/docker/daemon/logger/jsonfilelog"
16 17
 	"github.com/docker/docker/pkg/jsonlog"
17 18
 	"github.com/docker/docker/pkg/stdcopy"
... ...
@@ -40,7 +41,7 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
40 40
 		format = timeutils.RFC3339NanoFixed
41 41
 	}
42 42
 	if config.Tail == "" {
43
-		config.Tail = "all"
43
+		config.Tail = "latest"
44 44
 	}
45 45
 
46 46
 	container, err := daemon.Get(name)
... ...
@@ -62,13 +63,29 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
62 62
 	if container.LogDriverType() != jsonfilelog.Name {
63 63
 		return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
64 64
 	}
65
+
66
+	maxFile := 1
67
+	container.readHostConfig()
68
+	cfg := container.getLogConfig()
69
+	conf := cfg.Config
70
+	if val, ok := conf["max-file"]; ok {
71
+		var err error
72
+		maxFile, err = strconv.Atoi(val)
73
+		if err != nil {
74
+			return fmt.Errorf("Error reading max-file value: %s", err)
75
+		}
76
+	}
77
+
65 78
 	logDriver, err := container.getLogger()
66
-	cLog, err := logDriver.GetReader()
67 79
 	if err != nil {
68
-		logrus.Errorf("Error reading logs: %s", err)
80
+		return err
81
+	}
82
+	_, ok := logDriver.(logger.Reader)
83
+	if !ok {
84
+		logrus.Errorf("Cannot read logs of the [%s] driver", logDriver.Name())
69 85
 	} else {
70 86
 		// json-file driver
71
-		if config.Tail != "all" {
87
+		if config.Tail != "all" && config.Tail != "latest" {
72 88
 			var err error
73 89
 			lines, err = strconv.Atoi(config.Tail)
74 90
 			if err != nil {
... ...
@@ -78,42 +95,50 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
78 78
 		}
79 79
 
80 80
 		if lines != 0 {
81
-			if lines > 0 {
82
-				f := cLog.(*os.File)
83
-				ls, err := tailfile.TailFile(f, lines)
84
-				if err != nil {
85
-					return err
86
-				}
87
-				tmp := bytes.NewBuffer([]byte{})
88
-				for _, l := range ls {
89
-					fmt.Fprintf(tmp, "%s\n", l)
90
-				}
91
-				cLog = tmp
81
+			n := maxFile
82
+			if config.Tail == "latest" && config.Since.IsZero() {
83
+				n = 1
92 84
 			}
93
-
94
-			dec := json.NewDecoder(cLog)
95
-			l := &jsonlog.JSONLog{}
96
-			for {
97
-				l.Reset()
98
-				if err := dec.Decode(l); err == io.EOF {
99
-					break
100
-				} else if err != nil {
101
-					logrus.Errorf("Error streaming logs: %s", err)
85
+			before := false
86
+			for i := n; i > 0; i-- {
87
+				if before {
102 88
 					break
103 89
 				}
104
-				logLine := l.Log
105
-				if !config.Since.IsZero() && l.Created.Before(config.Since) {
90
+				cLog, err := getReader(logDriver, i, n, lines)
91
+				if err != nil {
92
+					logrus.Debugf("Error reading %d log file: %v", i-1, err)
106 93
 					continue
107 94
 				}
108
-				if config.Timestamps {
109
-					// format can be "" or time format, so here can't be error
110
-					logLine, _ = l.Format(format)
111
-				}
112
-				if l.Stream == "stdout" && config.UseStdout {
113
-					io.WriteString(outStream, logLine)
95
+				//if lines are specified, then iterate only once
96
+				if lines > 0 {
97
+					i = 1
98
+				} else { // if lines are not specified, cLog is a file, It needs to be closed
99
+					defer cLog.(*os.File).Close()
114 100
 				}
115
-				if l.Stream == "stderr" && config.UseStderr {
116
-					io.WriteString(errStream, logLine)
101
+				dec := json.NewDecoder(cLog)
102
+				l := &jsonlog.JSONLog{}
103
+				for {
104
+					l.Reset()
105
+					if err := dec.Decode(l); err == io.EOF {
106
+						break
107
+					} else if err != nil {
108
+						logrus.Errorf("Error streaming logs: %s", err)
109
+						break
110
+					}
111
+					logLine := l.Log
112
+					if !config.Since.IsZero() && l.Created.Before(config.Since) {
113
+						continue
114
+					}
115
+					if config.Timestamps {
116
+						// format can be "" or time format, so here can't be error
117
+						logLine, _ = l.Format(format)
118
+					}
119
+					if l.Stream == "stdout" && config.UseStdout {
120
+						io.WriteString(outStream, logLine)
121
+					}
122
+					if l.Stream == "stderr" && config.UseStderr {
123
+						io.WriteString(errStream, logLine)
124
+					}
117 125
 				}
118 126
 			}
119 127
 		}
... ...
@@ -177,3 +202,36 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
177 177
 	}
178 178
 	return nil
179 179
 }
180
+
181
+func getReader(logDriver logger.Logger, fileIndex, maxFiles, lines int) (io.Reader, error) {
182
+	if lines <= 0 {
183
+		index := strconv.Itoa(fileIndex - 1)
184
+		cLog, err := logDriver.(logger.Reader).ReadLog(index)
185
+		return cLog, err
186
+	}
187
+	buf := bytes.NewBuffer([]byte{})
188
+	remaining := lines
189
+	for i := 0; i < maxFiles; i++ {
190
+		index := strconv.Itoa(i)
191
+		cLog, err := logDriver.(logger.Reader).ReadLog(index)
192
+		if err != nil {
193
+			return buf, err
194
+		}
195
+		f := cLog.(*os.File)
196
+		ls, err := tailfile.TailFile(f, remaining)
197
+		if err != nil {
198
+			return buf, err
199
+		}
200
+		tmp := bytes.NewBuffer([]byte{})
201
+		for _, l := range ls {
202
+			fmt.Fprintf(tmp, "%s\n", l)
203
+		}
204
+		tmp.ReadFrom(buf)
205
+		buf = tmp
206
+		if len(ls) == remaining {
207
+			return buf, nil
208
+		}
209
+		remaining = remaining - len(ls)
210
+	}
211
+	return buf, nil
212
+}
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	"github.com/docker/docker/autogen/dockerversion"
16 16
 	"github.com/docker/docker/cliconfig"
17 17
 	"github.com/docker/docker/daemon"
18
+	"github.com/docker/docker/daemon/logger"
18 19
 	flag "github.com/docker/docker/pkg/mflag"
19 20
 	"github.com/docker/docker/pkg/pidfile"
20 21
 	"github.com/docker/docker/pkg/signal"
... ...
@@ -94,6 +95,12 @@ func mainDaemon() {
94 94
 		logrus.Fatalf("Failed to set umask: %v", err)
95 95
 	}
96 96
 
97
+	if len(daemonCfg.LogConfig.Config) > 0 {
98
+		if err := logger.ValidateLogOpts(daemonCfg.LogConfig.Type, daemonCfg.LogConfig.Config); err != nil {
99
+			logrus.Fatalf("Failed to set log opts: %v", err)
100
+		}
101
+	}
102
+
97 103
 	var pfile *pidfile.PidFile
98 104
 	if daemonCfg.Pidfile != "" {
99 105
 		pf, err := pidfile.New(daemonCfg.Pidfile)
... ...
@@ -29,7 +29,7 @@ The `docker logs --follow` command will continue streaming the new output from
29 29
 the container's `STDOUT` and `STDERR`.
30 30
 
31 31
 Passing a negative number or a non-integer to `--tail` is invalid and the
32
-value is set to `all` in that case. This behavior may change in the future.
32
+value is set to `latest` in that case.
33 33
 
34 34
 The `docker logs --timestamp` commands will add an RFC3339Nano
35 35
 timestamp, for example `2014-09-16T06:17:46.000000000Z`, to each
... ...
@@ -17,7 +17,7 @@ container's logging driver. The following options are supported:
17 17
 
18 18
 | `none`      | Disables any logging for the container. `docker logs` won't be available with this driver.                                    |
19 19
 |-------------|-------------------------------------------------------------------------------------------------------------------------------|
20
-| `json-file` | Default logging driver for Docker. Writes JSON messages to file.  No logging options are supported for this driver.           |
20
+| `json-file` | Default logging driver for Docker. Writes JSON messages to file.                                                              |
21 21
 | `syslog`    | Syslog logging driver for Docker. Writes log messages to syslog.                                                              |
22 22
 | `journald`  | Journald logging driver for Docker. Writes log messages to `journald`.                                                        |
23 23
 | `gelf`      | Graylog Extended Log Format (GELF) logging driver for Docker. Writes log messages to a GELF endpoint likeGraylog or Logstash. |
... ...
@@ -25,6 +25,20 @@ container's logging driver. The following options are supported:
25 25
 
26 26
 The `docker logs`command is available only for the `json-file` logging driver.  
27 27
 
28
+### The json-file options
29
+
30
+The following logging options are supported for the `json-file` logging driver:
31
+
32
+    --log-opt max-size=[0-9+][k|m|g]
33
+    --log-opt max-file=[0-9+]
34
+
35
+Logs that reach `max-size` are rolled over. You can set the size in kilobytes(k), megabytes(m), or gigabytes(g). eg `--log-opt max-size=50m`. If `max-size` is not set, then logs are not rolled over.
36
+
37
+
38
+`max-file` specifies the maximum number of files that a log is rolled over before being discarded. eg `--log-opt max-file=100`. If `max-size` is not set, then `max-file` is not honored.
39
+
40
+If `max-size` and `max-file` are set, `docker logs` only returns the log lines from the newest log file. 
41
+
28 42
 ### The syslog options
29 43
 
30 44
 The following logging options are supported for the `syslog` logging driver:
... ...
@@ -422,7 +422,6 @@ func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]st
422 422
 	if loggingDriver == "none" && len(loggingOpts) > 0 {
423 423
 		return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
424 424
 	}
425
-	//TODO - validation step
426 425
 	return loggingOptsMap, nil
427 426
 }
428 427