diff --git a/cmd/containerd/command/main.go b/cmd/containerd/command/main.go index 922b54f8f..3b8e9739e 100644 --- a/cmd/containerd/command/main.go +++ b/cmd/containerd/command/main.go @@ -357,7 +357,7 @@ func setLogLevel(context *cli.Context, config *srvconfig.Config) error { } func setLogFormat(config *srvconfig.Config) error { - f := config.Debug.Format + f := log.OutputFormat(config.Debug.Format) if f == "" { f = log.TextFormat } diff --git a/log/context.go b/log/context.go index b63c602f4..14ff2a59c 100644 --- a/log/context.go +++ b/log/context.go @@ -14,6 +14,27 @@ limitations under the License. */ +// Package log provides types and functions related to logging, passing +// loggers through a context, and attaching context to the logger. +// +// # Transitional types +// +// This package contains various types that are aliases for types in [logrus]. +// These aliases are intended for transitioning away from hard-coding logrus +// as logging implementation. Consumers of this package are encouraged to use +// the type-aliases from this package instead of directly using their logrus +// equivalent. +// +// The intent is to replace these aliases with locally defined types and +// interfaces once all consumers are no longer directly importing logrus +// types. +// +// IMPORTANT: due to the transitional purpose of this package, it is not +// guaranteed for the full logrus API to be provided in the future. As +// outlined, these aliases are provided as a step to transition away from +// a specific implementation which, as a result, exposes the full logrus API. +// While no decisions have been made on the ultimate design and interface +// provided by this package, we do not expect carrying "less common" features. package log import ( @@ -23,98 +44,138 @@ import ( "github.com/sirupsen/logrus" ) -var ( - // G is an alias for GetLogger. - // - // We may want to define this locally to a package to get package tagged log - // messages. - G = GetLogger +// L is an alias for the standard logger. +var L = &Entry{ + Logger: logrus.StandardLogger(), + // Default is three fields plus a little extra room. + Data: make(Fields, 6), +} - // L is an alias for the standard logger. - L = logrus.NewEntry(logrus.StandardLogger()) -) +type loggerKey struct{} -type ( - loggerKey struct{} +// Fields type to pass to "WithFields". +type Fields = map[string]any - // Fields type to pass to `WithFields`, alias from `logrus`. - Fields = logrus.Fields +// Entry is a logging entry. It contains all the fields passed with +// [Entry.WithFields]. It's finally logged when Trace, Debug, Info, Warn, +// Error, Fatal or Panic is called on it. These objects can be reused and +// passed around as much as you wish to avoid field duplication. +// +// Entry is a transitional type, and currently an alias for [logrus.Entry]. +type Entry = logrus.Entry - // Level is a logging level - Level = logrus.Level -) +// RFC3339NanoFixed is [time.RFC3339Nano] with nanoseconds padded using +// zeros to ensure the formatted time is always the same number of +// characters. +const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" +// Level is a logging level. +type Level = logrus.Level + +// Supported log levels. const ( - // RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to - // ensure the formatted time is always the same number of characters. - RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" + // TraceLevel level. Designates finer-grained informational events + // than [DebugLevel]. + TraceLevel Level = logrus.TraceLevel - // TextFormat represents the text logging format - TextFormat = "text" + // DebugLevel level. Usually only enabled when debugging. Very verbose + // logging. + DebugLevel Level = logrus.DebugLevel - // JSONFormat represents the JSON logging format - JSONFormat = "json" + // InfoLevel level. General operational entries about what's going on + // inside the application. + InfoLevel Level = logrus.InfoLevel - // TraceLevel level. - TraceLevel = logrus.TraceLevel + // WarnLevel level. Non-critical entries that deserve eyes. + WarnLevel Level = logrus.WarnLevel - // DebugLevel level. - DebugLevel = logrus.DebugLevel + // ErrorLevel level. Logs errors that should definitely be noted. + // Commonly used for hooks to send errors to an error tracking service. + ErrorLevel Level = logrus.ErrorLevel - // InfoLevel level. - InfoLevel = logrus.InfoLevel + // FatalLevel level. Logs and then calls "logger.Exit(1)". It exits + // even if the logging level is set to Panic. + FatalLevel Level = logrus.FatalLevel + + // PanicLevel level. This is the highest level of severity. Logs and + // then calls panic with the message passed to Debug, Info, ... + PanicLevel Level = logrus.PanicLevel ) -// SetLevel sets log level globally. +// SetLevel sets log level globally. It returns an error if the given +// level is not supported. +// +// level can be one of: +// +// - "trace" ([TraceLevel]) +// - "debug" ([DebugLevel]) +// - "info" ([InfoLevel]) +// - "warn" ([WarnLevel]) +// - "error" ([ErrorLevel]) +// - "fatal" ([FatalLevel]) +// - "panic" ([PanicLevel]) func SetLevel(level string) error { lvl, err := logrus.ParseLevel(level) if err != nil { return err } - logrus.SetLevel(lvl) + L.Logger.SetLevel(lvl) return nil } // GetLevel returns the current log level. func GetLevel() Level { - return logrus.GetLevel() + return L.Logger.GetLevel() } -// SetFormat sets log output format -func SetFormat(format string) error { +// OutputFormat specifies a log output format. +type OutputFormat string + +// Supported log output formats. +const ( + // TextFormat represents the text logging format. + TextFormat OutputFormat = "text" + + // JSONFormat represents the JSON logging format. + JSONFormat OutputFormat = "json" +) + +// SetFormat sets the log output format ([TextFormat] or [JSONFormat]). +func SetFormat(format OutputFormat) error { switch format { case TextFormat: - logrus.SetFormatter(&logrus.TextFormatter{ + L.Logger.SetFormatter(&logrus.TextFormatter{ TimestampFormat: RFC3339NanoFixed, FullTimestamp: true, }) + return nil case JSONFormat: - logrus.SetFormatter(&logrus.JSONFormatter{ + L.Logger.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: RFC3339NanoFixed, }) + return nil default: return fmt.Errorf("unknown log format: %s", format) } - - return nil } // WithLogger returns a new context with the provided logger. Use in // combination with logger.WithField(s) for great effect. -func WithLogger(ctx context.Context, logger *logrus.Entry) context.Context { - e := logger.WithContext(ctx) - return context.WithValue(ctx, loggerKey{}, e) +func WithLogger(ctx context.Context, logger *Entry) context.Context { + return context.WithValue(ctx, loggerKey{}, logger.WithContext(ctx)) } // GetLogger retrieves the current logger from the context. If no logger is // available, the default logger is returned. -func GetLogger(ctx context.Context) *logrus.Entry { - logger := ctx.Value(loggerKey{}) - - if logger == nil { - return L.WithContext(ctx) - } - - return logger.(*logrus.Entry) +func GetLogger(ctx context.Context) *Entry { + return G(ctx) +} + +// G is a shorthand for [GetLogger]. +func G(ctx context.Context) *Entry { + if logger := ctx.Value(loggerKey{}); logger != nil { + return logger.(*Entry) + } + return L.WithContext(ctx) } diff --git a/log/context_test.go b/log/context_test.go index 718302c6f..1b6d4a6cf 100644 --- a/log/context_test.go +++ b/log/context_test.go @@ -18,15 +18,46 @@ package log import ( "context" + "reflect" "testing" - "github.com/stretchr/testify/assert" + "github.com/sirupsen/logrus" ) func TestLoggerContext(t *testing.T) { + const expected = "one" ctx := context.Background() - - ctx = WithLogger(ctx, G(ctx).WithField("test", "one")) - assert.Equal(t, GetLogger(ctx).Data["test"], "one") - assert.Same(t, G(ctx), GetLogger(ctx)) // these should be the same. + ctx = WithLogger(ctx, G(ctx).WithField("test", expected)) + if actual := GetLogger(ctx).Data["test"]; actual != expected { + t.Errorf("expected: %v, got: %v", expected, actual) + } + a := G(ctx) + b := GetLogger(ctx) + if !reflect.DeepEqual(a, b) || a != b { + t.Errorf("should be the same: %+v, %+v", a, b) + } +} + +func TestCompat(t *testing.T) { + expected := Fields{ + "hello1": "world1", + "hello2": "world2", + "hello3": "world3", + } + + l := G(context.TODO()) + l = l.WithFields(logrus.Fields{"hello1": "world1"}) + l = l.WithFields(Fields{"hello2": "world2"}) + l = l.WithFields(map[string]any{"hello3": "world3"}) + if !reflect.DeepEqual(Fields(l.Data), expected) { + t.Errorf("expected: (%[1]T) %+[1]v, got: (%[2]T) %+[2]v", expected, l.Data) + } + + l2 := L + l2 = l2.WithFields(logrus.Fields{"hello1": "world1"}) + l2 = l2.WithFields(Fields{"hello2": "world2"}) + l2 = l2.WithFields(map[string]any{"hello3": "world3"}) + if !reflect.DeepEqual(Fields(l2.Data), expected) { + t.Errorf("expected: (%[1]T) %+[1]v, got: (%[2]T) %+[2]v", expected, l2.Data) + } } diff --git a/remotes/docker/resolver.go b/remotes/docker/resolver.go index 96110a188..3d6c0182f 100644 --- a/remotes/docker/resolver.go +++ b/remotes/docker/resolver.go @@ -673,7 +673,7 @@ func requestFields(req *http.Request) log.Fields { } } - return log.Fields(fields) + return fields } func responseFields(resp *http.Response) log.Fields { @@ -691,7 +691,7 @@ func responseFields(resp *http.Response) log.Fields { } } - return log.Fields(fields) + return fields } // IsLocalhost checks if the registry host is local.