-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
142 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
//go:build go1.21 | ||
// +build go1.21 | ||
|
||
package slog | ||
|
||
import ( | ||
"log/slog" | ||
|
||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// SlogHook sends logs to slog. | ||
type SlogHook struct { | ||
logger *slog.Logger | ||
} | ||
|
||
var _ logrus.Hook = (*SlogHook)(nil) | ||
|
||
// NewSlogHook creates a hook that sends logs to an existing 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. | ||
// | ||
// Example usage: | ||
// | ||
// logger := slog.New(slog.NewJSONHandler(os.Stderr, nil)) | ||
// hook := NewSlogHook(logger) | ||
func NewSlogHook(logger *slog.Logger) *SlogHook { | ||
return &SlogHook{ | ||
logger: logger, | ||
} | ||
} | ||
|
||
func (*SlogHook) toSlogLevel(level logrus.Level) slog.Level { | ||
switch level { | ||
case logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel: | ||
return slog.LevelError | ||
case logrus.WarnLevel: | ||
return slog.LevelWarn | ||
case logrus.InfoLevel: | ||
return slog.LevelInfo | ||
case logrus.DebugLevel, logrus.TraceLevel: | ||
return slog.LevelDebug | ||
default: | ||
// Treat all unknown levels as errors | ||
return slog.LevelError | ||
} | ||
} | ||
|
||
// Levels always returns all levels, since slog allows controlling level | ||
// enabling based on context. | ||
func (h *SlogHook) Levels() []logrus.Level { | ||
return logrus.AllLevels | ||
} | ||
|
||
// Fire sends entry to the underlying slog logger. The Time and Caller fields | ||
// of entry are ignored. | ||
func (h *SlogHook) Fire(entry *logrus.Entry) error { | ||
attrs := make([]interface{}, 0, len(entry.Data)) | ||
for k, v := range entry.Data { | ||
attrs = append(attrs, slog.Any(k, v)) | ||
} | ||
h.logger.Log( | ||
entry.Context, | ||
h.toSlogLevel(entry.Level), | ||
entry.Message, | ||
attrs..., | ||
) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
//go:build go1.21 | ||
// +build go1.21 | ||
|
||
package slog | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
"log/slog" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
func TestSlogHook(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
fn func(*logrus.Logger) | ||
want []string | ||
}{ | ||
{ | ||
name: "defaults", | ||
fn: func(log *logrus.Logger) { | ||
log.Info("info") | ||
}, | ||
want: []string{ | ||
"level=INFO msg=info", | ||
}, | ||
}, | ||
{ | ||
name: "with fields", | ||
fn: func(log *logrus.Logger) { | ||
log.WithFields(logrus.Fields{ | ||
"chicken": "cluck", | ||
}).Error("error") | ||
}, | ||
want: []string{ | ||
"level=ERROR msg=error chicken=cluck", | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
buf := &bytes.Buffer{} | ||
slogLogger := slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{ | ||
// Remove timestamps from logs, for easier comparison | ||
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { | ||
if a.Key == slog.TimeKey { | ||
return slog.Attr{} | ||
} | ||
return a | ||
}, | ||
})) | ||
log := logrus.New() | ||
log.Out = io.Discard | ||
log.AddHook(NewSlogHook(slogLogger)) | ||
tt.fn(log) | ||
got := strings.Split(strings.TrimSpace(buf.String()), "\n") | ||
if len(got) != len(tt.want) { | ||
t.Errorf("Got %d log lines, expected %d", len(got), len(tt.want)) | ||
return | ||
} | ||
for i, line := range got { | ||
if line != tt.want[i] { | ||
t.Errorf("line %d differs from expectation.\n Got: %s\nWant: %s", i, line, tt.want[i]) | ||
} | ||
} | ||
}) | ||
} | ||
} |