浏览代码

Merge pull request #10568 from LK4D4/logging_drivers

Logging drivers
Arnaud Porterie 10 年之前
父节点
当前提交
1ff5a91007

+ 4 - 0
api/client/commands.go

@@ -1961,6 +1961,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
 		return err
 		return err
 	}
 	}
 
 
+	if env.GetSubEnv("HostConfig").GetSubEnv("LogConfig").Get("Type") != "json-file" {
+		return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver")
+	}
+
 	v := url.Values{}
 	v := url.Values{}
 	v.Set("stdout", "1")
 	v.Set("stdout", "1")
 	v.Set("stderr", "1")
 	v.Set("stderr", "1")

+ 3 - 0
daemon/config.go

@@ -7,6 +7,7 @@ import (
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/pkg/ulimit"
 	"github.com/docker/docker/pkg/ulimit"
+	"github.com/docker/docker/runconfig"
 )
 )
 
 
 const (
 const (
@@ -47,6 +48,7 @@ type Config struct {
 	TrustKeyPath                string
 	TrustKeyPath                string
 	Labels                      []string
 	Labels                      []string
 	Ulimits                     map[string]*ulimit.Ulimit
 	Ulimits                     map[string]*ulimit.Ulimit
+	LogConfig                   runconfig.LogConfig
 }
 }
 
 
 // InstallFlags adds command-line options to the top-level flag parser for
 // InstallFlags adds command-line options to the top-level flag parser for
@@ -81,6 +83,7 @@ func (config *Config) InstallFlags() {
 	opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
 	opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
 	config.Ulimits = make(map[string]*ulimit.Ulimit)
 	config.Ulimits = make(map[string]*ulimit.Ulimit)
 	opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
 	opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
+	flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver(json-file/none)")
 }
 }
 
 
 func getDefaultNetworkMtu() int {
 func getDefaultNetworkMtu() int {

+ 40 - 12
daemon/container.go

@@ -21,6 +21,8 @@ import (
 
 
 	log "github.com/Sirupsen/logrus"
 	log "github.com/Sirupsen/logrus"
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/daemon/execdriver"
+	"github.com/docker/docker/daemon/logger"
+	"github.com/docker/docker/daemon/logger/jsonfilelog"
 	"github.com/docker/docker/engine"
 	"github.com/docker/docker/engine"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/links"
 	"github.com/docker/docker/links"
@@ -98,9 +100,11 @@ type Container struct {
 	VolumesRW  map[string]bool
 	VolumesRW  map[string]bool
 	hostConfig *runconfig.HostConfig
 	hostConfig *runconfig.HostConfig
 
 
-	activeLinks        map[string]*links.Link
-	monitor            *containerMonitor
-	execCommands       *execStore
+	activeLinks  map[string]*links.Link
+	monitor      *containerMonitor
+	execCommands *execStore
+	// logDriver for closing
+	logDriver          logger.Logger
 	AppliedVolumesFrom map[string]struct{}
 	AppliedVolumesFrom map[string]struct{}
 }
 }
 
 
@@ -1355,21 +1359,36 @@ func (container *Container) setupWorkingDirectory() error {
 	return nil
 	return nil
 }
 }
 
 
-func (container *Container) startLoggingToDisk() error {
-	// Setup logging of stdout and stderr to disk
-	logPath, err := container.logPath("json")
-	if err != nil {
-		return err
+func (container *Container) startLogging() error {
+	cfg := container.hostConfig.LogConfig
+	if cfg.Type == "" {
+		cfg = container.daemon.defaultLogConfig
 	}
 	}
-	container.LogPath = logPath
+	var l logger.Logger
+	switch cfg.Type {
+	case "json-file":
+		pth, err := container.logPath("json")
+		if err != nil {
+			return err
+		}
 
 
-	if err := container.daemon.LogToDisk(container.stdout, container.LogPath, "stdout"); err != nil {
-		return err
+		dl, err := jsonfilelog.New(pth)
+		if err != nil {
+			return err
+		}
+		l = dl
+	case "none":
+		return nil
+	default:
+		return fmt.Errorf("Unknown logging driver: %s", cfg.Type)
 	}
 	}
 
 
-	if err := container.daemon.LogToDisk(container.stderr, container.LogPath, "stderr"); err != nil {
+	if copier, err := logger.NewCopier(container.ID, map[string]io.Reader{"stdout": container.StdoutPipe(), "stderr": container.StderrPipe()}, l); err != nil {
 		return err
 		return err
+	} else {
+		copier.Run()
 	}
 	}
+	container.logDriver = l
 
 
 	return nil
 	return nil
 }
 }
@@ -1470,3 +1489,12 @@ func (container *Container) getNetworkedContainer() (*Container, error) {
 func (container *Container) Stats() (*execdriver.ResourceStats, error) {
 func (container *Container) Stats() (*execdriver.ResourceStats, error) {
 	return container.daemon.Stats(container)
 	return container.daemon.Stats(container)
 }
 }
+
+func (c *Container) LogDriverType() string {
+	c.Lock()
+	defer c.Unlock()
+	if c.hostConfig.LogConfig.Type == "" {
+		return c.daemon.defaultLogConfig.Type
+	}
+	return c.hostConfig.LogConfig.Type
+}

+ 36 - 34
daemon/daemon.go

@@ -89,23 +89,24 @@ func (c *contStore) List() []*Container {
 }
 }
 
 
 type Daemon struct {
 type Daemon struct {
-	ID             string
-	repository     string
-	sysInitPath    string
-	containers     *contStore
-	execCommands   *execStore
-	graph          *graph.Graph
-	repositories   *graph.TagStore
-	idIndex        *truncindex.TruncIndex
-	sysInfo        *sysinfo.SysInfo
-	volumes        *volumes.Repository
-	eng            *engine.Engine
-	config         *Config
-	containerGraph *graphdb.Database
-	driver         graphdriver.Driver
-	execDriver     execdriver.Driver
-	trustStore     *trust.TrustStore
-	statsCollector *statsCollector
+	ID               string
+	repository       string
+	sysInitPath      string
+	containers       *contStore
+	execCommands     *execStore
+	graph            *graph.Graph
+	repositories     *graph.TagStore
+	idIndex          *truncindex.TruncIndex
+	sysInfo          *sysinfo.SysInfo
+	volumes          *volumes.Repository
+	eng              *engine.Engine
+	config           *Config
+	containerGraph   *graphdb.Database
+	driver           graphdriver.Driver
+	execDriver       execdriver.Driver
+	trustStore       *trust.TrustStore
+	statsCollector   *statsCollector
+	defaultLogConfig runconfig.LogConfig
 }
 }
 
 
 // Install installs daemon capabilities to eng.
 // Install installs daemon capabilities to eng.
@@ -991,23 +992,24 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
 	}
 	}
 
 
 	daemon := &Daemon{
 	daemon := &Daemon{
-		ID:             trustKey.PublicKey().KeyID(),
-		repository:     daemonRepo,
-		containers:     &contStore{s: make(map[string]*Container)},
-		execCommands:   newExecStore(),
-		graph:          g,
-		repositories:   repositories,
-		idIndex:        truncindex.NewTruncIndex([]string{}),
-		sysInfo:        sysInfo,
-		volumes:        volumes,
-		config:         config,
-		containerGraph: graph,
-		driver:         driver,
-		sysInitPath:    sysInitPath,
-		execDriver:     ed,
-		eng:            eng,
-		trustStore:     t,
-		statsCollector: newStatsCollector(1 * time.Second),
+		ID:               trustKey.PublicKey().KeyID(),
+		repository:       daemonRepo,
+		containers:       &contStore{s: make(map[string]*Container)},
+		execCommands:     newExecStore(),
+		graph:            g,
+		repositories:     repositories,
+		idIndex:          truncindex.NewTruncIndex([]string{}),
+		sysInfo:          sysInfo,
+		volumes:          volumes,
+		config:           config,
+		containerGraph:   graph,
+		driver:           driver,
+		sysInitPath:      sysInitPath,
+		execDriver:       ed,
+		eng:              eng,
+		trustStore:       t,
+		statsCollector:   newStatsCollector(1 * time.Second),
+		defaultLogConfig: config.LogConfig,
 	}
 	}
 
 
 	// Setup shutdown handlers
 	// Setup shutdown handlers

+ 8 - 0
daemon/inspect.go

@@ -62,6 +62,14 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status {
 			container.hostConfig.Links = append(container.hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias))
 			container.hostConfig.Links = append(container.hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias))
 		}
 		}
 	}
 	}
+	// we need this trick to preserve empty log driver, so
+	// container will use daemon defaults even if daemon change them
+	if container.hostConfig.LogConfig.Type == "" {
+		container.hostConfig.LogConfig = daemon.defaultLogConfig
+		defer func() {
+			container.hostConfig.LogConfig = runconfig.LogConfig{}
+		}()
+	}
 
 
 	out.SetJson("HostConfig", container.hostConfig)
 	out.SetJson("HostConfig", container.hostConfig)
 
 

+ 48 - 0
daemon/logger/copier.go

@@ -0,0 +1,48 @@
+package logger
+
+import (
+	"bufio"
+	"io"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+)
+
+// Copier can copy logs from specified sources to Logger and attach
+// ContainerID and Timestamp.
+// Writes are concurrent, so you need implement some sync in your logger
+type Copier struct {
+	// cid is container id for which we copying logs
+	cid string
+	// srcs is map of name -> reader pairs, for example "stdout", "stderr"
+	srcs map[string]io.Reader
+	dst  Logger
+}
+
+// NewCopier creates new Copier
+func NewCopier(cid string, srcs map[string]io.Reader, dst Logger) (*Copier, error) {
+	return &Copier{
+		cid:  cid,
+		srcs: srcs,
+		dst:  dst,
+	}, nil
+}
+
+// Run starts logs copying
+func (c *Copier) Run() {
+	for src, w := range c.srcs {
+		go c.copySrc(src, w)
+	}
+}
+
+func (c *Copier) copySrc(name string, src io.Reader) {
+	scanner := bufio.NewScanner(src)
+	for scanner.Scan() {
+		if err := c.dst.Log(&Message{ContainerID: c.cid, Line: scanner.Bytes(), Source: name, Timestamp: time.Now().UTC()}); err != nil {
+			logrus.Errorf("Failed to log msg %q for logger %s: %s", scanner.Bytes(), c.dst.Name(), err)
+		}
+	}
+	if err := scanner.Err(); err != nil {
+		logrus.Errorf("Error scanning log stream: %s", err)
+	}
+}

+ 100 - 0
daemon/logger/copier_test.go

@@ -0,0 +1,100 @@
+package logger
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+	"testing"
+	"time"
+)
+
+type TestLoggerJSON struct {
+	*json.Encoder
+}
+
+func (l *TestLoggerJSON) Log(m *Message) error {
+	return l.Encode(m)
+}
+
+func (l *TestLoggerJSON) Close() error {
+	return nil
+}
+
+func (l *TestLoggerJSON) Name() string {
+	return "json"
+}
+
+type TestLoggerText struct {
+	*bytes.Buffer
+}
+
+func (l *TestLoggerText) Log(m *Message) error {
+	_, err := l.WriteString(m.ContainerID + " " + m.Source + " " + string(m.Line) + "\n")
+	return err
+}
+
+func (l *TestLoggerText) Close() error {
+	return nil
+}
+
+func (l *TestLoggerText) Name() string {
+	return "text"
+}
+
+func TestCopier(t *testing.T) {
+	stdoutLine := "Line that thinks that it is log line from docker stdout"
+	stderrLine := "Line that thinks that it is log line from docker stderr"
+	var stdout bytes.Buffer
+	var stderr bytes.Buffer
+	for i := 0; i < 30; i++ {
+		if _, err := stdout.WriteString(stdoutLine + "\n"); err != nil {
+			t.Fatal(err)
+		}
+		if _, err := stderr.WriteString(stderrLine + "\n"); err != nil {
+			t.Fatal(err)
+		}
+	}
+
+	var jsonBuf bytes.Buffer
+
+	jsonLog := &TestLoggerJSON{Encoder: json.NewEncoder(&jsonBuf)}
+
+	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
+	c, err := NewCopier(cid,
+		map[string]io.Reader{
+			"stdout": &stdout,
+			"stderr": &stderr,
+		},
+		jsonLog)
+	if err != nil {
+		t.Fatal(err)
+	}
+	c.Run()
+	time.Sleep(100 * time.Millisecond)
+	dec := json.NewDecoder(&jsonBuf)
+	for {
+		var msg Message
+		if err := dec.Decode(&msg); err != nil {
+			if err == io.EOF {
+				break
+			}
+			t.Fatal(err)
+		}
+		if msg.Source != "stdout" && msg.Source != "stderr" {
+			t.Fatalf("Wrong Source: %q, should be %q or %q", msg.Source, "stdout", "stderr")
+		}
+		if msg.ContainerID != cid {
+			t.Fatalf("Wrong ContainerID: %q, expected %q", msg.ContainerID, cid)
+		}
+		if msg.Source == "stdout" {
+			if string(msg.Line) != stdoutLine {
+				t.Fatalf("Wrong Line: %q, expected %q", msg.Line, stdoutLine)
+			}
+		}
+		if msg.Source == "stderr" {
+			if string(msg.Line) != stderrLine {
+				t.Fatalf("Wrong Line: %q, expected %q", msg.Line, stderrLine)
+			}
+		}
+	}
+}

+ 49 - 0
daemon/logger/jsonfilelog/jsonfilelog.go

@@ -0,0 +1,49 @@
+package jsonfilelog
+
+import (
+	"bytes"
+	"os"
+
+	"github.com/docker/docker/daemon/logger"
+	"github.com/docker/docker/pkg/jsonlog"
+)
+
+// JSONFileLogger is Logger implementation for default docker logging:
+// JSON objects to file
+type JSONFileLogger struct {
+	buf *bytes.Buffer
+	f   *os.File // store for closing
+}
+
+// New creates new JSONFileLogger which writes to filename
+func New(filename string) (logger.Logger, error) {
+	log, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
+	if err != nil {
+		return nil, err
+	}
+	return &JSONFileLogger{
+		f:   log,
+		buf: bytes.NewBuffer(nil),
+	}, nil
+}
+
+// Log converts logger.Message to jsonlog.JSONLog and serializes it to file
+func (l *JSONFileLogger) Log(msg *logger.Message) error {
+	err := (&jsonlog.JSONLog{Log: string(msg.Line) + "\n", Stream: msg.Source, Created: msg.Timestamp}).MarshalJSONBuf(l.buf)
+	if err != nil {
+		return err
+	}
+	l.buf.WriteByte('\n')
+	_, err = l.buf.WriteTo(l.f)
+	return err
+}
+
+// Close closes underlying file
+func (l *JSONFileLogger) Close() error {
+	return l.f.Close()
+}
+
+// Name returns name of this logger
+func (l *JSONFileLogger) Name() string {
+	return "JSONFile"
+}

+ 78 - 0
daemon/logger/jsonfilelog/jsonfilelog_test.go

@@ -0,0 +1,78 @@
+package jsonfilelog
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+	"time"
+
+	"github.com/docker/docker/daemon/logger"
+	"github.com/docker/docker/pkg/jsonlog"
+)
+
+func TestJSONFileLogger(t *testing.T) {
+	tmp, err := ioutil.TempDir("", "docker-logger-")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+	filename := filepath.Join(tmp, "container.log")
+	l, err := New(filename)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer l.Close()
+	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
+	if err := l.Log(&logger.Message{ContainerID: cid, Line: []byte("line1"), Source: "src1"}); err != nil {
+		t.Fatal(err)
+	}
+	if err := l.Log(&logger.Message{ContainerID: cid, Line: []byte("line2"), Source: "src2"}); err != nil {
+		t.Fatal(err)
+	}
+	if err := l.Log(&logger.Message{ContainerID: cid, Line: []byte("line3"), Source: "src3"}); err != nil {
+		t.Fatal(err)
+	}
+	res, err := ioutil.ReadFile(filename)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expected := `{"log":"line1\n","stream":"src1","time":"0001-01-01T00:00:00Z"}
+{"log":"line2\n","stream":"src2","time":"0001-01-01T00:00:00Z"}
+{"log":"line3\n","stream":"src3","time":"0001-01-01T00:00:00Z"}
+`
+
+	if string(res) != expected {
+		t.Fatalf("Wrong log content: %q, expected %q", res, expected)
+	}
+}
+
+func BenchmarkJSONFileLogger(b *testing.B) {
+	tmp, err := ioutil.TempDir("", "docker-logger-")
+	if err != nil {
+		b.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+	filename := filepath.Join(tmp, "container.log")
+	l, err := New(filename)
+	if err != nil {
+		b.Fatal(err)
+	}
+	defer l.Close()
+	cid := "a7317399f3f857173c6179d44823594f8294678dea9999662e5c625b5a1c7657"
+	testLine := "Line that thinks that it is log line from docker\n"
+	msg := &logger.Message{ContainerID: cid, Line: []byte(testLine), Source: "stderr", Timestamp: time.Now().UTC()}
+	jsonlog, err := (&jsonlog.JSONLog{Log: string(msg.Line) + "\n", Stream: msg.Source, Created: msg.Timestamp}).MarshalJSON()
+	if err != nil {
+		b.Fatal(err)
+	}
+	b.SetBytes(int64(len(jsonlog)+1) * 30)
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		for j := 0; j < 30; j++ {
+			if err := l.Log(msg); err != nil {
+				b.Fatal(err)
+			}
+		}
+	}
+}

+ 18 - 0
daemon/logger/logger.go

@@ -0,0 +1,18 @@
+package logger
+
+import "time"
+
+// Message is datastructure that represents record from some container
+type Message struct {
+	ContainerID string
+	Line        []byte
+	Source      string
+	Timestamp   time.Time
+}
+
+// Logger is interface for docker logging drivers
+type Logger interface {
+	Log(*Message) error
+	Name() string
+	Close() error
+}

+ 3 - 0
daemon/logs.go

@@ -44,6 +44,9 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) engine.Status {
 	if err != nil {
 	if err != nil {
 		return job.Error(err)
 		return job.Error(err)
 	}
 	}
+	if container.LogDriverType() != "json-file" {
+		return job.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
+	}
 	cLog, err := container.ReadLog("json")
 	cLog, err := container.ReadLog("json")
 	if err != nil && os.IsNotExist(err) {
 	if err != nil && os.IsNotExist(err) {
 		// Legacy logs
 		// Legacy logs

+ 6 - 1
daemon/monitor.go

@@ -123,7 +123,7 @@ func (m *containerMonitor) Start() error {
 	for {
 	for {
 		m.container.RestartCount++
 		m.container.RestartCount++
 
 
-		if err := m.container.startLoggingToDisk(); err != nil {
+		if err := m.container.startLogging(); err != nil {
 			m.resetContainer(false)
 			m.resetContainer(false)
 
 
 			return err
 			return err
@@ -302,6 +302,11 @@ func (m *containerMonitor) resetContainer(lock bool) {
 		container.stdin, container.stdinPipe = io.Pipe()
 		container.stdin, container.stdinPipe = io.Pipe()
 	}
 	}
 
 
+	if container.logDriver != nil {
+		container.logDriver.Close()
+		container.logDriver = nil
+	}
+
 	c := container.command.ProcessConfig.Cmd
 	c := container.command.ProcessConfig.Cmd
 
 
 	container.command.ProcessConfig.Cmd = exec.Cmd{
 	container.command.ProcessConfig.Cmd = exec.Cmd{

+ 5 - 0
docs/man/docker-create.1.md

@@ -28,6 +28,7 @@ docker-create - Create a new container
 [**--label-file**[=*[]*]]
 [**--label-file**[=*[]*]]
 [**--link**[=*[]*]]
 [**--link**[=*[]*]]
 [**--lxc-conf**[=*[]*]]
 [**--lxc-conf**[=*[]*]]
+[**--log-driver**[=*[]*]]
 [**-m**|**--memory**[=*MEMORY*]]
 [**-m**|**--memory**[=*MEMORY*]]
 [**--memory-swap**[=*MEMORY-SWAP*]]
 [**--memory-swap**[=*MEMORY-SWAP*]]
 [**--mac-address**[=*MAC-ADDRESS*]]
 [**--mac-address**[=*MAC-ADDRESS*]]
@@ -116,6 +117,10 @@ IMAGE [COMMAND] [ARG...]
 **--lxc-conf**=[]
 **--lxc-conf**=[]
    (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
    (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
 
 
+**--log-driver**="|*json-file*|*none*"
+  Logging driver for container. Default is defined by daemon `--log-driver` flag.
+  **Warning**: `docker logs` command works only for `json-file` logging driver.
+
 **-m**, **--memory**=""
 **-m**, **--memory**=""
    Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
    Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
 
 

+ 2 - 0
docs/man/docker-logs.1.md

@@ -22,6 +22,8 @@ The **docker logs --follow** command combines commands **docker logs** and
 **docker attach**. It will first return all logs from the beginning and
 **docker attach**. It will first return all logs from the beginning and
 then continue streaming new output from the container’s stdout and stderr.
 then continue streaming new output from the container’s stdout and stderr.
 
 
+**Warning**: This command works only for **json-file** logging driver.
+
 # OPTIONS
 # OPTIONS
 **--help**
 **--help**
   Print usage statement
   Print usage statement

+ 5 - 0
docs/man/docker-run.1.md

@@ -29,6 +29,7 @@ docker-run - Run a command in a new container
 [**--label-file**[=*[]*]]
 [**--label-file**[=*[]*]]
 [**--link**[=*[]*]]
 [**--link**[=*[]*]]
 [**--lxc-conf**[=*[]*]]
 [**--lxc-conf**[=*[]*]]
+[**--log-driver**[=*[]*]]
 [**-m**|**--memory**[=*MEMORY*]]
 [**-m**|**--memory**[=*MEMORY*]]
 [**--memory-swap**[=*MEMORY-SWAP]]
 [**--memory-swap**[=*MEMORY-SWAP]]
 [**--mac-address**[=*MAC-ADDRESS*]]
 [**--mac-address**[=*MAC-ADDRESS*]]
@@ -217,6 +218,10 @@ which interface and port to use.
 **--lxc-conf**=[]
 **--lxc-conf**=[]
    (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
    (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
 
 
+**--log-driver**="|*json-file*|*none*"
+  Logging driver for container. Default is defined by daemon `--log-driver` flag.
+  **Warning**: `docker logs` command works only for `json-file` logging driver.
+
 **-m**, **--memory**=""
 **-m**, **--memory**=""
    Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
    Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
 
 

+ 4 - 0
docs/man/docker.1.md

@@ -89,6 +89,10 @@ unix://[/path/to/socket] to use.
 **--label**="[]"
 **--label**="[]"
   Set key=value labels to the daemon (displayed in `docker info`)
   Set key=value labels to the daemon (displayed in `docker info`)
 
 
+**--log-driver**="*json-file*|*none*"
+  Container's logging driver. Default is `default`.
+  **Warning**: `docker logs` command works only for `json-file` logging driver.
+
 **--mtu**=VALUE
 **--mtu**=VALUE
   Set the containers network mtu. Default is `0`.
   Set the containers network mtu. Default is `0`.
 
 

+ 10 - 1
docs/sources/reference/api/docker_remote_api_v1.18.md

@@ -161,7 +161,8 @@ Create a container
                "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
                "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
                "NetworkMode": "bridge",
                "NetworkMode": "bridge",
                "Devices": [],
                "Devices": [],
-               "Ulimits": [{}]
+               "Ulimits": [{}],
+               "LogConfig": { "Type": "json-file", Config: {} }
             }
             }
         }
         }
 
 
@@ -255,6 +256,10 @@ Json Parameters:
   -   **Ulimits** - A list of ulimits to be set in the container, specified as
   -   **Ulimits** - A list of ulimits to be set in the container, specified as
         `{ "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }`, for example:
         `{ "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }`, for example:
         `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
         `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
+  -   **LogConfig** - Logging configuration to container, format
+        `{ "Type": "<driver_name>", "Config": {"key1": "val1"}}
+        Available types: `json-file`, `none`.
+        `json-file` logging driver.
 
 
 Query Parameters:
 Query Parameters:
 
 
@@ -352,6 +357,7 @@ Return low-level information on the container `id`
 				"MaximumRetryCount": 2,
 				"MaximumRetryCount": 2,
 				"Name": "on-failure"
 				"Name": "on-failure"
 			},
 			},
+           "LogConfig": { "Type": "json-file", Config: {} },
 			"SecurityOpt": null,
 			"SecurityOpt": null,
 			"VolumesFrom": null,
 			"VolumesFrom": null,
 			"Ulimits": [{}]
 			"Ulimits": [{}]
@@ -448,6 +454,9 @@ Status Codes:
 
 
 Get stdout and stderr logs from the container ``id``
 Get stdout and stderr logs from the container ``id``
 
 
+> **Note**:
+> This endpoint works only for containers with `json-file` logging driver.
+
 **Example request**:
 **Example request**:
 
 
        GET /containers/4fa6e0f0c678/logs?stderr=1&stdout=1&timestamps=1&follow=1&tail=10 HTTP/1.1
        GET /containers/4fa6e0f0c678/logs?stderr=1&stdout=1&timestamps=1&follow=1&tail=10 HTTP/1.1

+ 6 - 0
docs/sources/reference/commandline/cli.md

@@ -97,6 +97,7 @@ expect an integer, and they can only be specified once.
       --ipv6=false                           Enable IPv6 networking
       --ipv6=false                           Enable IPv6 networking
       -l, --log-level="info"                 Set the logging level
       -l, --log-level="info"                 Set the logging level
       --label=[]                             Set key=value labels to the daemon
       --label=[]                             Set key=value labels to the daemon
+      --log-driver="json-file"               Container's logging driver (json-file/none)
       --mtu=0                                Set the containers network MTU
       --mtu=0                                Set the containers network MTU
       -p, --pidfile="/var/run/docker.pid"    Path to use for daemon PID file
       -p, --pidfile="/var/run/docker.pid"    Path to use for daemon PID file
       --registry-mirror=[]                   Preferred Docker registry mirror
       --registry-mirror=[]                   Preferred Docker registry mirror
@@ -817,6 +818,7 @@ Creates a new container.
       -l, --label=[]             Set metadata on the container (e.g., --label=com.example.key=value)
       -l, --label=[]             Set metadata on the container (e.g., --label=com.example.key=value)
       --label-file=[]            Read in a line delimited file of labels
       --label-file=[]            Read in a line delimited file of labels
       --link=[]                  Add link to another container
       --link=[]                  Add link to another container
+      --log-driver=""            Logging driver for container
       --lxc-conf=[]              Add custom lxc options
       --lxc-conf=[]              Add custom lxc options
       -m, --memory=""            Memory limit
       -m, --memory=""            Memory limit
       --mac-address=""           Container MAC address (e.g. 92:d0:c6:0a:29:33)
       --mac-address=""           Container MAC address (e.g. 92:d0:c6:0a:29:33)
@@ -1447,6 +1449,9 @@ For example:
       -t, --timestamps=false    Show timestamps
       -t, --timestamps=false    Show timestamps
       --tail="all"              Number of lines to show from the end of the logs
       --tail="all"              Number of lines to show from the end of the logs
 
 
+NOTE: this command is available only for containers with `json-file` logging
+driver.
+
 The `docker logs` command batch-retrieves logs present at the time of execution.
 The `docker logs` command batch-retrieves logs present at the time of execution.
 
 
 The `docker logs --follow` command will continue streaming the new output from
 The `docker logs --follow` command will continue streaming the new output from
@@ -1722,6 +1727,7 @@ To remove an image using its digest:
       -i, --interactive=false    Keep STDIN open even if not attached
       -i, --interactive=false    Keep STDIN open even if not attached
       --ipc=""                   IPC namespace to use
       --ipc=""                   IPC namespace to use
       --link=[]                  Add link to another container
       --link=[]                  Add link to another container
+      --log-driver=""            Logging driver for container
       --lxc-conf=[]              Add custom lxc options
       --lxc-conf=[]              Add custom lxc options
       -m, --memory=""            Memory limit
       -m, --memory=""            Memory limit
       -l, --label=[]             Set metadata on the container (e.g., --label=com.example.key=value)
       -l, --label=[]             Set metadata on the container (e.g., --label=com.example.key=value)

+ 14 - 0
docs/sources/reference/run.md

@@ -642,6 +642,20 @@ familiar with using LXC directly.
 > you can use `--lxc-conf` to set a container's IP address, but this will not be
 > you can use `--lxc-conf` to set a container's IP address, but this will not be
 > reflected in the `/etc/hosts` file.
 > reflected in the `/etc/hosts` file.
 
 
+## Logging drivers (--log-driver)
+
+You can specify a different logging driver for the container than for the daemon.
+
+### Logging driver: none
+
+Disables any logging for the container. `docker logs` won't be available with
+this driver.
+
+### Log driver: json-file
+
+Default logging driver for Docker. Writes JSON messages to file. `docker logs`
+command is available only for this logging driver
+
 ## Overriding Dockerfile image defaults
 ## Overriding Dockerfile image defaults
 
 
 When a developer builds an image from a [*Dockerfile*](/reference/builder)
 When a developer builds an image from a [*Dockerfile*](/reference/builder)

+ 166 - 0
integration-cli/docker_cli_daemon_test.go

@@ -11,6 +11,7 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
+	"time"
 
 
 	"github.com/docker/libtrust"
 	"github.com/docker/libtrust"
 )
 )
@@ -560,3 +561,168 @@ func TestDaemonRestartRenameContainer(t *testing.T) {
 
 
 	logDone("daemon - rename persists through daemon restart")
 	logDone("daemon - rename persists through daemon restart")
 }
 }
+
+func TestDaemonLoggingDriverDefault(t *testing.T) {
+	d := NewDaemon(t)
+
+	if err := d.StartWithBusybox(); err != nil {
+		t.Fatal(err)
+	}
+	defer d.Stop()
+
+	out, err := d.Cmd("run", "-d", "busybox", "echo", "testline")
+	if err != nil {
+		t.Fatal(out, err)
+	}
+	id := strings.TrimSpace(out)
+
+	if out, err := d.Cmd("wait", id); err != nil {
+		t.Fatal(out, err)
+	}
+	logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
+
+	if _, err := os.Stat(logPath); err != nil {
+		t.Fatal(err)
+	}
+	f, err := os.Open(logPath)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var res struct {
+		Log    string    `json:log`
+		Stream string    `json:stream`
+		Time   time.Time `json:time`
+	}
+	if err := json.NewDecoder(f).Decode(&res); err != nil {
+		t.Fatal(err)
+	}
+	if res.Log != "testline\n" {
+		t.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n")
+	}
+	if res.Stream != "stdout" {
+		t.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout")
+	}
+	if !time.Now().After(res.Time) {
+		t.Fatalf("Log time %v in future", res.Time)
+	}
+	logDone("daemon - default 'json-file' logging driver")
+}
+
+func TestDaemonLoggingDriverDefaultOverride(t *testing.T) {
+	d := NewDaemon(t)
+
+	if err := d.StartWithBusybox(); err != nil {
+		t.Fatal(err)
+	}
+	defer d.Stop()
+
+	out, err := d.Cmd("run", "-d", "--log-driver=none", "busybox", "echo", "testline")
+	if err != nil {
+		t.Fatal(out, err)
+	}
+	id := strings.TrimSpace(out)
+
+	if out, err := d.Cmd("wait", id); err != nil {
+		t.Fatal(out, err)
+	}
+	logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
+
+	if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) {
+		t.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err)
+	}
+	logDone("daemon - default logging driver override in run")
+}
+
+func TestDaemonLoggingDriverNone(t *testing.T) {
+	d := NewDaemon(t)
+
+	if err := d.StartWithBusybox("--log-driver=none"); err != nil {
+		t.Fatal(err)
+	}
+	defer d.Stop()
+
+	out, err := d.Cmd("run", "-d", "busybox", "echo", "testline")
+	if err != nil {
+		t.Fatal(out, err)
+	}
+	id := strings.TrimSpace(out)
+	if out, err := d.Cmd("wait", id); err != nil {
+		t.Fatal(out, err)
+	}
+
+	logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
+
+	if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) {
+		t.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err)
+	}
+	logDone("daemon - 'none' logging driver")
+}
+
+func TestDaemonLoggingDriverNoneOverride(t *testing.T) {
+	d := NewDaemon(t)
+
+	if err := d.StartWithBusybox("--log-driver=none"); err != nil {
+		t.Fatal(err)
+	}
+	defer d.Stop()
+
+	out, err := d.Cmd("run", "-d", "--log-driver=json-file", "busybox", "echo", "testline")
+	if err != nil {
+		t.Fatal(out, err)
+	}
+	id := strings.TrimSpace(out)
+
+	if out, err := d.Cmd("wait", id); err != nil {
+		t.Fatal(out, err)
+	}
+	logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log")
+
+	if _, err := os.Stat(logPath); err != nil {
+		t.Fatal(err)
+	}
+	f, err := os.Open(logPath)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var res struct {
+		Log    string    `json:log`
+		Stream string    `json:stream`
+		Time   time.Time `json:time`
+	}
+	if err := json.NewDecoder(f).Decode(&res); err != nil {
+		t.Fatal(err)
+	}
+	if res.Log != "testline\n" {
+		t.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n")
+	}
+	if res.Stream != "stdout" {
+		t.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout")
+	}
+	if !time.Now().After(res.Time) {
+		t.Fatalf("Log time %v in future", res.Time)
+	}
+	logDone("daemon - 'none' logging driver override in run")
+}
+
+func TestDaemonLoggingDriverNoneLogsError(t *testing.T) {
+	d := NewDaemon(t)
+
+	if err := d.StartWithBusybox("--log-driver=none"); err != nil {
+		t.Fatal(err)
+	}
+	defer d.Stop()
+
+	out, err := d.Cmd("run", "-d", "busybox", "echo", "testline")
+	if err != nil {
+		t.Fatal(out, err)
+	}
+	id := strings.TrimSpace(out)
+	out, err = d.Cmd("logs", id)
+	if err == nil {
+		t.Fatalf("Logs should fail with \"none\" driver")
+	}
+	if !strings.Contains(out, `\"logs\" command is supported only for \"json-file\" logging driver`) {
+		t.Fatalf("There should be error about non-json-file driver, got %s", out)
+	}
+	logDone("daemon - logs not available for non-json-file drivers")
+}

+ 1 - 0
integration/utils_test.go

@@ -191,6 +191,7 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine {
 		// otherwise NewDaemon will fail because of conflicting settings.
 		// otherwise NewDaemon will fail because of conflicting settings.
 		InterContainerCommunication: true,
 		InterContainerCommunication: true,
 		TrustKeyPath:                filepath.Join(root, "key.json"),
 		TrustKeyPath:                filepath.Join(root, "key.json"),
+		LogConfig:                   runconfig.LogConfig{Type: "json-file"},
 	}
 	}
 	d, err := daemon.NewDaemon(cfg, eng)
 	d, err := daemon.NewDaemon(cfg, eng)
 	if err != nil {
 	if err != nil {

+ 7 - 2
runconfig/hostconfig.go

@@ -99,6 +99,11 @@ type RestartPolicy struct {
 	MaximumRetryCount int
 	MaximumRetryCount int
 }
 }
 
 
+type LogConfig struct {
+	Type   string
+	Config map[string]string
+}
+
 type HostConfig struct {
 type HostConfig struct {
 	Binds           []string
 	Binds           []string
 	ContainerIDFile string
 	ContainerIDFile string
@@ -125,6 +130,7 @@ type HostConfig struct {
 	SecurityOpt     []string
 	SecurityOpt     []string
 	ReadonlyRootfs  bool
 	ReadonlyRootfs  bool
 	Ulimits         []*ulimit.Ulimit
 	Ulimits         []*ulimit.Ulimit
+	LogConfig       LogConfig
 }
 }
 
 
 // This is used by the create command when you want to set both the
 // This is used by the create command when you want to set both the
@@ -189,9 +195,8 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
 	job.GetenvJson("PortBindings", &hostConfig.PortBindings)
 	job.GetenvJson("PortBindings", &hostConfig.PortBindings)
 	job.GetenvJson("Devices", &hostConfig.Devices)
 	job.GetenvJson("Devices", &hostConfig.Devices)
 	job.GetenvJson("RestartPolicy", &hostConfig.RestartPolicy)
 	job.GetenvJson("RestartPolicy", &hostConfig.RestartPolicy)
-
 	job.GetenvJson("Ulimits", &hostConfig.Ulimits)
 	job.GetenvJson("Ulimits", &hostConfig.Ulimits)
-
+	job.GetenvJson("LogConfig", &hostConfig.LogConfig)
 	hostConfig.SecurityOpt = job.GetenvList("SecurityOpt")
 	hostConfig.SecurityOpt = job.GetenvList("SecurityOpt")
 	if Binds := job.GetenvList("Binds"); Binds != nil {
 	if Binds := job.GetenvList("Binds"); Binds != nil {
 		hostConfig.Binds = Binds
 		hostConfig.Binds = Binds

+ 2 - 0
runconfig/parse.go

@@ -70,6 +70,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flIpcMode         = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
 		flIpcMode         = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
 		flRestartPolicy   = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
 		flRestartPolicy   = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
 		flReadonlyRootfs  = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
 		flReadonlyRootfs  = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
+		flLoggingDriver   = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
 	)
 	)
 
 
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
 	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
 		SecurityOpt:     flSecurityOpt.GetAll(),
 		SecurityOpt:     flSecurityOpt.GetAll(),
 		ReadonlyRootfs:  *flReadonlyRootfs,
 		ReadonlyRootfs:  *flReadonlyRootfs,
 		Ulimits:         flUlimits.GetList(),
 		Ulimits:         flUlimits.GetList(),
+		LogConfig:       LogConfig{Type: *flLoggingDriver},
 	}
 	}
 
 
 	// When allocating stdin in attached mode, close stdin at client disconnect
 	// When allocating stdin in attached mode, close stdin at client disconnect