Browse code

Merge pull request #10568 from LK4D4/logging_drivers

Logging drivers

Arnaud Porterie authored on 2015/03/18 01:45:58
Showing 23 changed files
... ...
@@ -1961,6 +1961,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
1961 1961
 		return err
1962 1962
 	}
1963 1963
 
1964
+	if env.GetSubEnv("HostConfig").GetSubEnv("LogConfig").Get("Type") != "json-file" {
1965
+		return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver")
1966
+	}
1967
+
1964 1968
 	v := url.Values{}
1965 1969
 	v.Set("stdout", "1")
1966 1970
 	v.Set("stderr", "1")
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"github.com/docker/docker/opts"
8 8
 	flag "github.com/docker/docker/pkg/mflag"
9 9
 	"github.com/docker/docker/pkg/ulimit"
10
+	"github.com/docker/docker/runconfig"
10 11
 )
11 12
 
12 13
 const (
... ...
@@ -47,6 +48,7 @@ type Config struct {
47 47
 	TrustKeyPath                string
48 48
 	Labels                      []string
49 49
 	Ulimits                     map[string]*ulimit.Ulimit
50
+	LogConfig                   runconfig.LogConfig
50 51
 }
51 52
 
52 53
 // InstallFlags adds command-line options to the top-level flag parser for
... ...
@@ -81,6 +83,7 @@ func (config *Config) InstallFlags() {
81 81
 	opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
82 82
 	config.Ulimits = make(map[string]*ulimit.Ulimit)
83 83
 	opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
84
+	flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver(json-file/none)")
84 85
 }
85 86
 
86 87
 func getDefaultNetworkMtu() int {
... ...
@@ -21,6 +21,8 @@ import (
21 21
 
22 22
 	log "github.com/Sirupsen/logrus"
23 23
 	"github.com/docker/docker/daemon/execdriver"
24
+	"github.com/docker/docker/daemon/logger"
25
+	"github.com/docker/docker/daemon/logger/jsonfilelog"
24 26
 	"github.com/docker/docker/engine"
25 27
 	"github.com/docker/docker/image"
26 28
 	"github.com/docker/docker/links"
... ...
@@ -98,9 +100,11 @@ type Container struct {
98 98
 	VolumesRW  map[string]bool
99 99
 	hostConfig *runconfig.HostConfig
100 100
 
101
-	activeLinks        map[string]*links.Link
102
-	monitor            *containerMonitor
103
-	execCommands       *execStore
101
+	activeLinks  map[string]*links.Link
102
+	monitor      *containerMonitor
103
+	execCommands *execStore
104
+	// logDriver for closing
105
+	logDriver          logger.Logger
104 106
 	AppliedVolumesFrom map[string]struct{}
105 107
 }
106 108
 
... ...
@@ -1355,21 +1359,36 @@ func (container *Container) setupWorkingDirectory() error {
1355 1355
 	return nil
1356 1356
 }
1357 1357
 
1358
-func (container *Container) startLoggingToDisk() error {
1359
-	// Setup logging of stdout and stderr to disk
1360
-	logPath, err := container.logPath("json")
1361
-	if err != nil {
1362
-		return err
1358
+func (container *Container) startLogging() error {
1359
+	cfg := container.hostConfig.LogConfig
1360
+	if cfg.Type == "" {
1361
+		cfg = container.daemon.defaultLogConfig
1363 1362
 	}
1364
-	container.LogPath = logPath
1363
+	var l logger.Logger
1364
+	switch cfg.Type {
1365
+	case "json-file":
1366
+		pth, err := container.logPath("json")
1367
+		if err != nil {
1368
+			return err
1369
+		}
1365 1370
 
1366
-	if err := container.daemon.LogToDisk(container.stdout, container.LogPath, "stdout"); err != nil {
1367
-		return err
1371
+		dl, err := jsonfilelog.New(pth)
1372
+		if err != nil {
1373
+			return err
1374
+		}
1375
+		l = dl
1376
+	case "none":
1377
+		return nil
1378
+	default:
1379
+		return fmt.Errorf("Unknown logging driver: %s", cfg.Type)
1368 1380
 	}
1369 1381
 
1370
-	if err := container.daemon.LogToDisk(container.stderr, container.LogPath, "stderr"); err != nil {
1382
+	if copier, err := logger.NewCopier(container.ID, map[string]io.Reader{"stdout": container.StdoutPipe(), "stderr": container.StderrPipe()}, l); err != nil {
1371 1383
 		return err
1384
+	} else {
1385
+		copier.Run()
1372 1386
 	}
1387
+	container.logDriver = l
1373 1388
 
1374 1389
 	return nil
1375 1390
 }
... ...
@@ -1470,3 +1489,12 @@ func (container *Container) getNetworkedContainer() (*Container, error) {
1470 1470
 func (container *Container) Stats() (*execdriver.ResourceStats, error) {
1471 1471
 	return container.daemon.Stats(container)
1472 1472
 }
1473
+
1474
+func (c *Container) LogDriverType() string {
1475
+	c.Lock()
1476
+	defer c.Unlock()
1477
+	if c.hostConfig.LogConfig.Type == "" {
1478
+		return c.daemon.defaultLogConfig.Type
1479
+	}
1480
+	return c.hostConfig.LogConfig.Type
1481
+}
... ...
@@ -89,23 +89,24 @@ func (c *contStore) List() []*Container {
89 89
 }
90 90
 
91 91
 type Daemon struct {
92
-	ID             string
93
-	repository     string
94
-	sysInitPath    string
95
-	containers     *contStore
96
-	execCommands   *execStore
97
-	graph          *graph.Graph
98
-	repositories   *graph.TagStore
99
-	idIndex        *truncindex.TruncIndex
100
-	sysInfo        *sysinfo.SysInfo
101
-	volumes        *volumes.Repository
102
-	eng            *engine.Engine
103
-	config         *Config
104
-	containerGraph *graphdb.Database
105
-	driver         graphdriver.Driver
106
-	execDriver     execdriver.Driver
107
-	trustStore     *trust.TrustStore
108
-	statsCollector *statsCollector
92
+	ID               string
93
+	repository       string
94
+	sysInitPath      string
95
+	containers       *contStore
96
+	execCommands     *execStore
97
+	graph            *graph.Graph
98
+	repositories     *graph.TagStore
99
+	idIndex          *truncindex.TruncIndex
100
+	sysInfo          *sysinfo.SysInfo
101
+	volumes          *volumes.Repository
102
+	eng              *engine.Engine
103
+	config           *Config
104
+	containerGraph   *graphdb.Database
105
+	driver           graphdriver.Driver
106
+	execDriver       execdriver.Driver
107
+	trustStore       *trust.TrustStore
108
+	statsCollector   *statsCollector
109
+	defaultLogConfig runconfig.LogConfig
109 110
 }
110 111
 
111 112
 // Install installs daemon capabilities to eng.
... ...
@@ -991,23 +992,24 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
991 991
 	}
992 992
 
993 993
 	daemon := &Daemon{
994
-		ID:             trustKey.PublicKey().KeyID(),
995
-		repository:     daemonRepo,
996
-		containers:     &contStore{s: make(map[string]*Container)},
997
-		execCommands:   newExecStore(),
998
-		graph:          g,
999
-		repositories:   repositories,
1000
-		idIndex:        truncindex.NewTruncIndex([]string{}),
1001
-		sysInfo:        sysInfo,
1002
-		volumes:        volumes,
1003
-		config:         config,
1004
-		containerGraph: graph,
1005
-		driver:         driver,
1006
-		sysInitPath:    sysInitPath,
1007
-		execDriver:     ed,
1008
-		eng:            eng,
1009
-		trustStore:     t,
1010
-		statsCollector: newStatsCollector(1 * time.Second),
994
+		ID:               trustKey.PublicKey().KeyID(),
995
+		repository:       daemonRepo,
996
+		containers:       &contStore{s: make(map[string]*Container)},
997
+		execCommands:     newExecStore(),
998
+		graph:            g,
999
+		repositories:     repositories,
1000
+		idIndex:          truncindex.NewTruncIndex([]string{}),
1001
+		sysInfo:          sysInfo,
1002
+		volumes:          volumes,
1003
+		config:           config,
1004
+		containerGraph:   graph,
1005
+		driver:           driver,
1006
+		sysInitPath:      sysInitPath,
1007
+		execDriver:       ed,
1008
+		eng:              eng,
1009
+		trustStore:       t,
1010
+		statsCollector:   newStatsCollector(1 * time.Second),
1011
+		defaultLogConfig: config.LogConfig,
1011 1012
 	}
1012 1013
 
1013 1014
 	// Setup shutdown handlers
... ...
@@ -62,6 +62,14 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status {
62 62
 			container.hostConfig.Links = append(container.hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias))
63 63
 		}
64 64
 	}
65
+	// we need this trick to preserve empty log driver, so
66
+	// container will use daemon defaults even if daemon change them
67
+	if container.hostConfig.LogConfig.Type == "" {
68
+		container.hostConfig.LogConfig = daemon.defaultLogConfig
69
+		defer func() {
70
+			container.hostConfig.LogConfig = runconfig.LogConfig{}
71
+		}()
72
+	}
65 73
 
66 74
 	out.SetJson("HostConfig", container.hostConfig)
67 75
 
68 76
new file mode 100644
... ...
@@ -0,0 +1,48 @@
0
+package logger
1
+
2
+import (
3
+	"bufio"
4
+	"io"
5
+	"time"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+// Copier can copy logs from specified sources to Logger and attach
11
+// ContainerID and Timestamp.
12
+// Writes are concurrent, so you need implement some sync in your logger
13
+type Copier struct {
14
+	// cid is container id for which we copying logs
15
+	cid string
16
+	// srcs is map of name -> reader pairs, for example "stdout", "stderr"
17
+	srcs map[string]io.Reader
18
+	dst  Logger
19
+}
20
+
21
+// NewCopier creates new Copier
22
+func NewCopier(cid string, srcs map[string]io.Reader, dst Logger) (*Copier, error) {
23
+	return &Copier{
24
+		cid:  cid,
25
+		srcs: srcs,
26
+		dst:  dst,
27
+	}, nil
28
+}
29
+
30
+// Run starts logs copying
31
+func (c *Copier) Run() {
32
+	for src, w := range c.srcs {
33
+		go c.copySrc(src, w)
34
+	}
35
+}
36
+
37
+func (c *Copier) copySrc(name string, src io.Reader) {
38
+	scanner := bufio.NewScanner(src)
39
+	for scanner.Scan() {
40
+		if err := c.dst.Log(&Message{ContainerID: c.cid, Line: scanner.Bytes(), Source: name, Timestamp: time.Now().UTC()}); err != nil {
41
+			logrus.Errorf("Failed to log msg %q for logger %s: %s", scanner.Bytes(), c.dst.Name(), err)
42
+		}
43
+	}
44
+	if err := scanner.Err(); err != nil {
45
+		logrus.Errorf("Error scanning log stream: %s", err)
46
+	}
47
+}
0 48
new file mode 100644
... ...
@@ -0,0 +1,100 @@
0
+package logger
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"io"
6
+	"testing"
7
+	"time"
8
+)
9
+
10
+type TestLoggerJSON struct {
11
+	*json.Encoder
12
+}
13
+
14
+func (l *TestLoggerJSON) Log(m *Message) error {
15
+	return l.Encode(m)
16
+}
17
+
18
+func (l *TestLoggerJSON) Close() error {
19
+	return nil
20
+}
21
+
22
+func (l *TestLoggerJSON) Name() string {
23
+	return "json"
24
+}
25
+
26
+type TestLoggerText struct {
27
+	*bytes.Buffer
28
+}
29
+
30
+func (l *TestLoggerText) Log(m *Message) error {
31
+	_, err := l.WriteString(m.ContainerID + " " + m.Source + " " + string(m.Line) + "\n")
32
+	return err
33
+}
34
+
35
+func (l *TestLoggerText) Close() error {
36
+	return nil
37
+}
38
+
39
+func (l *TestLoggerText) Name() string {
40
+	return "text"
41
+}
42
+
43
+func TestCopier(t *testing.T) {
44
+	stdoutLine := "Line that thinks that it is log line from docker stdout"
45
+	stderrLine := "Line that thinks that it is log line from docker stderr"
46
+	var stdout bytes.Buffer
47
+	var stderr bytes.Buffer
48
+	for i := 0; i < 30; i++ {
49
+		if _, err := stdout.WriteString(stdoutLine + "\n"); err != nil {
50
+			t.Fatal(err)
51
+		}
52
+		if _, err := stderr.WriteString(stderrLine + "\n"); err != nil {
53
+			t.Fatal(err)
54
+		}
55
+	}
56
+
57
+	var jsonBuf bytes.Buffer
58
+
59
+	jsonLog := &TestLoggerJSON{Encoder: json.NewEncoder(&jsonBuf)}
60
+
61
+	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
62
+	c, err := NewCopier(cid,
63
+		map[string]io.Reader{
64
+			"stdout": &stdout,
65
+			"stderr": &stderr,
66
+		},
67
+		jsonLog)
68
+	if err != nil {
69
+		t.Fatal(err)
70
+	}
71
+	c.Run()
72
+	time.Sleep(100 * time.Millisecond)
73
+	dec := json.NewDecoder(&jsonBuf)
74
+	for {
75
+		var msg Message
76
+		if err := dec.Decode(&msg); err != nil {
77
+			if err == io.EOF {
78
+				break
79
+			}
80
+			t.Fatal(err)
81
+		}
82
+		if msg.Source != "stdout" && msg.Source != "stderr" {
83
+			t.Fatalf("Wrong Source: %q, should be %q or %q", msg.Source, "stdout", "stderr")
84
+		}
85
+		if msg.ContainerID != cid {
86
+			t.Fatalf("Wrong ContainerID: %q, expected %q", msg.ContainerID, cid)
87
+		}
88
+		if msg.Source == "stdout" {
89
+			if string(msg.Line) != stdoutLine {
90
+				t.Fatalf("Wrong Line: %q, expected %q", msg.Line, stdoutLine)
91
+			}
92
+		}
93
+		if msg.Source == "stderr" {
94
+			if string(msg.Line) != stderrLine {
95
+				t.Fatalf("Wrong Line: %q, expected %q", msg.Line, stderrLine)
96
+			}
97
+		}
98
+	}
99
+}
0 100
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+package jsonfilelog
1
+
2
+import (
3
+	"bytes"
4
+	"os"
5
+
6
+	"github.com/docker/docker/daemon/logger"
7
+	"github.com/docker/docker/pkg/jsonlog"
8
+)
9
+
10
+// JSONFileLogger is Logger implementation for default docker logging:
11
+// JSON objects to file
12
+type JSONFileLogger struct {
13
+	buf *bytes.Buffer
14
+	f   *os.File // store for closing
15
+}
16
+
17
+// New creates new JSONFileLogger which writes to filename
18
+func New(filename string) (logger.Logger, error) {
19
+	log, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
20
+	if err != nil {
21
+		return nil, err
22
+	}
23
+	return &JSONFileLogger{
24
+		f:   log,
25
+		buf: bytes.NewBuffer(nil),
26
+	}, nil
27
+}
28
+
29
+// Log converts logger.Message to jsonlog.JSONLog and serializes it to file
30
+func (l *JSONFileLogger) Log(msg *logger.Message) error {
31
+	err := (&jsonlog.JSONLog{Log: string(msg.Line) + "\n", Stream: msg.Source, Created: msg.Timestamp}).MarshalJSONBuf(l.buf)
32
+	if err != nil {
33
+		return err
34
+	}
35
+	l.buf.WriteByte('\n')
36
+	_, err = l.buf.WriteTo(l.f)
37
+	return err
38
+}
39
+
40
+// Close closes underlying file
41
+func (l *JSONFileLogger) Close() error {
42
+	return l.f.Close()
43
+}
44
+
45
+// Name returns name of this logger
46
+func (l *JSONFileLogger) Name() string {
47
+	return "JSONFile"
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,78 @@
0
+package jsonfilelog
1
+
2
+import (
3
+	"io/ioutil"
4
+	"os"
5
+	"path/filepath"
6
+	"testing"
7
+	"time"
8
+
9
+	"github.com/docker/docker/daemon/logger"
10
+	"github.com/docker/docker/pkg/jsonlog"
11
+)
12
+
13
+func TestJSONFileLogger(t *testing.T) {
14
+	tmp, err := ioutil.TempDir("", "docker-logger-")
15
+	if err != nil {
16
+		t.Fatal(err)
17
+	}
18
+	defer os.RemoveAll(tmp)
19
+	filename := filepath.Join(tmp, "container.log")
20
+	l, err := New(filename)
21
+	if err != nil {
22
+		t.Fatal(err)
23
+	}
24
+	defer l.Close()
25
+	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
26
+	if err := l.Log(&logger.Message{ContainerID: cid, Line: []byte("line1"), Source: "src1"}); err != nil {
27
+		t.Fatal(err)
28
+	}
29
+	if err := l.Log(&logger.Message{ContainerID: cid, Line: []byte("line2"), Source: "src2"}); err != nil {
30
+		t.Fatal(err)
31
+	}
32
+	if err := l.Log(&logger.Message{ContainerID: cid, Line: []byte("line3"), Source: "src3"}); err != nil {
33
+		t.Fatal(err)
34
+	}
35
+	res, err := ioutil.ReadFile(filename)
36
+	if err != nil {
37
+		t.Fatal(err)
38
+	}
39
+	expected := `{"log":"line1\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
40
+{"log":"line2\n","stream":"src2","time":"0001-01-01T00:00:00Z"}
41
+{"log":"line3\n","stream":"src3","time":"0001-01-01T00:00:00Z"}
42
+`
43
+
44
+	if string(res) != expected {
45
+		t.Fatalf("Wrong log content: %q, expected %q", res, expected)
46
+	}
47
+}
48
+
49
+func BenchmarkJSONFileLogger(b *testing.B) {
50
+	tmp, err := ioutil.TempDir("", "docker-logger-")
51
+	if err != nil {
52
+		b.Fatal(err)
53
+	}
54
+	defer os.RemoveAll(tmp)
55
+	filename := filepath.Join(tmp, "container.log")
56
+	l, err := New(filename)
57
+	if err != nil {
58
+		b.Fatal(err)
59
+	}
60
+	defer l.Close()
61
+	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
62
+	testLine := "Line that thinks that it is log line from docker\n"
63
+	msg := &logger.Message{ContainerID: cid, Line: []byte(testLine), Source: "stderr", Timestamp: time.Now().UTC()}
64
+	jsonlog, err := (&jsonlog.JSONLog{Log: string(msg.Line) + "\n", Stream: msg.Source, Created: msg.Timestamp}).MarshalJSON()
65
+	if err != nil {
66
+		b.Fatal(err)
67
+	}
68
+	b.SetBytes(int64(len(jsonlog)+1) * 30)
69
+	b.ResetTimer()
70
+	for i := 0; i < b.N; i++ {
71
+		for j := 0; j < 30; j++ {
72
+			if err := l.Log(msg); err != nil {
73
+				b.Fatal(err)
74
+			}
75
+		}
76
+	}
77
+}
0 78
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+package logger
1
+
2
+import "time"
3
+
4
+// Message is datastructure that represents record from some container
5
+type Message struct {
6
+	ContainerID string
7
+	Line        []byte
8
+	Source      string
9
+	Timestamp   time.Time
10
+}
11
+
12
+// Logger is interface for docker logging drivers
13
+type Logger interface {
14
+	Log(*Message) error
15
+	Name() string
16
+	Close() error
17
+}
... ...
@@ -44,6 +44,9 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) engine.Status {
44 44
 	if err != nil {
45 45
 		return job.Error(err)
46 46
 	}
47
+	if container.LogDriverType() != "json-file" {
48
+		return job.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
49
+	}
47 50
 	cLog, err := container.ReadLog("json")
48 51
 	if err != nil && os.IsNotExist(err) {
49 52
 		// Legacy logs
... ...
@@ -123,7 +123,7 @@ func (m *containerMonitor) Start() error {
123 123
 	for {
124 124
 		m.container.RestartCount++
125 125
 
126
-		if err := m.container.startLoggingToDisk(); err != nil {
126
+		if err := m.container.startLogging(); err != nil {
127 127
 			m.resetContainer(false)
128 128
 
129 129
 			return err
... ...
@@ -302,6 +302,11 @@ func (m *containerMonitor) resetContainer(lock bool) {
302 302
 		container.stdin, container.stdinPipe = io.Pipe()
303 303
 	}
304 304
 
305
+	if container.logDriver != nil {
306
+		container.logDriver.Close()
307
+		container.logDriver = nil
308
+	}
309
+
305 310
 	c := container.command.ProcessConfig.Cmd
306 311
 
307 312
 	container.command.ProcessConfig.Cmd = exec.Cmd{
... ...
@@ -28,6 +28,7 @@ docker-create - Create a new container
28 28
 [**--label-file**[=*[]*]]
29 29
 [**--link**[=*[]*]]
30 30
 [**--lxc-conf**[=*[]*]]
31
+[**--log-driver**[=*[]*]]
31 32
 [**-m**|**--memory**[=*MEMORY*]]
32 33
 [**--memory-swap**[=*MEMORY-SWAP*]]
33 34
 [**--mac-address**[=*MAC-ADDRESS*]]
... ...
@@ -116,6 +117,10 @@ IMAGE [COMMAND] [ARG...]
116 116
 **--lxc-conf**=[]
117 117
    (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
118 118
 
119
+**--log-driver**="|*json-file*|*none*"
120
+  Logging driver for container. Default is defined by daemon `--log-driver` flag.
121
+  **Warning**: `docker logs` command works only for `json-file` logging driver.
122
+
119 123
 **-m**, **--memory**=""
120 124
    Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
121 125
 
... ...
@@ -22,6 +22,8 @@ The **docker logs --follow** command combines commands **docker logs** and
22 22
 **docker attach**. It will first return all logs from the beginning and
23 23
 then continue streaming new output from the container’s stdout and stderr.
24 24
 
25
+**Warning**: This command works only for **json-file** logging driver.
26
+
25 27
 # OPTIONS
26 28
 **--help**
27 29
   Print usage statement
... ...
@@ -29,6 +29,7 @@ docker-run - Run a command in a new container
29 29
 [**--label-file**[=*[]*]]
30 30
 [**--link**[=*[]*]]
31 31
 [**--lxc-conf**[=*[]*]]
32
+[**--log-driver**[=*[]*]]
32 33
 [**-m**|**--memory**[=*MEMORY*]]
33 34
 [**--memory-swap**[=*MEMORY-SWAP]]
34 35
 [**--mac-address**[=*MAC-ADDRESS*]]
... ...
@@ -217,6 +218,10 @@ which interface and port to use.
217 217
 **--lxc-conf**=[]
218 218
    (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
219 219
 
220
+**--log-driver**="|*json-file*|*none*"
221
+  Logging driver for container. Default is defined by daemon `--log-driver` flag.
222
+  **Warning**: `docker logs` command works only for `json-file` logging driver.
223
+
220 224
 **-m**, **--memory**=""
221 225
    Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
222 226
 
... ...
@@ -89,6 +89,10 @@ unix://[/path/to/socket] to use.
89 89
 **--label**="[]"
90 90
   Set key=value labels to the daemon (displayed in `docker info`)
91 91
 
92
+**--log-driver**="*json-file*|*none*"
93
+  Container's logging driver. Default is `default`.
94
+  **Warning**: `docker logs` command works only for `json-file` logging driver.
95
+
92 96
 **--mtu**=VALUE
93 97
   Set the containers network mtu. Default is `0`.
94 98
 
... ...
@@ -161,7 +161,8 @@ Create a container
161 161
                "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
162 162
                "NetworkMode": "bridge",
163 163
                "Devices": [],
164
-               "Ulimits": [{}]
164
+               "Ulimits": [{}],
165
+               "LogConfig": { "Type": "json-file", Config: {} }
165 166
             }
166 167
         }
167 168
 
... ...
@@ -255,6 +256,10 @@ Json Parameters:
255 255
   -   **Ulimits** - A list of ulimits to be set in the container, specified as
256 256
         `{ "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }`, for example:
257 257
         `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
258
+  -   **LogConfig** - Logging configuration to container, format
259
+        `{ "Type": "<driver_name>", "Config": {"key1": "val1"}}
260
+        Available types: `json-file`, `none`.
261
+        `json-file` logging driver.
258 262
 
259 263
 Query Parameters:
260 264
 
... ...
@@ -352,6 +357,7 @@ Return low-level information on the container `id`
352 352
 				"MaximumRetryCount": 2,
353 353
 				"Name": "on-failure"
354 354
 			},
355
+           "LogConfig": { "Type": "json-file", Config: {} },
355 356
 			"SecurityOpt": null,
356 357
 			"VolumesFrom": null,
357 358
 			"Ulimits": [{}]
... ...
@@ -448,6 +454,9 @@ Status Codes:
448 448
 
449 449
 Get stdout and stderr logs from the container ``id``
450 450
 
451
+> **Note**:
452
+> This endpoint works only for containers with `json-file` logging driver.
453
+
451 454
 **Example request**:
452 455
 
453 456
        GET /containers/4fa6e0f0c678/logs?stderr=1&stdout=1&timestamps=1&follow=1&tail=10 HTTP/1.1
... ...
@@ -97,6 +97,7 @@ expect an integer, and they can only be specified once.
97 97
       --ipv6=false                           Enable IPv6 networking
98 98
       -l, --log-level="info"                 Set the logging level
99 99
       --label=[]                             Set key=value labels to the daemon
100
+      --log-driver="json-file"               Container's logging driver (json-file/none)
100 101
       --mtu=0                                Set the containers network MTU
101 102
       -p, --pidfile="/var/run/docker.pid"    Path to use for daemon PID file
102 103
       --registry-mirror=[]                   Preferred Docker registry mirror
... ...
@@ -817,6 +818,7 @@ Creates a new container.
817 817
       -l, --label=[]             Set metadata on the container (e.g., --label=com.example.key=value)
818 818
       --label-file=[]            Read in a line delimited file of labels
819 819
       --link=[]                  Add link to another container
820
+      --log-driver=""            Logging driver for container
820 821
       --lxc-conf=[]              Add custom lxc options
821 822
       -m, --memory=""            Memory limit
822 823
       --mac-address=""           Container MAC address (e.g. 92:d0:c6:0a:29:33)
... ...
@@ -1447,6 +1449,9 @@ For example:
1447 1447
       -t, --timestamps=false    Show timestamps
1448 1448
       --tail="all"              Number of lines to show from the end of the logs
1449 1449
 
1450
+NOTE: this command is available only for containers with `json-file` logging
1451
+driver.
1452
+
1450 1453
 The `docker logs` command batch-retrieves logs present at the time of execution.
1451 1454
 
1452 1455
 The `docker logs --follow` command will continue streaming the new output from
... ...
@@ -1722,6 +1727,7 @@ To remove an image using its digest:
1722 1722
       -i, --interactive=false    Keep STDIN open even if not attached
1723 1723
       --ipc=""                   IPC namespace to use
1724 1724
       --link=[]                  Add link to another container
1725
+      --log-driver=""            Logging driver for container
1725 1726
       --lxc-conf=[]              Add custom lxc options
1726 1727
       -m, --memory=""            Memory limit
1727 1728
       -l, --label=[]             Set metadata on the container (e.g., --label=com.example.key=value)
... ...
@@ -642,6 +642,20 @@ familiar with using LXC directly.
642 642
 > you can use `--lxc-conf` to set a container's IP address, but this will not be
643 643
 > reflected in the `/etc/hosts` file.
644 644
 
645
+## Logging drivers (--log-driver)
646
+
647
+You can specify a different logging driver for the container than for the daemon.
648
+
649
+### Logging driver: none
650
+
651
+Disables any logging for the container. `docker logs` won't be available with
652
+this driver.
653
+
654
+### Log driver: json-file
655
+
656
+Default logging driver for Docker. Writes JSON messages to file. `docker logs`
657
+command is available only for this logging driver
658
+
645 659
 ## Overriding Dockerfile image defaults
646 660
 
647 661
 When a developer builds an image from a [*Dockerfile*](/reference/builder)
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"path/filepath"
12 12
 	"strings"
13 13
 	"testing"
14
+	"time"
14 15
 
15 16
 	"github.com/docker/libtrust"
16 17
 )
... ...
@@ -560,3 +561,168 @@ func TestDaemonRestartRenameContainer(t *testing.T) {
560 560
 
561 561
 	logDone("daemon - rename persists through daemon restart")
562 562
 }
563
+
564
+func TestDaemonLoggingDriverDefault(t *testing.T) {
565
+	d := NewDaemon(t)
566
+
567
+	if err := d.StartWithBusybox(); err != nil {
568
+		t.Fatal(err)
569
+	}
570
+	defer d.Stop()
571
+
572
+	out, err := d.Cmd("run", "-d", "busybox", "echo", "testline")
573
+	if err != nil {
574
+		t.Fatal(out, err)
575
+	}
576
+	id := strings.TrimSpace(out)
577
+
578
+	if out, err := d.Cmd("wait", id); err != nil {
579
+		t.Fatal(out, err)
580
+	}
581
+	logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
582
+
583
+	if _, err := os.Stat(logPath); err != nil {
584
+		t.Fatal(err)
585
+	}
586
+	f, err := os.Open(logPath)
587
+	if err != nil {
588
+		t.Fatal(err)
589
+	}
590
+	var res struct {
591
+		Log    string    `json:log`
592
+		Stream string    `json:stream`
593
+		Time   time.Time `json:time`
594
+	}
595
+	if err := json.NewDecoder(f).Decode(&res); err != nil {
596
+		t.Fatal(err)
597
+	}
598
+	if res.Log != "testline\n" {
599
+		t.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n")
600
+	}
601
+	if res.Stream != "stdout" {
602
+		t.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout")
603
+	}
604
+	if !time.Now().After(res.Time) {
605
+		t.Fatalf("Log time %v in future", res.Time)
606
+	}
607
+	logDone("daemon - default 'json-file' logging driver")
608
+}
609
+
610
+func TestDaemonLoggingDriverDefaultOverride(t *testing.T) {
611
+	d := NewDaemon(t)
612
+
613
+	if err := d.StartWithBusybox(); err != nil {
614
+		t.Fatal(err)
615
+	}
616
+	defer d.Stop()
617
+
618
+	out, err := d.Cmd("run", "-d", "--log-driver=none", "busybox", "echo", "testline")
619
+	if err != nil {
620
+		t.Fatal(out, err)
621
+	}
622
+	id := strings.TrimSpace(out)
623
+
624
+	if out, err := d.Cmd("wait", id); err != nil {
625
+		t.Fatal(out, err)
626
+	}
627
+	logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
628
+
629
+	if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) {
630
+		t.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err)
631
+	}
632
+	logDone("daemon - default logging driver override in run")
633
+}
634
+
635
+func TestDaemonLoggingDriverNone(t *testing.T) {
636
+	d := NewDaemon(t)
637
+
638
+	if err := d.StartWithBusybox("--log-driver=none"); err != nil {
639
+		t.Fatal(err)
640
+	}
641
+	defer d.Stop()
642
+
643
+	out, err := d.Cmd("run", "-d", "busybox", "echo", "testline")
644
+	if err != nil {
645
+		t.Fatal(out, err)
646
+	}
647
+	id := strings.TrimSpace(out)
648
+	if out, err := d.Cmd("wait", id); err != nil {
649
+		t.Fatal(out, err)
650
+	}
651
+
652
+	logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
653
+
654
+	if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) {
655
+		t.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err)
656
+	}
657
+	logDone("daemon - 'none' logging driver")
658
+}
659
+
660
+func TestDaemonLoggingDriverNoneOverride(t *testing.T) {
661
+	d := NewDaemon(t)
662
+
663
+	if err := d.StartWithBusybox("--log-driver=none"); err != nil {
664
+		t.Fatal(err)
665
+	}
666
+	defer d.Stop()
667
+
668
+	out, err := d.Cmd("run", "-d", "--log-driver=json-file", "busybox", "echo", "testline")
669
+	if err != nil {
670
+		t.Fatal(out, err)
671
+	}
672
+	id := strings.TrimSpace(out)
673
+
674
+	if out, err := d.Cmd("wait", id); err != nil {
675
+		t.Fatal(out, err)
676
+	}
677
+	logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
678
+
679
+	if _, err := os.Stat(logPath); err != nil {
680
+		t.Fatal(err)
681
+	}
682
+	f, err := os.Open(logPath)
683
+	if err != nil {
684
+		t.Fatal(err)
685
+	}
686
+	var res struct {
687
+		Log    string    `json:log`
688
+		Stream string    `json:stream`
689
+		Time   time.Time `json:time`
690
+	}
691
+	if err := json.NewDecoder(f).Decode(&res); err != nil {
692
+		t.Fatal(err)
693
+	}
694
+	if res.Log != "testline\n" {
695
+		t.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n")
696
+	}
697
+	if res.Stream != "stdout" {
698
+		t.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout")
699
+	}
700
+	if !time.Now().After(res.Time) {
701
+		t.Fatalf("Log time %v in future", res.Time)
702
+	}
703
+	logDone("daemon - 'none' logging driver override in run")
704
+}
705
+
706
+func TestDaemonLoggingDriverNoneLogsError(t *testing.T) {
707
+	d := NewDaemon(t)
708
+
709
+	if err := d.StartWithBusybox("--log-driver=none"); err != nil {
710
+		t.Fatal(err)
711
+	}
712
+	defer d.Stop()
713
+
714
+	out, err := d.Cmd("run", "-d", "busybox", "echo", "testline")
715
+	if err != nil {
716
+		t.Fatal(out, err)
717
+	}
718
+	id := strings.TrimSpace(out)
719
+	out, err = d.Cmd("logs", id)
720
+	if err == nil {
721
+		t.Fatalf("Logs should fail with \"none\" driver")
722
+	}
723
+	if !strings.Contains(out, `\"logs\" command is supported only for \"json-file\" logging driver`) {
724
+		t.Fatalf("There should be error about non-json-file driver, got %s", out)
725
+	}
726
+	logDone("daemon - logs not available for non-json-file drivers")
727
+}
... ...
@@ -191,6 +191,7 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine {
191 191
 		// otherwise NewDaemon will fail because of conflicting settings.
192 192
 		InterContainerCommunication: true,
193 193
 		TrustKeyPath:                filepath.Join(root, "key.json"),
194
+		LogConfig:                   runconfig.LogConfig{Type: "json-file"},
194 195
 	}
195 196
 	d, err := daemon.NewDaemon(cfg, eng)
196 197
 	if err != nil {
... ...
@@ -99,6 +99,11 @@ type RestartPolicy struct {
99 99
 	MaximumRetryCount int
100 100
 }
101 101
 
102
+type LogConfig struct {
103
+	Type   string
104
+	Config map[string]string
105
+}
106
+
102 107
 type HostConfig struct {
103 108
 	Binds           []string
104 109
 	ContainerIDFile string
... ...
@@ -125,6 +130,7 @@ type HostConfig struct {
125 125
 	SecurityOpt     []string
126 126
 	ReadonlyRootfs  bool
127 127
 	Ulimits         []*ulimit.Ulimit
128
+	LogConfig       LogConfig
128 129
 }
129 130
 
130 131
 // This is used by the create command when you want to set both the
... ...
@@ -189,9 +195,8 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
189 189
 	job.GetenvJson("PortBindings", &hostConfig.PortBindings)
190 190
 	job.GetenvJson("Devices", &hostConfig.Devices)
191 191
 	job.GetenvJson("RestartPolicy", &hostConfig.RestartPolicy)
192
-
193 192
 	job.GetenvJson("Ulimits", &hostConfig.Ulimits)
194
-
193
+	job.GetenvJson("LogConfig", &hostConfig.LogConfig)
195 194
 	hostConfig.SecurityOpt = job.GetenvList("SecurityOpt")
196 195
 	if Binds := job.GetenvList("Binds"); Binds != nil {
197 196
 		hostConfig.Binds = Binds
... ...
@@ -70,6 +70,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
70 70
 		flIpcMode         = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
71 71
 		flRestartPolicy   = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
72 72
 		flReadonlyRootfs  = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
73
+		flLoggingDriver   = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
73 74
 	)
74 75
 
75 76
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
... ...
@@ -330,6 +331,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
330 330
 		SecurityOpt:     flSecurityOpt.GetAll(),
331 331
 		ReadonlyRootfs:  *flReadonlyRootfs,
332 332
 		Ulimits:         flUlimits.GetList(),
333
+		LogConfig:       LogConfig{Type: *flLoggingDriver},
333 334
 	}
334 335
 
335 336
 	// When allocating stdin in attached mode, close stdin at client disconnect