Cheat Sheet
At a Glance
import (
"go.loglayer.dev"
"go.loglayer.dev/transports/structured"
)
log := loglayer.New(loglayer.Config{Transport: structured.New(structured.Config{})})
// The fluent chain: persistent fields → per-call metadata + error → terminal level method.
log.
WithFields(loglayer.Fields{"requestId": "abc"}). // returns a new logger; assign on a real call site
WithMetadata(loglayer.Metadata{"durationMs": 23}).
WithError(err).
Error("request failed")That's the 80% case. The rest of this page is a one-line lookup for everything else.
Method Conventions
LogLayer uses two distinct method patterns. Knowing which is which avoids one of the few footguns in the API.
| Prefix | Pattern | Example |
|---|---|---|
With* | Returns a new logger or builder. The receiver is unchanged; assign the return value or your change is lost. | log = log.WithFields(...) |
Mute, Unmute, Set, Enable, Disable, Add, Remove | Mutates the receiver in place. Returns *LogLayer for chaining; the return value is the same instance. | log.MuteFields() |
Child() is the one exception to the prefix rule: it returns a new logger (conventional name in Go logging libraries; mirrors zerolog/slog). Treat it the same as With* and assign the result.
log = log.WithFields(loglayer.Fields{"req": "abc"}) // ✅ assigned
log.WithFields(loglayer.Fields{"req": "abc"}) // ❌ result discarded; emits without req
log.MuteFields() // ✅ in-place mutation; no assignment neededConstruction
loglayer.New(Config) *LogLayer is the typical entry point: it panics on misconfiguration (matches Go convention for setup-time errors). For applications that prefer explicit error handling on missing or invalid config, use loglayer.Build(Config) (*LogLayer, error), which returns loglayer.ErrNoTransport, ErrTransportAndTransports, or ErrUngroupedTransportsWithoutMode instead of panicking.
// Panics on misconfiguration (typical setup).
log := loglayer.New(loglayer.Config{
Transport: structured.New(structured.Config{}),
})
// Or explicit error handling.
log, err := loglayer.Build(loglayer.Config{
Transport: structured.New(structured.Config{}),
})Log Levels
log.Trace("...")
log.Debug("...")
log.Info("...")
log.Warn("...")
log.Error("...")
log.Fatal("...") // calls os.Exit(1) by default; set Config.DisableFatalExit to skip
log.Panic("...") // calls panic(joined-message) after dispatch; recoverableEach method takes ...any, joined with a space.
For fmt.Sprintf-style format strings, register the optional fmtlog plugin:
import "go.loglayer.dev/plugins/fmtlog"
log.AddPlugin(fmtlog.New())
log.Info("user %d signed in", userID)
log.WithMetadata(loglayer.Metadata{"reqId": reqID}).
Error("request %s failed: %v", reqID, err)Without the plugin, multi-arg messages are space-joined.
Metadata
// Struct (preferred when the shape is fixed; cheaper, type-checked)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
log.WithMetadata(User{ID: 7, Name: "Alice"}).Info("user")
// Map (use when keys vary at runtime; loglayer.Metadata is map[string]any)
log.WithMetadata(loglayer.Metadata{"id": 1}).Info("ok")
// No message, log just the metadata
log.MetadataOnly(loglayer.Metadata{"status": "healthy"})
log.MetadataOnly(loglayer.Metadata{"status": "warn"}, loglayer.MetadataOnlyOpts{LogLevel: loglayer.LogLevelWarn})MetadataOnly is a terminal call, not a builder. It dispatches the entry immediately. You cannot chain WithError or WithContext onto it; for that, use log.WithMetadata(...).Info(...) etc.
Errors
log.WithError(err).Error("failed")
log.ErrorOnly(err)
log.ErrorOnly(err, loglayer.ErrorOnlyOpts{LogLevel: loglayer.LogLevelFatal})ErrorOnly is also a terminal call like MetadataOnly. Use log.WithError(err).Error("...") if you want to attach an error and a message together.
Fields (persistent)
// WithFields and WithoutFields return a NEW logger; assign the result.
log = log.WithFields(loglayer.Fields{"requestId": "123"})
// Read it back
fields := log.GetFields()
// Remove specific keys (returns new logger)
log = log.WithoutFields("requestId")
// Remove all (returns new logger)
log = log.WithoutFields()
// Mute / unmute output without losing the data (mutates in place)
log.MuteFields().UnmuteFields()Short aliases: loglayer.F and loglayer.M
Both are type aliases for loglayer.Fields and loglayer.Metadata (interchangeable at every call site). Use them to keep dense call sites readable:
log.WithFields(loglayer.F{"requestId": "abc"}).
WithMetadata(loglayer.M{"durationMs": 23}).
Info("served")Go Context
import "context"
// Bind once, every emission carries it (per-request handlers).
log = log.WithContext(ctx)
log.Info("served")
log.Warn("retrying")
// Or per-call only (override):
log.WithContext(otherCtx).Info("override for this entry")Surfaced to transports via TransportParams.Ctx and to plugin dispatch hooks via params.Ctx. The loghttp middleware binds r.Context() automatically. See Go Context.
Logger in a Go Context (zerolog-style)
// Middleware: store request-scoped logger in ctx
ctx = loglayer.NewContext(ctx, reqLog)
// Handler: pull it back out
log := loglayer.FromContext(ctx) // nil if not attached
log := loglayer.MustFromContext(ctx) // panics if not attachedBuilder vs Logger chain
The "At a Glance" example shows the typical chain. Two things to know:
WithFields,WithContext,WithGroup(when called on*LogLayer) andWithPrefix,Child,WithoutFieldsall return a new logger. Assign them:log = log.WithContext(ctx).WithMetadata,WithError, and the sameWithContext/WithGroupwhen called on a*LogBuilderreturn a*LogBuilder: single-use, terminated by a level method (Info,Warn, ...). Don't assign the builder.
Child Loggers
child := log.Child() // copy of fields + level state
prefixed := log.WithPrefix("[auth]") // child with a prefix prependedMutations on the child do not affect the parent.
Level Control
log.SetLevel(loglayer.LogLevelWarn) // enable warn and above
log.EnableLevel(loglayer.LogLevelDebug) // turn one level on
log.DisableLevel(loglayer.LogLevelDebug) // turn one level off
log.IsLevelEnabled(loglayer.LogLevelInfo) // check
log.DisableLogging() // master kill switch
log.EnableLogging()Transport Management
log.AddTransport(t) // append (replaces if same ID)
log.RemoveTransport("id") // returns true if removed
log.SetTransports(t1, t2) // replace all
log.GetLoggerInstance("id") // underlying logger from a transportGroups
// Define routing rules at construction
log := loglayer.New(loglayer.Config{
Transports: []loglayer.Transport{...},
Routing: loglayer.RoutingConfig{
Groups: map[string]loglayer.LogGroup{
"database": {Transports: []string{"datadog"}, Level: loglayer.LogLevelError},
"auth": {Transports: []string{"sentry"}, Level: loglayer.LogLevelWarn},
},
},
})
// Tag a single entry
log.WithGroup("database").Error("connection lost")
log.WithGroup("database", "auth").Error("auth db failure") // union of both groups' transports
// Persistent tagging via a child logger
dbLog := log.WithGroup("database")
dbLog.Error("pool exhausted") // every log routes via 'database'
// Runtime management
log.AddGroup("inbox", loglayer.LogGroup{Transports: []string{"datadog"}})
log.RemoveGroup("inbox") // returns bool
log.EnableGroup("database")
log.DisableGroup("database")
log.SetGroupLevel("database", loglayer.LogLevelDebug)
log.SetActiveGroups("database") // restrict to these groups
log.ClearActiveGroups() // remove the filter
log.GetGroups() // shallow copy of current config
// Drive the active filter from an env var
loglayer.ActiveGroupsFromEnv("LOGLAYER_GROUPS") // returns []string for Routing.ActiveGroupsSee Groups for the eight-rule routing precedence (defined-but-disabled vs undefined groups, per-group level filtering, ungrouped fallback) and a worked multi-service example.
Plugins
import "go.loglayer.dev/plugins/redact"
// Inline single-hook plugin via an adapter constructor
log.AddPlugin(loglayer.NewDataHook("tag", func(p loglayer.BeforeDataOutParams) loglayer.Data {
return loglayer.Data{"service": "checkout"}
}))
// Other adapters: NewFieldsHook, NewMetadataHook, NewMessageHook, NewLevelHook, NewSendGate, NewPlugin (no hooks).
// Multi-hook plugins: declare a type implementing Plugin + the hook interfaces.
// Redact plugin (key + regex matching, walks structs/maps/slices)
log.AddPlugin(redact.New(redact.Config{
Keys: []string{"password", "apiKey"},
Patterns: []*regexp.Regexp{regexp.MustCompile(`^\d{16}$`)}, // credit-card-shaped
}))
// Management
log.RemovePlugin("id") // returns true if removed
log.GetPlugin("id") // (Plugin, bool)
log.PluginCount() // intSix lifecycle hooks (any subset, nil fields skipped): OnFieldsCalled, OnMetadataCalled, OnBeforeDataOut, OnBeforeMessageOut, TransformLogLevel, ShouldSend. See Plugins overview for hook semantics, Creating Plugins for the authoring tutorial (covers panic recovery, testing, and the RecoveredPanicError type), or the examples/custom-plugin directory for a runnable from-scratch demo.
Raw
log.Raw(loglayer.RawLogEntry{
LogLevel: loglayer.LogLevelInfo,
Messages: []any{"already assembled"},
Metadata: loglayer.Metadata{"k": "v"},
Err: err,
Fields: loglayer.Fields{"override": "ctx"}, // optional override
Ctx: ctx, // optional Go context
})Levels
loglayer.LogLevelDebug // 10
loglayer.LogLevelInfo // 20
loglayer.LogLevelWarn // 30
loglayer.LogLevelError // 40
loglayer.LogLevelFatal // 50Source / Caller info
log := loglayer.New(loglayer.Config{
Transport: structured.New(structured.Config{}),
Source: loglayer.SourceConfig{
Enabled: true, // capture file/line/function
FieldName: "source", // default; override to "caller" etc.
},
})
log.Info("served")
// {"level":"info","time":"...","msg":"served","source":{"function":"main.handler","file":"/app/main.go","line":42}}Off by default. Costs ~100 ns / one runtime.Caller per emission when on. The slog Handler forwards slog.Record.PC automatically (no capture cost on the slog path).
slog Interop
import "go.loglayer.dev/integrations/sloghandler"
// Make every slog.Info(...) flow through your loglayer pipeline (plugins,
// fan-out, groups, level state). slog.With / WithAttrs become persistent
// fields; WithGroup nests; LogValuer is resolved.
slog.SetDefault(slog.New(sloghandler.New(log)))
slog.Info("served", "userId", 42)The opposite direction (loglayer emitting through a *slog.Logger backend) is the slog Transport.
