Skip to content

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.

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

Basic 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:

go
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:

go
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:

sh
go get go.loglayer.dev/transports/zerolog/v3 github.com/rs/zerolog
go
import (
    "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