| ... | ... |
@@ -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×tamps=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 |