diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 8fb31c2288..ba74289a79 100644 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -748,6 +748,7 @@ _docker_daemon() { --ip-masq=false --iptables=false --ipv6 + --raw-logs --selinux-enabled --userland-proxy=false " diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 25be25e02d..b77435a156 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -666,6 +666,7 @@ __docker_subcommand() { "($help)*--log-opt=[Log driver specific options]:log driver options:__docker_log_options" \ "($help)--mtu=[Set the containers network MTU]:mtu:(0 576 1420 1500 9000)" \ "($help -p --pidfile)"{-p=,--pidfile=}"[Path to use for daemon PID file]:PID file:_files" \ + "($help)--raw-logs[Full timestamps without ANSI coloring]" \ "($help)*--registry-mirror=[Preferred Docker registry mirror]:registry mirror: " \ "($help -s --storage-driver)"{-s=,--storage-driver=}"[Storage driver to use]:driver:(aufs devicemapper btrfs zfs overlay)" \ "($help)--selinux-enabled[Enable selinux support]" \ diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index b40340847e..72fa69b73b 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -39,7 +39,7 @@ script if [ -f /etc/default/$UPSTART_JOB ]; then . /etc/default/$UPSTART_JOB fi - exec "$DOCKER" daemon $DOCKER_OPTS + exec "$DOCKER" daemon $DOCKER_OPTS --raw-logs end script # Don't emit "started" event until docker.sock is ready. diff --git a/daemon/config.go b/daemon/config.go index 932d535e57..e0b07b056d 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -57,6 +57,7 @@ type CommonConfig struct { Labels []string `json:"labels,omitempty"` Mtu int `json:"mtu,omitempty"` Pidfile string `json:"pidfile,omitempty"` + RawLogs bool `json:"raw-logs,omitempty"` Root string `json:"graph,omitempty"` TrustKeyPath string `json:"-"` @@ -104,6 +105,7 @@ func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) cmd.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, usageFn("--restart on the daemon has been deprecated in favor of --restart policies on docker run")) cmd.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", usageFn("Storage driver to use")) cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU")) + cmd.BoolVar(&config.RawLogs, []string{"-raw-logs"}, false, usageFn("Full timestamps without ANSI coloring")) // FIXME: why the inconsistency between "hosts" and "sockets"? cmd.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use")) cmd.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use")) diff --git a/docker/daemon.go b/docker/daemon.go index 0f85419215..793fd56cc6 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -168,7 +168,10 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { logrus.Warn("Running experimental build") } - logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: jsonlog.RFC3339NanoFixed}) + logrus.SetFormatter(&logrus.TextFormatter{ + TimestampFormat: jsonlog.RFC3339NanoFixed, + DisableColors: cli.Config.RawLogs, + }) if err := setDefaultUmask(); err != nil { logrus.Fatalf("Failed to set umask: %v", err) diff --git a/docs/reference/commandline/daemon.md b/docs/reference/commandline/daemon.md index df823995cd..1986295414 100644 --- a/docs/reference/commandline/daemon.md +++ b/docs/reference/commandline/daemon.md @@ -54,6 +54,7 @@ weight = -1 --mtu=0 Set the containers network MTU --disable-legacy-registry Do not contact legacy registries -p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file + --raw-logs Full timestamps without ANSI coloring --registry-mirror=[] Preferred Docker registry mirror -s, --storage-driver="" Storage driver to use --selinux-enabled Enable selinux support @@ -860,19 +861,20 @@ This is a full example of the allowed configuration options in the file: "group": "", "cgroup-parent": "", "default-ulimits": {}, - "ipv6": false, - "iptables": false, - "ip-forward": false, - "ip-mask": false, - "userland-proxy": false, - "ip": "0.0.0.0", - "bridge": "", - "bip": "", - "fixed-cidr": "", - "fixed-cidr-v6": "", - "default-gateway": "", - "default-gateway-v6": "", - "icc": false + "ipv6": false, + "iptables": false, + "ip-forward": false, + "ip-mask": false, + "userland-proxy": false, + "ip": "0.0.0.0", + "bridge": "", + "bip": "", + "fixed-cidr": "", + "fixed-cidr-v6": "", + "default-gateway": "", + "default-gateway-v6": "", + "icc": false, + "raw-logs": false } ``` diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 8faf0719be..a9cf86d90a 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -6,6 +6,7 @@ import ( "bytes" "encoding/json" "fmt" + "io" "io/ioutil" "net" "os" @@ -22,6 +23,7 @@ import ( "github.com/docker/libnetwork/iptables" "github.com/docker/libtrust" "github.com/go-check/check" + "github.com/kr/pty" ) func (s *DockerDaemonSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) { @@ -584,7 +586,7 @@ func (s *DockerDaemonSuite) TestDaemonExitOnFailure(c *check.C) { c.Fatalf("Expected daemon not to start, got %v", err) } // look in the log and make sure we got the message that daemon is shutting down - runCmd := exec.Command("grep", "Error starting daemon", s.d.LogfileName()) + runCmd := exec.Command("grep", "Error starting daemon", s.d.LogFileName()) if out, _, err := runCommandWithOutput(runCmd); err != nil { c.Fatalf("Expected 'Error starting daemon' message; but doesn't exist in log: %q, err: %v", out, err) } @@ -1759,7 +1761,7 @@ func (s *DockerDaemonSuite) TestDaemonRestartLocalVolumes(c *check.C) { func (s *DockerDaemonSuite) TestDaemonCorruptedLogDriverAddress(c *check.C) { c.Assert(s.d.Start("--log-driver=syslog", "--log-opt", "syslog-address=corrupted:42"), check.NotNil) expected := "Failed to set log opts: syslog-address should be in form proto://address" - runCmd := exec.Command("grep", expected, s.d.LogfileName()) + runCmd := exec.Command("grep", expected, s.d.LogFileName()) if out, _, err := runCommandWithOutput(runCmd); err != nil { c.Fatalf("Expected %q message; but doesn't exist in log: %q, err: %v", expected, out, err) } @@ -1768,7 +1770,7 @@ func (s *DockerDaemonSuite) TestDaemonCorruptedLogDriverAddress(c *check.C) { func (s *DockerDaemonSuite) TestDaemonCorruptedFluentdAddress(c *check.C) { c.Assert(s.d.Start("--log-driver=fluentd", "--log-opt", "fluentd-address=corrupted:c"), check.NotNil) expected := "Failed to set log opts: invalid fluentd-address corrupted:c: " - runCmd := exec.Command("grep", expected, s.d.LogfileName()) + runCmd := exec.Command("grep", expected, s.d.LogFileName()) if out, _, err := runCommandWithOutput(runCmd); err != nil { c.Fatalf("Expected %q message; but doesn't exist in log: %q, err: %v", expected, out, err) } @@ -1922,7 +1924,7 @@ func (s *DockerDaemonSuite) TestDaemonRestartContainerLinksRestart(c *check.C) { c.Assert(err, check.IsNil) // clear the log file -- we don't need any of it but may for the next part // can ignore the error here, this is just a cleanup - os.Truncate(d.LogfileName(), 0) + os.Truncate(d.LogFileName(), 0) err = d.Start() c.Assert(err, check.IsNil) @@ -1930,7 +1932,7 @@ func (s *DockerDaemonSuite) TestDaemonRestartContainerLinksRestart(c *check.C) { out, err := d.Cmd("inspect", "-f", "{{ .State.Running }}", "parent"+num) c.Assert(err, check.IsNil) if strings.TrimSpace(out) != "true" { - log, _ := ioutil.ReadFile(d.LogfileName()) + log, _ := ioutil.ReadFile(d.LogFileName()) c.Fatalf("parent container is not running\n%s", string(log)) } } @@ -2064,3 +2066,32 @@ func (s *DockerDaemonSuite) TestRunLinksChanged(c *check.C) { c.Assert(err, check.NotNil, check.Commentf(out)) c.Assert(out, check.Not(checker.Contains), "1 packets transmitted, 1 packets received") } + +func (s *DockerDaemonSuite) TestDaemonStartWithoutColors(c *check.C) { + testRequires(c, DaemonIsLinux) + newD := NewDaemon(c) + + infoLog := "\x1b[34mINFO\x1b" + + p, tty, err := pty.Open() + c.Assert(err, checker.IsNil) + defer func() { + tty.Close() + p.Close() + }() + + b := bytes.NewBuffer(nil) + go io.Copy(b, p) + + // Enable coloring explicitly + newD.StartWithLogFile(tty, "--raw-logs=false") + newD.Stop() + c.Assert(b.String(), checker.Contains, infoLog) + + b.Reset() + + // Disable coloring explicitly + newD.StartWithLogFile(tty, "--raw-logs=true") + newD.Stop() + c.Assert(b.String(), check.Not(checker.Contains), infoLog) +} diff --git a/integration-cli/docker_cli_external_graphdriver_unix_test.go b/integration-cli/docker_cli_external_graphdriver_unix_test.go index 3dcbe620ab..9fd36ec9c1 100644 --- a/integration-cli/docker_cli_external_graphdriver_unix_test.go +++ b/integration-cli/docker_cli_external_graphdriver_unix_test.go @@ -299,7 +299,7 @@ func (s *DockerExternalGraphdriverSuite) TearDownSuite(c *check.C) { func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriver(c *check.C) { if err := s.d.StartWithBusybox("-s", "test-external-graph-driver"); err != nil { - b, _ := ioutil.ReadFile(s.d.LogfileName()) + b, _ := ioutil.ReadFile(s.d.LogFileName()) c.Assert(err, check.IsNil, check.Commentf("\n%s", string(b))) } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 12bb252839..04bb5b4079 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -205,7 +205,15 @@ func (d *Daemon) getClientConfig() (*clientConfig, error) { // Start will start the daemon and return once it is ready to receive requests. // You can specify additional daemon flags. -func (d *Daemon) Start(arg ...string) error { +func (d *Daemon) Start(args ...string) error { + logFile, err := os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) + d.c.Assert(err, check.IsNil, check.Commentf("[%s] Could not create %s/docker.log", d.id, d.folder)) + + return d.StartWithLogFile(logFile, args...) +} + +// StartWithLogFile will start the daemon and attach its streams to a given file. +func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error { dockerBinary, err := exec.LookPath(dockerBinary) d.c.Assert(err, check.IsNil, check.Commentf("[%s] could not find docker binary in $PATH", d.id)) @@ -226,7 +234,7 @@ func (d *Daemon) Start(arg ...string) error { // turn on debug mode foundLog := false foundSd := false - for _, a := range arg { + for _, a := range providedArgs { if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") { foundLog = true } @@ -241,14 +249,12 @@ func (d *Daemon) Start(arg ...string) error { args = append(args, "--storage-driver", d.storageDriver) } - args = append(args, arg...) + args = append(args, providedArgs...) d.cmd = exec.Command(dockerBinary, args...) - d.logFile, err = os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) - d.c.Assert(err, check.IsNil, check.Commentf("[%s] Could not create %s/docker.log", d.id, d.folder)) - - d.cmd.Stdout = d.logFile - d.cmd.Stderr = d.logFile + d.cmd.Stdout = out + d.cmd.Stderr = out + d.logFile = out if err := d.cmd.Start(); err != nil { return fmt.Errorf("[%s] could not start daemon container: %v", d.id, err) @@ -472,8 +478,8 @@ func (d *Daemon) CmdWithArgs(daemonArgs []string, name string, arg ...string) (s return string(b), err } -// LogfileName returns the path the the daemon's log file -func (d *Daemon) LogfileName() string { +// LogFileName returns the path the the daemon's log file +func (d *Daemon) LogFileName() string { return d.logFile.Name() } diff --git a/man/docker-daemon.8.md b/man/docker-daemon.8.md index 02adaeda91..051c9e0748 100644 --- a/man/docker-daemon.8.md +++ b/man/docker-daemon.8.md @@ -44,6 +44,7 @@ docker-daemon - Enable daemon mode [**--log-opt**[=*map[]*]] [**--mtu**[=*0*]] [**-p**|**--pidfile**[=*/var/run/docker.pid*]] +[**--raw-logs**] [**--registry-mirror**[=*[]*]] [**-s**|**--storage-driver**[=*STORAGE-DRIVER*]] [**--selinux-enabled**] @@ -197,6 +198,11 @@ unix://[/path/to/socket] to use. **-p**, **--pidfile**="" Path to use for daemon PID file. Default is `/var/run/docker.pid` +**--raw-logs** +Output daemon logs in full timestamp format without ANSI coloring. If this flag is not set, +the daemon outputs condensed, colorized logs if a terminal is detected, or full ("raw") +output otherwise. + **--registry-mirror**=*://* Prepend a registry mirror to be used for image pulls. May be specified multiple times.