slog

package
v1.10.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 7 Imported by: 0

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")

}

func (*Handler) Enabled

func (h *Handler) Enabled(_ context.Context, level slog.Level) bool

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

func (h *Handler) Handle(ctx context.Context, record slog.Record) error

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

func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs returns a new Handler whose attributes consist of h's attributes followed by attrs.

func (*Handler) WithGroup

func (h *Handler) WithGroup(name string) slog.Handler

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.

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"

func (*Hook) Fire

func (h *Hook) Fire(entry *logrus.Entry) error

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 (*Hook) Levels

func (h *Hook) Levels() []logrus.Level

Levels always returns all levels, since slog allows controlling level enabling based on context.

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

type Level logrus.Level

Level adapts a logrus.Level to a slog.Leveler using the default Logrus-to-slog level mapping:

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

func (Level) Level

func (l Level) Level() slog.Level

Level returns l mapped to the corresponding slog level.

type Leveler

type Leveler interface {
	Level() logrus.Level
}

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

type SlogLevel slog.Level

SlogLevel adapts a slog.Level to a Leveler using the default slog-to-Logrus level mapping:

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.

func (SlogLevel) Level

func (l SlogLevel) Level() logrus.Level

Level returns l mapped to the corresponding Logrus level.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL