Getting Started
LogLayer for Go targets Go 1.25+ for the main module: go.loglayer.dev/v3. Transports and plugins ship as separate modules, so you only pull in dependencies for the ones you actually use. Individual transports and plugins call out any stricter requirement on their per-page docs.
Installation
LogLayer ships as a multi-module repo: the core lives at go.loglayer.dev/v3, and every transport and plugin is its own independently-versioned sub-module. You install the core plus only the transports you actually use.
go get go.loglayer.dev/v3
go get go.loglayer.dev/transports/structured/v3Basic Usage with the Structured Transport
The simplest way to start is the Structured Transport, which writes one JSON object per log entry to os.Stdout:
package main
import (
"errors"
"fmt"
"go.loglayer.dev/v3"
"go.loglayer.dev/transports/structured/v3"
)
func main() {
log, err := loglayer.Build(loglayer.Config{
Transport: structured.New(structured.Config{}),
FieldsKey: "context",
})
if err != nil {
fmt.Printf("configure logger: %v\n", err)
return
}
// Basic logging
log.Info("Hello world!")
// {"level":"info","time":"2026-04-25T12:00:00Z","msg":"Hello world!"}
// With metadata (loglayer.Metadata is an alias for map[string]any)
log.WithMetadata(loglayer.Metadata{"user": "alice"}).Info("User logged in")
// {"level":"info","time":"...","msg":"User logged in","metadata":{"user":"alice"}}
// With persistent fields (WithFields returns a NEW logger; assign it)
reqLog := log.WithFields(loglayer.Fields{"requestId": "123"})
reqLog.Info("Processing request")
// {"level":"info","time":"...","msg":"Processing request","context":{"requestId":"123"}}
// With an error
log.WithError(errors.New("something went wrong")).Error("Failed")
// {"level":"error","time":"...","msg":"Failed","err":{"message":"something went wrong"}}
}The example above uses loglayer.Build because it showcases runtime config: when the config comes from a runtime source (env vars, config file), Build handles errors explicitly instead of panicking. For programmatic setup, loglayer.New panics on misconfiguration (no transport configured) and fits where a bad config is a programmer error. See New vs Build.
The example sets FieldsKey to nest fields under their own key; metadata nests under "metadata" by default. See Configuration for every knob on loglayer.Config: error serialization, field/metadata placement, prefix, source capture, group routing, fatal-exit control, and more.
Pretty terminal output
For local development, the Pretty Transport gives you colorized, theme-aware output with three view modes. Much easier to scan than raw JSON or the basic Console Transport.
Configure an Error Serializer
The default error format is {"message": err.Error()}. To expand fmt.Errorf("...: %w", err) chains and errors.Join lists into a causes array, use loglayer.UnwrappingErrorSerializer:
log := loglayer.New(loglayer.Config{
Transport: structured.New(structured.Config{}),
ErrorSerializer: loglayer.UnwrappingErrorSerializer,
})
log.WithError(fmt.Errorf("op failed: %w", io.EOF)).Error("oops")
// {"err":{"causes":[{"message":"EOF"}],"message":"op failed: EOF"}}For stack traces, custom shapes, or other options, see Error Handling.
Using a Logger Wrapper
If you already have an existing logging stack, LogLayer can wrap it so your call sites use the LogLayer API while emission goes through the underlying logger you've already configured. Here it is for zerolog:
go get go.loglayer.dev/transports/zerolog/v3 github.com/rs/zerologimport (
"os"
zlog "github.com/rs/zerolog"
"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.WithFields(loglayer.Fields{"requestId": "abc"}).Info("served")The same shape works for zap, log/slog, logrus, charmbracelet/log, and phuslu/log. See the Transports overview for the full list and per-wrapper config.
Next Steps
- Configuration: every option in
Config. - Cheat Sheet: quick reference for the common methods.
- Logging API: the full method-by-method tour.
- Transports: pick a backend and configure it.
