Documentation
¶
Overview ¶
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.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
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.
func NewHandler ¶
func NewHandler(logger *logrus.Logger, opts *HandlerOptions) *Handler
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.
Example ¶
ExampleNewHandler demonstrates using slog alongside an existing Logrus logger.
package main
import (
"log/slog"
"os"
"github.com/sirupsen/logrus"
lslog "github.com/sirupsen/logrus/hooks/slog"
)
func main() {
logger := logrus.New()
logger.SetOutput(os.Stdout)
logger.SetFormatter(&logrus.TextFormatter{
DisableColors: true,
DisableTimestamp: true,
})
slog.SetDefault(slog.New(lslog.NewHandler(logger, nil)))
// Both slog and Logrus write through the same Logrus backend.
slog.Info("hello from slog", "source", "slog")
logger.WithField("source", "logrus").Info("hello from logrus")
}
Output: level=info msg="hello from slog" source=slog level=info msg="hello from logrus" source=logrus
Example (Options) ¶
ExampleNewHandler_options demonstrates configuring source reporting and custom slog-to-Logrus level mapping.
Logrus writes to stderr by default, so the example has no stdout output. The log output will look similar to:
time="2026-01-02T03:04:05Z" level=info msg="regular info" func=github.com/sirupsen/logrus/hooks/slog_test.ExampleNewHandler_options file="/src/hooks/slog/slog_example_test.go:62" animal=walrus time="2026-01-02T03:04:06Z" level=warning msg="custom level" func=github.com/sirupsen/logrus/hooks/slog_test.ExampleNewHandler_options file="/src/hooks/slog/slog_example_test.go:63" animal=walrus
package main
import (
"context"
"log/slog"
"github.com/sirupsen/logrus"
lslog "github.com/sirupsen/logrus/hooks/slog"
)
func main() {
logger := logrus.New()
logger.SetFormatter(&logrus.TextFormatter{
DisableColors: true,
})
slogger := slog.New(lslog.NewHandler(logger, &lslog.HandlerOptions{
// Preserve slog's source location as Logrus caller information.
AddSource: true,
// Map this custom slog level to Logrus WarnLevel.
LevelMapper: func(level slog.Level) logrus.Level {
if level == slog.LevelInfo+1 {
return logrus.WarnLevel
}
return logrus.InfoLevel
},
}))
slogger.Info("regular info", "animal", "walrus")
slogger.Log(context.Background(), slog.LevelInfo+1, "custom level", "animal", "walrus")
}
Output:
func (*Handler) Enabled ¶
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 (*Handler) Handle ¶
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 (*Handler) WithAttrs ¶
WithAttrs returns a new Handler whose attributes consist of h's attributes followed by attrs.
type HandlerOptions ¶
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
}
HandlerOptions are options for a Handler. A zero HandlerOptions consists entirely of default values.
type Hook ¶
type Hook struct {
// contains filtered or unexported fields
}
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.
func NewHook ¶
func NewHook(logger *slog.Logger, opts *HookOptions) *Hook
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.
Example ¶
ExampleNewHook demonstrates forwarding existing Logrus logging to an slog logger, allowing applications to migrate their logging backend independently from code that still uses Logrus.
package main
import (
"bytes"
"fmt"
"io"
"log/slog"
"github.com/sirupsen/logrus"
lslog "github.com/sirupsen/logrus/hooks/slog"
)
func main() {
var slogOutput bytes.Buffer
slogger := slog.New(slog.NewTextHandler(&slogOutput, &slog.HandlerOptions{
ReplaceAttr: func(_ []string, attr slog.Attr) slog.Attr {
if attr.Key == slog.TimeKey {
return slog.Attr{}
}
return attr
},
}))
logger := logrus.New()
// Discard Logrus's own output; the hook forwards the entry to slog.
logger.SetOutput(io.Discard)
logger.AddHook(lslog.NewHook(slogger, nil))
// Log through Logrus; the hook forwards this to slog.
logger.WithField("animal", "walrus").Info("hello from logrus")
// Show what was emitted by the slog handler.
fmt.Print(slogOutput.String())
}
Output: level=INFO msg="hello from logrus" animal=walrus
Example (Migration) ¶
ExampleNewHook_migration demonstrates using both adapters while migrating from Logrus to slog. Records can cross the bridge without being forwarded back into a Logrus logger they have already passed through.
package main
import (
"bytes"
"fmt"
"log/slog"
"github.com/sirupsen/logrus"
lslog "github.com/sirupsen/logrus/hooks/slog"
)
func main() {
var logrusOutput bytes.Buffer
var slogOutput bytes.Buffer
legacy := logrus.New()
legacy.SetOutput(&logrusOutput)
legacy.SetFormatter(&logrus.TextFormatter{
DisableColors: true,
DisableTimestamp: true,
})
slogger := slog.New(slog.NewTextHandler(&slogOutput, &slog.HandlerOptions{
ReplaceAttr: func(_ []string, attr slog.Attr) slog.Attr {
if attr.Key == slog.TimeKey {
return slog.Attr{} // Suppress timestamps for the example.
}
return attr
},
}))
// Existing Logrus code is forwarded to the slog backend.
legacy.AddHook(lslog.NewHook(slogger, nil))
// New slog code can continue using the existing Logrus backend.
bridged := slog.New(lslog.NewHandler(legacy, nil))
legacy.Info("hello from logrus")
bridged.Info("hello from slog")
fmt.Println("Logrus output:")
fmt.Print(logrusOutput.String())
fmt.Println("slog output:")
fmt.Print(slogOutput.String())
}
Output: Logrus output: level=info msg="hello from logrus" level=info msg="hello from slog" slog output: level=INFO msg="hello from logrus" level=INFO msg="hello from slog"
type HookOptions ¶
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
}
HookOptions are options for a Hook. A zero HookOptions consists entirely of default values.
type 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.
Example (Dynamic) ¶
ExampleLevel_dynamic demonstrates using Level to share dynamic level configuration between Logrus and slog.
package main
import (
"log/slog"
"os"
"github.com/sirupsen/logrus"
lslog "github.com/sirupsen/logrus/hooks/slog"
)
// LoggerLevel exposes a Logrus logger's configured level as a slog.Leveler.
//
// This lets slog handlers follow changes to the Logrus logger's level without
// keeping a separate slog.LevelVar in sync.
type LoggerLevel struct {
Logger *logrus.Logger
}
func (l LoggerLevel) Level() slog.Level {
return lslog.Level(l.Logger.GetLevel()).Level()
}
// ExampleLevel_dynamic demonstrates using Level to share dynamic level
// configuration between Logrus and slog.
func main() {
logger := logrus.New()
logger.SetLevel(logrus.InfoLevel)
slogger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: LoggerLevel{Logger: logger},
ReplaceAttr: func(_ []string, attr slog.Attr) slog.Attr {
if attr.Key == slog.TimeKey {
return slog.Attr{} // Suppress timestamps for the example.
}
return attr
},
}))
slogger.Info("before")
// The slog handler follows changes to the Logrus logger's level without
// requiring a separate slog.LevelVar to be updated.
logger.SetLevel(logrus.WarnLevel)
slogger.Info("ignored")
slogger.Warn("after")
}
Output: level=INFO msg=before level=WARN msg=after
type Leveler ¶
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 SlogLevel ¶
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.