Skip to content

Zerolog Transport ​

Go ReferenceVersionSourceChangelog

Wraps an existing *zerolog.Logger. Metadata nests under a configurable key ("metadata" by default); with Config.FlattenMetadata: true, map metadata merges as fields. Fatal-level entries are written via WithLevel so the process is not terminated, regardless of zerolog's defaults.

sh
go get go.loglayer.dev/transports/zerolog/v3
go get github.com/rs/zerolog

Basic Usage ​

go
import (
    zlog "github.com/rs/zerolog"
    "os"

    "go.loglayer.dev/v3"
    llzero "go.loglayer.dev/transports/zerolog/v3"
)

z := zlog.New(os.Stderr).With().Timestamp().Logger()

log := loglayer.New(loglayer.Config{
    Transport: llzero.New(llzero.Config{Logger: &z}),
})

log.Info("hello")
// {"level":"info","time":"...","message":"hello"}

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

Config ​

go
type Config struct {
    transport.BaseConfig

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

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")
// {"level":"info","metadata":{"requestId":"abc","n":42},"time":"...","message":"served"}

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

Zerolog's Interface field handler reflects directly into the map or struct, so the value is encoded once at write time without an extra JSON roundtrip.

Change the nesting key on the core's config:

go
loglayer.New(loglayer.Config{
    Transport:         llzero.New(llzero.Config{Logger: &z}),
    MetadataFieldName: "payload",
})

Fields ​

Map fields are merged at the root via zerolog's Fields:

go
log.WithFields(loglayer.Fields{"service": "api"})
log.Info("request")
// {"level":"info","service":"api","time":"...","message":"request"}

If FieldsKey is set on the LogLayer config, the fields are nested first by the core, then merged at root by zerolog. The result appears as a single nested object:

go
loglayer.New(loglayer.Config{
    Transport: llzero.New(llzero.Config{Logger: &z}),
    FieldsKey: "fields",
})

log.WithFields(loglayer.Fields{"requestId": "abc"})
log.Info("hi")
// {"level":"info","fields":{"requestId":"abc"},"time":"...","message":"hi"}

Fatal Behavior ​

The wrapper routes fatal entries through logger.WithLevel(zerolog.FatalLevel) rather than .Fatal(), so it does not trigger zerolog's built-in os.Exit. The core's DisableFatalExit then decides whether os.Exit(1) is called after dispatch. See Fatal Exits the Process.

go
// Default: fatal exits via core
log.Fatal("unrecoverable")

// Opt-out
log = loglayer.New(loglayer.Config{
    Transport:        llzero.New(llzero.Config{Logger: &z}),
    DisableFatalExit: true,
})
log.Fatal("logged but no exit")

Reaching the Underlying Logger ​

GetLoggerInstance returns the wrapped *zerolog.Logger:

go
z := log.GetLoggerInstance("zerolog").(*zlog.Logger)
z.Hook(myHook)

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

Level Mapping ​

LogLayer Levelzerolog Level
LogLevelTraceTraceLevel
LogLevelDebugDebugLevel
LogLevelInfoInfoLevel
LogLevelWarnWarnLevel
LogLevelErrorErrorLevel
LogLevelFatalFatalLevel
LogLevelPanicPanicLevel