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>
This commit is contained in:
Philip K. Warren 2023-06-13 17:54:14 -04:00
parent d2452c2102
commit a08abec9f8
5 changed files with 71 additions and 5 deletions

View file

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

View file

@ -3,6 +3,7 @@ package main
import ( import (
"testing" "testing"
"github.com/containerd/containerd/log"
"github.com/docker/docker/daemon/config" "github.com/docker/docker/daemon/config"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -155,6 +156,26 @@ func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) {
assert.Check(t, is.Equal("warn", loadedConfig.LogLevel)) 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) { func TestLoadDaemonConfigWithEmbeddedOptions(t *testing.T) {
content := `{"tlscacert": "/etc/certs/ca.pem", "log-driver": "syslog"}` content := `{"tlscacert": "/etc/certs/ca.pem", "log-driver": "syslog"}`
tempFile := fs.NewFile(t, "config", fs.WithContent(content)) tempFile := fs.NewFile(t, "config", fs.WithContent(content))

View file

@ -1,9 +1,11 @@
package main package main
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"github.com/containerd/containerd/log"
"github.com/docker/docker/daemon/config" "github.com/docker/docker/daemon/config"
"github.com/docker/docker/opts" "github.com/docker/docker/opts"
"github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/homedir"
@ -72,6 +74,7 @@ type daemonOptions struct {
Debug bool Debug bool
Hosts []string Hosts []string
LogLevel string LogLevel string
LogFormat string
TLS bool TLS bool
TLSVerify bool TLSVerify bool
TLSOptions *tlsconfig.Options 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.BoolVarP(&o.Debug, "debug", "D", false, "Enable debug mode")
flags.BoolVar(&o.Validate, "validate", false, "Validate daemon configuration and exit") 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.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.TLS, FlagTLS, DefaultTLSValue, "Use TLS; implied by --tlsverify")
flags.BoolVar(&o.TLSVerify, FlagTLSVerify, dockerTLSVerify || DefaultTLSValue, "Use TLS and verify the remote") flags.BoolVar(&o.TLSVerify, FlagTLSVerify, dockerTLSVerify || DefaultTLSValue, "Use TLS and verify the remote")

View file

@ -187,6 +187,7 @@ type CommonConfig struct {
Debug bool `json:"debug,omitempty"` Debug bool `json:"debug,omitempty"`
Hosts []string `json:"hosts,omitempty"` Hosts []string `json:"hosts,omitempty"`
LogLevel string `json:"log-level,omitempty"` LogLevel string `json:"log-level,omitempty"`
LogFormat string `json:"log-format,omitempty"`
TLS *bool `json:"tls,omitempty"` TLS *bool `json:"tls,omitempty"`
TLSVerify *bool `json:"tlsverify,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 // validate DNS
for _, dns := range config.DNS { for _, dns := range config.DNS {
if _, err := opts.ValidateIPAddress(dns); err != nil { if _, err := opts.ValidateIPAddress(dns); err != nil {

View file

@ -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. // WithCRIDisabled disables the CRI plugin.
func WithCRIDisabled() DaemonOpt { func WithCRIDisabled() DaemonOpt {
return func(r *remote) error { return func(r *remote) error {