mirror of
https://github.com/docker/cli.git
synced 2026-11-06 13:10:41 -04:00
vendor: github.com/containerd/log v0.2.0
full diff: https://github.com/containerd/log/compare/v0.1.0...v0.2.0 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@ require (
|
||||
dario.cat/mergo v1.0.2
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/containerd/errdefs/pkg v0.3.0
|
||||
github.com/containerd/log v0.1.0
|
||||
github.com/containerd/log v0.2.0
|
||||
github.com/containerd/platforms v1.0.0-rc.5
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/distribution/reference v0.6.0
|
||||
|
||||
+2
-2
@@ -18,8 +18,8 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/log v0.2.0 h1:BewD/umNgVnoczglOpX8eRMyEy5t5iPlu5AIpnWDONc=
|
||||
github.com/containerd/log v0.2.0/go.mod h1:/M7L7CXKcPTfNC74XzaK+5H5KbO5+4lJVpuVI6vRLoM=
|
||||
github.com/containerd/platforms v1.0.0-rc.5 h1:vXd569rDrz8LeMXzAnBsy6LADV5YtsD8oyaRarxdmSU=
|
||||
github.com/containerd/platforms v1.0.0-rc.5/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
+35
-15
@@ -1,30 +1,50 @@
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- exportloopref # Checks for pointers to enclosing loop variables
|
||||
- gofmt
|
||||
- goimports
|
||||
- copyloopvar
|
||||
- dupword
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17
|
||||
- unconvert
|
||||
- unused
|
||||
- vet
|
||||
- dupword # Checks for duplicate words in the source code
|
||||
disable:
|
||||
- errcheck
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- api
|
||||
- cluster
|
||||
- design
|
||||
- docs
|
||||
- docs/man
|
||||
- releases
|
||||
- reports
|
||||
- test
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- api
|
||||
- cluster
|
||||
- design
|
||||
- docs
|
||||
- docs/man
|
||||
- releases
|
||||
- reports
|
||||
- test
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
skip-dirs:
|
||||
- api
|
||||
- cluster
|
||||
- design
|
||||
- docs
|
||||
- docs/man
|
||||
- releases
|
||||
- reports
|
||||
- test # e2e scripts
|
||||
|
||||
+61
-9
@@ -40,8 +40,11 @@ package log
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
lslog "github.com/sirupsen/logrus/hooks/slog"
|
||||
)
|
||||
|
||||
// G is a shorthand for [GetLogger].
|
||||
@@ -108,10 +111,17 @@ const (
|
||||
PanicLevel Level = logrus.PanicLevel
|
||||
)
|
||||
|
||||
// SetLevel sets log level globally. It returns an error if the given
|
||||
// level is not supported.
|
||||
// levelValue is a log level accepted by [SetLevel].
|
||||
type levelValue interface {
|
||||
string | Level | slog.Level | lslog.Level | lslog.SlogLevel
|
||||
}
|
||||
|
||||
// SetLevel sets the global log level.
|
||||
//
|
||||
// level can be one of:
|
||||
// The level may be specified as a string, a [Level], a [slog.Level], an
|
||||
// [lslog.Level], or an [lslog.SlogLevel].
|
||||
//
|
||||
// String levels are parsed using [logrus.ParseLevel] and may be one of:
|
||||
//
|
||||
// - "trace" ([TraceLevel])
|
||||
// - "debug" ([DebugLevel])
|
||||
@@ -120,10 +130,34 @@ const (
|
||||
// - "error" ([ErrorLevel])
|
||||
// - "fatal" ([FatalLevel])
|
||||
// - "panic" ([PanicLevel])
|
||||
func SetLevel(level string) error {
|
||||
lvl, err := logrus.ParseLevel(level)
|
||||
if err != nil {
|
||||
return err
|
||||
//
|
||||
// slog levels are mapped to Logrus levels using the mapping defined by
|
||||
// [lslog.SlogLevel]. [lslog.Level] values are accepted directly as their
|
||||
// underlying Logrus level.
|
||||
//
|
||||
// SetLevel returns an error if a string level is not supported.
|
||||
func SetLevel[T levelValue](level T) error {
|
||||
var lvl Level
|
||||
|
||||
switch l := any(level).(type) {
|
||||
case string:
|
||||
var err error
|
||||
lvl, err = logrus.ParseLevel(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case Level:
|
||||
lvl = l
|
||||
|
||||
case slog.Level:
|
||||
lvl = lslog.SlogLevel(l).Level()
|
||||
|
||||
case lslog.Level:
|
||||
lvl = Level(l)
|
||||
|
||||
case lslog.SlogLevel:
|
||||
lvl = l.Level()
|
||||
}
|
||||
|
||||
L.Logger.SetLevel(lvl)
|
||||
@@ -149,21 +183,39 @@ const (
|
||||
|
||||
// SetFormat sets the log output format ([TextFormat] or [JSONFormat]).
|
||||
func SetFormat(format OutputFormat) error {
|
||||
if slogOut != nil {
|
||||
var handler slog.Handler
|
||||
switch format {
|
||||
case TextFormat:
|
||||
handler = slog.NewTextHandler(slogOut, &slog.HandlerOptions{Level: loggerLevel{}})
|
||||
case JSONFormat:
|
||||
handler = slog.NewJSONHandler(slogOut, &slog.HandlerOptions{Level: loggerLevel{}})
|
||||
default:
|
||||
return fmt.Errorf("unknown log format: %s", format)
|
||||
}
|
||||
slog.SetDefault(slog.New(handler))
|
||||
|
||||
// Keep logrus formatting and output disabled, as both are handled by slog.
|
||||
L.Logger.SetFormatter(discardFormatter{})
|
||||
L.Logger.SetOutput(io.Discard)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch format {
|
||||
case TextFormat:
|
||||
L.Logger.SetFormatter(&logrus.TextFormatter{
|
||||
TimestampFormat: RFC3339NanoFixed,
|
||||
FullTimestamp: true,
|
||||
})
|
||||
return nil
|
||||
case JSONFormat:
|
||||
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
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
lslog "github.com/sirupsen/logrus/hooks/slog"
|
||||
)
|
||||
|
||||
// slogOut is used to set the slog logger when setting output format.
|
||||
var slogOut io.Writer
|
||||
|
||||
// slogOnce guards UseSlog so repeated calls do not stack up hooks or
|
||||
// reset slogOut to the discard writer installed on the first call.
|
||||
var slogOnce sync.Once
|
||||
|
||||
func UseSlog() {
|
||||
slogOnce.Do(func() {
|
||||
L.Logger.SetNoLock()
|
||||
L.Logger.AddHook(slogHook{})
|
||||
slogOut = L.Logger.Out
|
||||
|
||||
// Disable logrus formatting and output, as both are handled by slog.
|
||||
L.Logger.SetFormatter(discardFormatter{})
|
||||
L.Logger.SetOutput(io.Discard)
|
||||
})
|
||||
}
|
||||
|
||||
type slogHook struct{}
|
||||
|
||||
func (slogHook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
|
||||
func (slogHook) Fire(entry *logrus.Entry) error {
|
||||
return lslog.NewHook(slog.Default(), nil).Fire(entry)
|
||||
}
|
||||
|
||||
// loggerLevel exposes the current Logrus logger level as a slog.Leveler.
|
||||
type loggerLevel struct{}
|
||||
|
||||
func (loggerLevel) Level() slog.Level {
|
||||
return lslog.Level(L.Logger.GetLevel()).Level()
|
||||
}
|
||||
|
||||
type discardFormatter struct{}
|
||||
|
||||
func (discardFormatter) Format(*logrus.Entry) ([]byte, error) { return nil, nil }
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// HandlerOptions are options for a [Handler].
|
||||
// A zero HandlerOptions consists entirely of default values.
|
||||
type HandlerOptions struct {
|
||||
// AddSource causes the handler to include the source code position
|
||||
// of the log statement in the Logrus entry.
|
||||
AddSource bool
|
||||
|
||||
// LevelMapper maps slog levels to Logrus levels. If nil, the default
|
||||
// mapping is used. Set it to customize level mapping, for example to map
|
||||
// custom slog levels to specific Logrus levels.
|
||||
LevelMapper func(slog.Level) logrus.Level
|
||||
}
|
||||
|
||||
// Handler is a [slog.Handler] that writes records to a [logrus.Logger].
|
||||
//
|
||||
// It is intended for bridging libraries or application code that log via slog
|
||||
// into an existing Logrus logger, for example during a gradual migration.
|
||||
//
|
||||
// By default, slog levels are mapped using [SlogLevel].
|
||||
// Set [HandlerOptions.LevelMapper] to customize the mapping.
|
||||
//
|
||||
// Mapping to [logrus.FatalLevel] or [logrus.PanicLevel] preserves the level
|
||||
// only; handling a record does not exit or panic.
|
||||
type Handler struct {
|
||||
logger *logrus.Logger
|
||||
opts HandlerOptions
|
||||
|
||||
// fields holds attributes from prior WithAttrs calls, already resolved
|
||||
// under the group prefix that applied when they were added.
|
||||
fields logrus.Fields
|
||||
|
||||
// groups is the current group name stack for attributes added later.
|
||||
groups []string
|
||||
}
|
||||
|
||||
var _ slog.Handler = (*Handler)(nil)
|
||||
|
||||
// NewHandler creates a [slog.Handler] that writes to the provided
|
||||
// [logrus.Logger].
|
||||
//
|
||||
// The provided logger must not be nil. NewHandler panics if logger is nil.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewHandler(logger *logrus.Logger, opts *HandlerOptions) *Handler {
|
||||
if logger == nil {
|
||||
panic("cannot create handler from nil logger")
|
||||
}
|
||||
if opts == nil {
|
||||
opts = &HandlerOptions{}
|
||||
}
|
||||
return &Handler{
|
||||
logger: logger,
|
||||
opts: *opts,
|
||||
}
|
||||
}
|
||||
|
||||
// toLogrusLevel maps a slog level using LevelMapper or the default mapping.
|
||||
func (h *Handler) toLogrusLevel(level slog.Level) logrus.Level {
|
||||
if h.opts.LevelMapper != nil {
|
||||
return h.opts.LevelMapper(level)
|
||||
}
|
||||
return SlogLevel(level).Level()
|
||||
}
|
||||
|
||||
// Enabled reports whether the handler handles records at the given level.
|
||||
// It maps the slog level to a logrus level and consults the underlying logger.
|
||||
func (h *Handler) Enabled(_ context.Context, level slog.Level) bool {
|
||||
return h.logger.IsLevelEnabled(h.toLogrusLevel(level))
|
||||
}
|
||||
|
||||
// Handle converts the slog record into a logrus entry and logs it.
|
||||
// Record time, context, message, and attributes (including those from
|
||||
// [Handler.WithAttrs]/[Handler.WithGroup]) are preserved. Attributes are
|
||||
// attached as logrus fields; group names are joined with "." as a key prefix
|
||||
// (similar to [slog.TextHandler]).
|
||||
func (h *Handler) Handle(ctx context.Context, record slog.Record) error {
|
||||
// Stop recursive forwarding before it can cycle back through this logger.
|
||||
if hasLogrusLogger(ctx, h.logger) {
|
||||
return nil
|
||||
}
|
||||
level := h.toLogrusLevel(record.Level)
|
||||
if !h.logger.IsLevelEnabled(level) {
|
||||
return nil
|
||||
}
|
||||
|
||||
entry := &logrus.Entry{
|
||||
Logger: h.logger,
|
||||
Data: h.fields,
|
||||
Time: record.Time,
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
if h.opts.AddSource && record.PC != 0 {
|
||||
// Preserve the caller selected by slog instead of rediscovering it
|
||||
// through Logrus's caller stack filtering.
|
||||
//
|
||||
// Record.PC is a return PC; resolve it with CallersFrames.
|
||||
frame, _ := runtime.CallersFrames([]uintptr{record.PC}).Next()
|
||||
entry.Caller = &frame
|
||||
}
|
||||
|
||||
if n := record.NumAttrs(); n > 0 {
|
||||
// Clone before mutating the handler's fields.
|
||||
entry.Data = maps.Clone(h.fields)
|
||||
if entry.Data == nil {
|
||||
entry.Data = make(logrus.Fields, n)
|
||||
}
|
||||
record.Attrs(func(a slog.Attr) bool {
|
||||
appendAttr(entry.Data, h.groups, a)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
entry.Log(level, record.Message)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithAttrs returns a new Handler whose attributes consist of h's attributes
|
||||
// followed by attrs.
|
||||
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
if len(attrs) == 0 {
|
||||
return h
|
||||
}
|
||||
h2 := h.clone()
|
||||
h2.fields = maps.Clone(h.fields)
|
||||
if h2.fields == nil {
|
||||
h2.fields = make(logrus.Fields, len(attrs))
|
||||
}
|
||||
for _, a := range attrs {
|
||||
appendAttr(h2.fields, h.groups, a)
|
||||
}
|
||||
return h2
|
||||
}
|
||||
|
||||
// WithGroup returns a new Handler with a group appended to the receiver's
|
||||
// existing groups. All attributes added to the returned handler (via WithAttrs
|
||||
// or Handle) are nested under the combined group names.
|
||||
// If name is empty, WithGroup returns h unchanged.
|
||||
func (h *Handler) WithGroup(name string) slog.Handler {
|
||||
if name == "" {
|
||||
return h
|
||||
}
|
||||
h2 := h.clone()
|
||||
h2.groups = append(slices.Clip(h.groups), name)
|
||||
return h2
|
||||
}
|
||||
|
||||
// clone returns a shallow copy of h. Callers must clone fields or groups
|
||||
// before modifying them.
|
||||
func (h *Handler) clone() *Handler {
|
||||
return &Handler{
|
||||
logger: h.logger,
|
||||
opts: h.opts,
|
||||
fields: h.fields,
|
||||
groups: h.groups,
|
||||
}
|
||||
}
|
||||
|
||||
// appendAttr adds attr to fields, resolving it and applying group prefixes.
|
||||
// Group attributes are flattened with "."-separated keys.
|
||||
func appendAttr(fields logrus.Fields, groups []string, attr slog.Attr) {
|
||||
attr.Value = attr.Value.Resolve()
|
||||
if attr.Equal(slog.Attr{}) {
|
||||
return
|
||||
}
|
||||
if attr.Value.Kind() == slog.KindGroup {
|
||||
gs := attr.Value.Group()
|
||||
if len(gs) == 0 {
|
||||
return
|
||||
}
|
||||
g := groups
|
||||
if attr.Key != "" {
|
||||
g = append(slices.Clip(groups), attr.Key)
|
||||
}
|
||||
for _, a := range gs {
|
||||
appendAttr(fields, g, a)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
key := attr.Key
|
||||
if len(groups) > 0 {
|
||||
key = strings.Join(groups, ".")
|
||||
if attr.Key != "" {
|
||||
key += "." + attr.Key
|
||||
}
|
||||
}
|
||||
fields[key] = attr.Value.Any()
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package slog
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// slog levels corresponding to Logrus levels.
|
||||
const (
|
||||
slogLevelTrace = slog.LevelDebug - 4
|
||||
slogLevelDebug = slog.LevelDebug
|
||||
slogLevelInfo = slog.LevelInfo
|
||||
slogLevelWarn = slog.LevelWarn
|
||||
slogLevelError = slog.LevelError
|
||||
slogLevelFatal = slog.LevelError + 2
|
||||
slogLevelPanic = slog.LevelError + 4
|
||||
)
|
||||
|
||||
// A Leveler provides a [logrus.Level] value.
|
||||
//
|
||||
// It is the Logrus counterpart of [slog.Leveler].
|
||||
//
|
||||
// As [SlogLevel] itself implements Leveler, clients typically supply
|
||||
// a SlogLevel value wherever a Leveler is needed.
|
||||
// Clients who need to vary the level dynamically can provide a more complex
|
||||
// Leveler implementation.
|
||||
type Leveler interface {
|
||||
Level() logrus.Level
|
||||
}
|
||||
|
||||
// Level adapts a [logrus.Level] to a [slog.Leveler] using the default
|
||||
// Logrus-to-slog level mapping:
|
||||
//
|
||||
// - [logrus.TraceLevel] -> [slog.LevelDebug] - 4
|
||||
// - [logrus.DebugLevel] -> [slog.LevelDebug]
|
||||
// - [logrus.InfoLevel] -> [slog.LevelInfo]
|
||||
// - [logrus.WarnLevel] -> [slog.LevelWarn]
|
||||
// - [logrus.ErrorLevel] -> [slog.LevelError]
|
||||
// - [logrus.FatalLevel] -> [slog.LevelError] + 2
|
||||
// - [logrus.PanicLevel] -> [slog.LevelError] + 4
|
||||
//
|
||||
// Unknown Logrus levels map to [slog.LevelError].
|
||||
type Level logrus.Level
|
||||
|
||||
// Level returns l mapped to the corresponding slog level.
|
||||
func (l Level) Level() slog.Level {
|
||||
return toSlogLevel(logrus.Level(l))
|
||||
}
|
||||
|
||||
var _ slog.Leveler = Level(0)
|
||||
|
||||
// SlogLevel adapts a [slog.Level] to a [Leveler] using the default
|
||||
// slog-to-Logrus level mapping:
|
||||
//
|
||||
// - [slog.LevelDebug] - 4 -> [logrus.TraceLevel]
|
||||
// - [slog.LevelDebug] -> [logrus.DebugLevel]
|
||||
// - [slog.LevelInfo] -> [logrus.InfoLevel]
|
||||
// - [slog.LevelWarn] -> [logrus.WarnLevel]
|
||||
// - [slog.LevelError] -> [logrus.ErrorLevel]
|
||||
// - [slog.LevelError] + 2 -> [logrus.FatalLevel]
|
||||
// - [slog.LevelError] + 4 -> [logrus.PanicLevel]
|
||||
//
|
||||
// Levels between these boundaries map to the next lower Logrus severity.
|
||||
// Levels below [slog.LevelDebug] map to [logrus.TraceLevel], and levels at or
|
||||
// above the Panic boundary map to [logrus.PanicLevel].
|
||||
type SlogLevel slog.Level
|
||||
|
||||
// Level returns l mapped to the corresponding Logrus level.
|
||||
func (l SlogLevel) Level() logrus.Level {
|
||||
return toLogrusLevel(slog.Level(l))
|
||||
}
|
||||
|
||||
var _ Leveler = SlogLevel(0)
|
||||
|
||||
// toLogrusLevel maps a slog level using the default mapping.
|
||||
func toLogrusLevel(level slog.Level) logrus.Level {
|
||||
switch {
|
||||
case level >= slogLevelPanic:
|
||||
return logrus.PanicLevel
|
||||
case level >= slogLevelFatal:
|
||||
return logrus.FatalLevel
|
||||
case level >= slogLevelError:
|
||||
return logrus.ErrorLevel
|
||||
case level >= slogLevelWarn:
|
||||
return logrus.WarnLevel
|
||||
case level >= slogLevelInfo:
|
||||
return logrus.InfoLevel
|
||||
case level >= slogLevelDebug:
|
||||
return logrus.DebugLevel
|
||||
case level >= slogLevelTrace:
|
||||
return logrus.TraceLevel
|
||||
default:
|
||||
return logrus.TraceLevel
|
||||
}
|
||||
}
|
||||
|
||||
// toSlogLevel maps a Logrus level using the default mapping.
|
||||
func toSlogLevel(level logrus.Level) slog.Level {
|
||||
switch level {
|
||||
case logrus.PanicLevel:
|
||||
return slogLevelPanic
|
||||
case logrus.FatalLevel:
|
||||
return slogLevelFatal
|
||||
case logrus.ErrorLevel:
|
||||
return slogLevelError
|
||||
case logrus.WarnLevel:
|
||||
return slogLevelWarn
|
||||
case logrus.InfoLevel:
|
||||
return slogLevelInfo
|
||||
case logrus.DebugLevel:
|
||||
return slogLevelDebug
|
||||
case logrus.TraceLevel:
|
||||
return slogLevelTrace
|
||||
default:
|
||||
// Treat all unknown levels as errors
|
||||
return slogLevelError
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
// Package slog provides adapters between Logrus and log/slog.
|
||||
//
|
||||
// [Handler] forwards slog records to a Logrus logger, while [Hook] forwards
|
||||
// Logrus entries to a slog logger. They can be used independently to support
|
||||
// incremental migration between the two logging APIs.
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"maps"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// logrusLoggerContextKey is the context key used to track Logrus loggers a
|
||||
// record has already traversed while being forwarded through slog adapters.
|
||||
type logrusLoggerContextKey struct{}
|
||||
|
||||
// withLogrusLogger returns a context that records logger as having already
|
||||
// handled the current log record.
|
||||
func withLogrusLogger(ctx context.Context, logger *logrus.Logger) context.Context {
|
||||
seen, _ := ctx.Value(logrusLoggerContextKey{}).(map[*logrus.Logger]struct{})
|
||||
seen = maps.Clone(seen)
|
||||
if seen == nil {
|
||||
seen = make(map[*logrus.Logger]struct{}, 1)
|
||||
}
|
||||
seen[logger] = struct{}{}
|
||||
return context.WithValue(ctx, logrusLoggerContextKey{}, seen)
|
||||
}
|
||||
|
||||
// hasLogrusLogger reports whether logger has already handled the current log
|
||||
// record while it was forwarded through slog adapters.
|
||||
func hasLogrusLogger(ctx context.Context, logger *logrus.Logger) bool {
|
||||
seen, _ := ctx.Value(logrusLoggerContextKey{}).(map[*logrus.Logger]struct{})
|
||||
_, ok := seen[logger]
|
||||
return ok
|
||||
}
|
||||
|
||||
// HookOptions are options for a [Hook].
|
||||
// A zero HookOptions consists entirely of default values.
|
||||
type HookOptions struct {
|
||||
// LevelMapper maps Logrus levels to slog levels. If nil, the default
|
||||
// mapping is used. Set it to customize level mapping, for example to map
|
||||
// custom Logrus levels to specific slog levels.
|
||||
LevelMapper func(logrus.Level) slog.Level
|
||||
}
|
||||
|
||||
// Hook sends Logrus entries to slog.
|
||||
//
|
||||
// It is intended for bridging libraries or application code that log via
|
||||
// Logrus into an existing slog logger, for example during a gradual migration.
|
||||
//
|
||||
// By default, Logrus levels are mapped using [Level]. Set
|
||||
// [HookOptions.LevelMapper] to customize the mapping.
|
||||
type Hook struct {
|
||||
logger *slog.Logger
|
||||
opts HookOptions
|
||||
}
|
||||
|
||||
var _ logrus.Hook = (*Hook)(nil)
|
||||
|
||||
// NewHook creates a [logrus.Hook] that sends logs to the provided [slog.Logger].
|
||||
//
|
||||
// This hook is intended to be used during transition from Logrus to slog,
|
||||
// or as a shim between different parts of your application or different
|
||||
// libraries that depend on different loggers.
|
||||
//
|
||||
// The provided logger must not be nil. NewHook panics if logger is nil.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewHook(logger *slog.Logger, opts *HookOptions) *Hook {
|
||||
if logger == nil {
|
||||
panic("cannot create hook from nil logger")
|
||||
}
|
||||
if opts == nil {
|
||||
opts = &HookOptions{}
|
||||
}
|
||||
return &Hook{
|
||||
logger: logger,
|
||||
opts: *opts,
|
||||
}
|
||||
}
|
||||
|
||||
// toSlogLevel maps a Logrus level using LevelMapper or the default mapping.
|
||||
func (h *Hook) toSlogLevel(level logrus.Level) slog.Level {
|
||||
if h.opts.LevelMapper != nil {
|
||||
return h.opts.LevelMapper(level)
|
||||
}
|
||||
return Level(level).Level()
|
||||
}
|
||||
|
||||
// Levels always returns all levels, since slog allows controlling level
|
||||
// enabling based on context.
|
||||
func (h *Hook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
|
||||
// Fire forwards the provided logrus Entry to the underlying slog.Logger's
|
||||
// Handler, mapping it to a slog.Record. Time and caller information are
|
||||
// preserved when available, and Entry.Data is converted to attributes.
|
||||
// If Entry.Context is nil, context.Background() is used.
|
||||
func (h *Hook) Fire(entry *logrus.Entry) error {
|
||||
ctx := entry.Context
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
// Track this logger to guard against recursive forwarding.
|
||||
ctx = withLogrusLogger(ctx, entry.Logger)
|
||||
|
||||
level := h.toSlogLevel(entry.Level)
|
||||
handler := h.logger.Handler()
|
||||
if !handler.Enabled(ctx, level) {
|
||||
return nil
|
||||
}
|
||||
attrs := make([]slog.Attr, 0, len(entry.Data))
|
||||
for k, v := range entry.Data {
|
||||
attrs = append(attrs, slog.Any(k, v))
|
||||
}
|
||||
var pc uintptr
|
||||
if entry.Caller != nil {
|
||||
pc = entry.Caller.PC
|
||||
}
|
||||
r := slog.NewRecord(entry.Time, level, entry.Message, pc)
|
||||
r.AddAttrs(attrs...)
|
||||
return handler.Handle(ctx, r)
|
||||
}
|
||||
Vendored
+3
-2
@@ -31,8 +31,8 @@ github.com/containerd/errdefs
|
||||
## explicit; go 1.22
|
||||
github.com/containerd/errdefs/pkg/errhttp
|
||||
github.com/containerd/errdefs/pkg/internal/cause
|
||||
# github.com/containerd/log v0.1.0
|
||||
## explicit; go 1.20
|
||||
# github.com/containerd/log v0.2.0
|
||||
## explicit; go 1.23
|
||||
github.com/containerd/log
|
||||
# github.com/containerd/platforms v1.0.0-rc.5
|
||||
## explicit; go 1.24
|
||||
@@ -283,6 +283,7 @@ github.com/santhosh-tekuri/jsonschema/v6/kind
|
||||
# github.com/sirupsen/logrus v1.10.2
|
||||
## explicit; go 1.23
|
||||
github.com/sirupsen/logrus
|
||||
github.com/sirupsen/logrus/hooks/slog
|
||||
# github.com/spf13/cobra v1.10.2
|
||||
## explicit; go 1.15
|
||||
github.com/spf13/cobra
|
||||
|
||||
Reference in New Issue
Block a user