Skip to content

log/slog Transport ​

Go ReferenceVersionSourceChangelog

Wraps a stdlib *log/slog.Logger. Metadata nests under a single key via one slog.Attr. Per-call context.Context attached via WithContext is passed through to slog.Logger.LogAttrs so handlers downstream (OpenTelemetry, structured shippers) can extract trace context.

sh
go get go.loglayer.dev/transports/slog/v3

Basic Usage ​

go
import (
    "log/slog"
    "os"

    "go.loglayer.dev/v3"
    llslog "go.loglayer.dev/transports/slog/v3"
)

handler := slog.NewJSONHandler(os.Stderr, nil)
sl := slog.New(handler)

log := loglayer.New(loglayer.Config{
    Transport: llslog.New(llslog.Config{Logger: sl}),
})

log.Info("hello")
// {"time":"...","level":"INFO","msg":"hello"}

If you don't pass a Logger, the transport constructs one with slog.NewJSONHandler writing to Writer (default os.Stderr).

Config ​

go
type Config struct {
    transport.BaseConfig

    Logger *slog.Logger // wrap an existing logger
    Writer io.Writer    // used only when Logger is nil
}

Fatal Behavior ​

slog has no fatal level. This transport maps LogLevelFatal to slog.LevelError + 4 so it sorts above Error in any handler that filters by level. The actual os.Exit(1) decision is made by the LogLayer core based on Config.DisableFatalExit. See Fatal Exits the Process.

Metadata Handling ​

The core nests the entry's metadata under MetadataFieldName (default "metadata"; set Config.FlattenMetadata: true to restore per-transport v2 placement). This transport honors that key.

Metadata nests under the metadata key ​

go
log.WithMetadata(loglayer.Metadata{"requestId": "abc", "n": 42}).Info("served")
// {"time":"...","level":"INFO","msg":"served","metadata":{"requestId":"abc","n":42}}

log.WithMetadata(User{ID: 7, Name: "Alice"}).Info("user")
// {"time":"...","level":"INFO","msg":"user","metadata":{"id":7,"name":"Alice"}}

The nested value becomes a single slog.Any(key, value) attribute, so slog renders it via the configured handler (JSON, text, or anything custom). The JSON handler honors json: tags; other handlers may render fields differently.

context.Context Pass-through ​

The slog transport is special: it forwards WithContext directly to the underlying slog.Logger.LogAttrs call.

go
import "context"

ctx := context.WithValue(context.Background(), traceKey{}, "trace-abc")
log.WithContext(ctx).Info("request received")

If your slog handler is wired to OpenTelemetry (e.g. via slogcontext or a custom handler), the trace context is extracted automatically. See Go Context for the broader pattern.

Reaching the Underlying Logger ​

GetLoggerInstance returns the underlying *slog.Logger:

go
sl := log.GetLoggerInstance("slog").(*slog.Logger)
sl.With("global", "field").Info("...")

("slog" is whatever you set as BaseConfig.ID; defaults to an auto-generated ID when unset.)

Level Mapping ​

LogLayer Levelslog LevelNote
LogLevelTraceLevelDebug - 4slog has no Trace; renders as DEBUG-4 in output
LogLevelDebugLevelDebug
LogLevelInfoLevelInfo
LogLevelWarnLevelWarn
LogLevelErrorLevelError
LogLevelFatalLevelError + 4slog has no Fatal; renders as ERROR+4 in output
LogLevelPanicLevelError + 8slog has no Panic; renders as ERROR+8 in output