log/slog Transport
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.
go get go.loglayer.dev/transports/slog/v3Basic Usage
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
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
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.
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:
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 Level | slog Level | Note |
|---|---|---|
LogLevelTrace | LevelDebug - 4 | slog has no Trace; renders as DEBUG-4 in output |
LogLevelDebug | LevelDebug | |
LogLevelInfo | LevelInfo | |
LogLevelWarn | LevelWarn | |
LogLevelError | LevelError | |
LogLevelFatal | LevelError + 4 | slog has no Fatal; renders as ERROR+4 in output |
LogLevelPanic | LevelError + 8 | slog has no Panic; renders as ERROR+8 in output |
