Quellcode durchsuchen

Update dockerd to support JSON logging format

Update docker to support a '--log-format' option, which accepts either
'text' (default) or 'json'. Propagate the log format to containerd as
well, to ensure that everything will be logged consistently.

Signed-off-by: Philip K. Warren <pkwarren@gmail.com>
Philip K. Warren vor 2 Jahren
Ursprung
Commit
a08abec9f8

+ 25 - 5
cmd/dockerd/daemon.go

@@ -478,6 +478,7 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
 	conf.Debug = opts.Debug
 	conf.Hosts = opts.Hosts
 	conf.LogLevel = opts.LogLevel
+	conf.LogFormat = opts.LogFormat
 
 	if flags.Changed(FlagTLS) {
 		conf.TLS = &opts.TLS
@@ -657,6 +658,10 @@ func (cli *DaemonCli) getContainerdDaemonOpts() ([]supervisor.DaemonOpt, error)
 		opts = append(opts, supervisor.WithLogLevel(cli.LogLevel))
 	}
 
+	if logFormat := cli.Config.LogFormat; logFormat != "" {
+		opts = append(opts, supervisor.WithLogFormat(logFormat))
+	}
+
 	if !cli.CriContainerd {
 		// CRI support in the managed daemon is currently opt-in.
 		//
@@ -867,11 +872,26 @@ func configureDaemonLogs(conf *config.Config) {
 	} else {
 		logrus.SetLevel(logrus.InfoLevel)
 	}
-	logrus.SetFormatter(&logrus.TextFormatter{
-		TimestampFormat: jsonmessage.RFC3339NanoFixed,
-		DisableColors:   conf.RawLogs,
-		FullTimestamp:   true,
-	})
+	logFormat := conf.LogFormat
+	if logFormat == "" {
+		logFormat = log.TextFormat
+	}
+	var formatter logrus.Formatter
+	switch logFormat {
+	case log.JSONFormat:
+		formatter = &logrus.JSONFormatter{
+			TimestampFormat: jsonmessage.RFC3339NanoFixed,
+		}
+	case log.TextFormat:
+		formatter = &logrus.TextFormatter{
+			TimestampFormat: jsonmessage.RFC3339NanoFixed,
+			DisableColors:   conf.RawLogs,
+			FullTimestamp:   true,
+		}
+	default:
+		panic("unsupported log format " + logFormat)
+	}
+	logrus.SetFormatter(formatter)
 }
 
 func configureProxyEnv(conf *config.Config) {

+ 21 - 0
cmd/dockerd/daemon_test.go

@@ -3,6 +3,7 @@ package main
 import (
 	"testing"
 
+	"github.com/containerd/containerd/log"
 	"github.com/docker/docker/daemon/config"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/pflag"
@@ -155,6 +156,26 @@ func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) {
 	assert.Check(t, is.Equal("warn", loadedConfig.LogLevel))
 }
 
+func TestLoadDaemonCliConfigWithLogFormat(t *testing.T) {
+	tempFile := fs.NewFile(t, "config", fs.WithContent(`{"log-format": "json"}`))
+	defer tempFile.Remove()
+
+	opts := defaultOptions(t, tempFile.Path())
+	loadedConfig, err := loadDaemonCliConfig(opts)
+	assert.NilError(t, err)
+	assert.Assert(t, loadedConfig != nil)
+	assert.Check(t, is.Equal(log.JSONFormat, loadedConfig.LogFormat))
+}
+
+func TestLoadDaemonCliConfigWithInvalidLogFormat(t *testing.T) {
+	tempFile := fs.NewFile(t, "config", fs.WithContent(`{"log-format": "foo"}`))
+	defer tempFile.Remove()
+
+	opts := defaultOptions(t, tempFile.Path())
+	_, err := loadDaemonCliConfig(opts)
+	assert.Check(t, is.ErrorContains(err, "invalid log format: foo"))
+}
+
 func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) {
 	content := `{"tlscacert": "/etc/certs/ca.pem", "log-driver": "syslog"}`
 	tempFile := fs.NewFile(t, "config", fs.WithContent(content))

+ 5 - 0
cmd/dockerd/options.go

@@ -1,9 +1,11 @@
 package main
 
 import (
+	"fmt"
 	"os"
 	"path/filepath"
 
+	"github.com/containerd/containerd/log"
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/homedir"
@@ -72,6 +74,7 @@ type daemonOptions struct {
 	Debug        bool
 	Hosts        []string
 	LogLevel     string
+	LogFormat    string
 	TLS          bool
 	TLSVerify    bool
 	TLSOptions   *tlsconfig.Options
@@ -104,6 +107,8 @@ func (o *daemonOptions) installFlags(flags *pflag.FlagSet) {
 	flags.BoolVarP(&o.Debug, "debug", "D", false, "Enable debug mode")
 	flags.BoolVar(&o.Validate, "validate", false, "Validate daemon configuration and exit")
 	flags.StringVarP(&o.LogLevel, "log-level", "l", "info", `Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")`)
+	flags.StringVar(&o.LogFormat, "log-format", log.TextFormat,
+		fmt.Sprintf(`Set the logging format ("%s"|"%s")`, log.TextFormat, log.JSONFormat))
 	flags.BoolVar(&o.TLS, FlagTLS, DefaultTLSValue, "Use TLS; implied by --tlsverify")
 	flags.BoolVar(&o.TLSVerify, FlagTLSVerify, dockerTLSVerify || DefaultTLSValue, "Use TLS and verify the remote")
 

+ 11 - 0
daemon/config/config.go

@@ -187,6 +187,7 @@ type CommonConfig struct {
 	Debug     bool     `json:"debug,omitempty"`
 	Hosts     []string `json:"hosts,omitempty"`
 	LogLevel  string   `json:"log-level,omitempty"`
+	LogFormat string   `json:"log-format,omitempty"`
 	TLS       *bool    `json:"tls,omitempty"`
 	TLSVerify *bool    `json:"tlsverify,omitempty"`
 
@@ -594,6 +595,16 @@ func Validate(config *Config) error {
 		}
 	}
 
+	// validate log-format
+	if logFormat := config.LogFormat; logFormat != "" {
+		switch logFormat {
+		case log.TextFormat, log.JSONFormat:
+			// These are valid
+		default:
+			return errors.Errorf("invalid log format: %s", logFormat)
+		}
+	}
+
 	// validate DNS
 	for _, dns := range config.DNS {
 		if _, err := opts.ValidateIPAddress(dns); err != nil {

+ 9 - 0
libcontainerd/supervisor/remote_daemon_options.go

@@ -13,6 +13,15 @@ func WithLogLevel(lvl string) DaemonOpt {
 	}
 }
 
+// WithLogFormat defines the containerd log format.
+// This only makes sense if WithStartDaemon() was set to true.
+func WithLogFormat(format string) DaemonOpt {
+	return func(r *remote) error {
+		r.Debug.Format = format
+		return nil
+	}
+}
+
 // WithCRIDisabled disables the CRI plugin.
 func WithCRIDisabled() DaemonOpt {
 	return func(r *remote) error {