or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-types.mdcontextual-logging.mdcore-logging.mddiode-writer.mdfield-methods.mdglobal-configuration.mdglobal-logger.mdhooks.mdhttp-middleware.mdindex.mdjournald-integration.mdpkgerrors-integration.mdsampling.mdwriters-and-output.md
tile.json

core-logging.mddocs/

Core Logging

Core logging in zerolog centers around three primary types: Logger, Event, and Level. These form the foundation of all logging operations.

Imports

import "github.com/rs/zerolog"

Logger

The Logger type is the main logging object. It creates log events and manages configuration like minimum level, output writer, samplers, and hooks.

Creating Loggers

// Create a root logger with an io.Writer
func New(w io.Writer) Logger

Example:

import (
    "os"
    "github.com/rs/zerolog"
)

logger := zerolog.New(os.Stderr)
// Create a disabled logger (no-op, for testing or conditional logging)
func Nop() Logger

Example:

logger := zerolog.Nop()
logger.Info().Msg("this message is discarded")

Level Methods

Level methods create log events at specific severity levels. Each returns an *Event that can be configured with fields and terminated with a message.

// Create debug level event
func (l Logger) Debug() *Event

// Create info level event
func (l Logger) Info() *Event

// Create warn level event
func (l Logger) Warn() *Event

// Create error level event
func (l Logger) Error() *Event

// Create fatal level event (calls os.Exit(1) after logging)
func (l Logger) Fatal() *Event

// Create panic level event (calls panic() after logging)
func (l Logger) Panic() *Event

// Create trace level event
func (l Logger) Trace() *Event

// Create event with no level
func (l Logger) Log() *Event

Example:

logger.Info().Msg("application started")
logger.Debug().Str("var", value).Msg("debug info")
logger.Error().Err(err).Msg("operation failed")
// Create event at specified level
func (l Logger) WithLevel(level Level) *Event

Example:

level := zerolog.InfoLevel
logger.WithLevel(level).Msg("dynamic level message")
// Create error or info level event based on whether err is nil
func (l Logger) Err(err error) *Event

Example:

err := doSomething()
logger.Err(err).Msg("operation completed")
// Logs at error level if err != nil, info level if err == nil

Configuration Methods

These methods return a new Logger with different configuration. The original logger is not modified.

// Create child logger with minimum level
func (l Logger) Level(lvl Level) Logger

// Get current minimum level
func (l Logger) GetLevel() Level

Example:

debugLogger := logger.Level(zerolog.DebugLevel)
currentLevel := debugLogger.GetLevel()
// Create child logger with sampler
func (l Logger) Sample(s Sampler) Logger

Example:

// Log only every 10th message
sampledLogger := logger.Sample(&zerolog.BasicSampler{N: 10})
// Create child logger with hooks
func (l Logger) Hook(hooks ...Hook) Logger

Example:

hook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, msg string) {
    e.Str("environment", "production")
})
hookedLogger := logger.Hook(hook)
// Create child logger with different output writer
func (l Logger) Output(w io.Writer) Logger

Example:

fileLogger := logger.Output(logFile)

Context Methods

// Create Context for building child logger with persistent fields
func (l Logger) With() Context

Example:

childLogger := logger.With().
    Str("service", "api").
    Str("version", "1.0").
    Logger()

See Contextual Logging for complete Context API.

// Update logger's context (NOT concurrency-safe)
func (l Logger) UpdateContext(update func(c Context) Context)

Example:

logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
    return c.Str("request_id", requestID)
})

Warning: UpdateContext is not thread-safe. Do not call it concurrently with logging operations.

Context Integration

// Attach logger to Go context.Context
func (l Logger) WithContext(ctx context.Context) context.Context

Example:

ctx := logger.WithContext(context.Background())
// Later retrieve with zerolog.Ctx(ctx)
// Retrieve logger from context.Context
func Ctx(ctx context.Context) *Logger

Example:

logger := zerolog.Ctx(ctx)
logger.Info().Msg("using logger from context")

If no logger is found in the context, Ctx() returns DefaultContextLogger (which defaults to a disabled logger if not set).

Printf-style Methods

For compatibility with standard library logger interface:

// Print at trace level
func (l Logger) Print(v ...interface{})

// Printf at trace level
func (l Logger) Printf(format string, v ...interface{})

// Println at trace level
func (l Logger) Println(v ...interface{})

Example:

logger.Printf("count: %d", 42)

io.Writer Interface

Logger implements io.Writer, allowing it to be used as a writer destination:

func (l Logger) Write(p []byte) (n int, err error)

Example:

stdlog := log.New(logger, "", 0)
stdlog.Print("message from standard logger")

Event

The Event type represents a single log event. Events are created by calling level methods on a Logger and are terminated by calling Msg(), Msgf(), or Send().

Events use a fluent, chainable API for adding fields. Events are immutable after termination and should not be reused.

Event Lifecycle

// 1. Create event
event := logger.Info()

// 2. Add fields (chainable)
event.Str("user", "alice").
      Int("age", 30).
      Bool("active", true)

// 3. Terminate event
event.Msg("user logged in")

Termination Methods

// Send event with message
func (e *Event) Msg(msg string)

// Send event with formatted message
func (e *Event) Msgf(format string, v ...interface{})

// Send event without message
func (e *Event) Send()

// Send event with lazy message (only evaluated if event is enabled)
func (e *Event) MsgFunc(createMsg func() string)

Example:

logger.Info().Msg("simple message")
logger.Info().Msgf("user %s logged in", username)
logger.Info().Str("status", "ok").Send()
logger.Debug().MsgFunc(func() string {
    return fmt.Sprintf("expensive: %v", computeExpensiveValue())
})

Control Methods

// Check if event will be logged
func (e *Event) Enabled() bool

// Disable the event (subsequent operations are no-ops)
func (e *Event) Discard() *Event

// Execute function only if event is enabled
func (e *Event) Func(f func(e *Event)) *Event

Example:

// Conditional expensive operations
if e := logger.Debug(); e.Enabled() {
    result := computeExpensiveValue()
    e.Str("result", result).Msg("debug info")
}

// Conditional discard
logger.Info().
    Func(func(e *zerolog.Event) {
        if shouldDiscard {
            e.Discard()
        }
    }).
    Msg("conditional message")

Context Methods

// Store Go context in event (accessible to hooks)
func (e *Event) Ctx(ctx context.Context) *Event

// Get stored Go context
func (e *Event) GetCtx() context.Context

Example:

ctx := context.WithValue(context.Background(), "trace_id", "123")
logger.Info().Ctx(ctx).Msg("message with context")

Metadata Methods

// Add caller file and line information
func (e *Event) Caller(skip ...int) *Event

// Add caller with specific frame skip count
func (e *Event) CallerSkipFrame(skip int) *Event

// Add type name of value
func (e *Event) Type(key string, val interface{}) *Event

Example:

logger.Info().Caller().Msg("log with caller info")
// Output includes: "caller":"/path/to/file.go:42"

logger.Info().Type("value_type", myStruct).Msg("log with type")
// Output includes: "value_type":"main.MyStruct"

Field Methods

Events have numerous field methods for adding typed data. See Field Methods for the complete reference.

Common field methods:

func (e *Event) Str(key, val string) *Event
func (e *Event) Int(key string, i int) *Event
func (e *Event) Bool(key string, b bool) *Event
func (e *Event) Err(err error) *Event
func (e *Event) Time(key string, t time.Time) *Event
func (e *Event) Dur(key string, d time.Duration) *Event

Level

The Level type represents log severity levels as an int8 enumeration.

Level Type

type Level int8

Level Constants

const (
    TraceLevel Level = -1  // Finest-grained information
    DebugLevel Level = 0   // Debug information
    InfoLevel  Level = 1   // General informational messages
    WarnLevel  Level = 2   // Warning messages
    ErrorLevel Level = 3   // Error conditions
    FatalLevel Level = 4   // Fatal errors (logs then calls os.Exit(1))
    PanicLevel Level = 5   // Panic conditions (logs then calls panic())
    NoLevel    Level = 6   // No level (for events created with Log())
    Disabled   Level = 7   // Completely disabled
)

Level Methods

// Get string representation of level
func (l Level) String() string

Example:

level := zerolog.InfoLevel
fmt.Println(level.String()) // "info"
// Implement encoding.TextMarshaler
func (l Level) MarshalText() ([]byte, error)

// Implement encoding.TextUnmarshaler
func (l *Level) UnmarshalText(text []byte) error

Level Functions

// Parse level from string
func ParseLevel(levelStr string) (Level, error)

Example:

level, err := zerolog.ParseLevel("info")
if err != nil {
    log.Fatal(err)
}
// level == zerolog.InfoLevel

Accepted strings: "trace", "debug", "info", "warn", "warning", "error", "fatal", "panic", "disabled", "" (NoLevel).

// Set global minimum level for all loggers
func SetGlobalLevel(l Level)

Example:

// Disable debug and trace in production
zerolog.SetGlobalLevel(zerolog.InfoLevel)

Note: Global level affects all loggers created before and after the call. Individual logger level settings via Logger.Level() override the global setting.

Usage Examples

Basic Logging

package main

import (
    "os"
    "github.com/rs/zerolog"
)

func main() {
    logger := zerolog.New(os.Stderr)

    logger.Info().Msg("application started")
    logger.Debug().Str("config", "dev").Msg("configuration loaded")
}

Structured Error Logging

err := database.Connect()
if err != nil {
    logger.Error().
        Err(err).
        Str("host", "localhost").
        Int("port", 5432).
        Msg("database connection failed")
    return
}

Level-based Configuration

var logger zerolog.Logger

if production {
    logger = zerolog.New(os.Stderr).Level(zerolog.InfoLevel)
} else {
    logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).
        Level(zerolog.DebugLevel)
}

Conditional Debug Logging

if e := logger.Debug(); e.Enabled() {
    // This expensive operation only runs if debug level is enabled
    data := collectDebugData()
    e.Interface("data", data).Msg("debug snapshot")
}

Fatal and Panic Levels

// Fatal level calls os.Exit(1) after logging
if criticalError {
    logger.Fatal().
        Err(err).
        Msg("critical failure, shutting down")
    // Program exits here
}

// Panic level calls panic() after logging
if invariantViolated {
    logger.Panic().
        Str("invariant", "balance >= 0").
        Int64("balance", balance).
        Msg("invariant violation")
    // panic() is called here
}

No Level Logging

// Log without a level field
logger.Log().
    Str("metric", "request_count").
    Int("value", 1000).
    Send()
// Output: {"metric":"request_count","value":1000}

Logger Chaining

// Create hierarchy of loggers
appLogger := zerolog.New(os.Stderr).
    With().Timestamp().Logger()

requestLogger := appLogger.With().
    Str("request_id", requestID).
    Logger()

dbLogger := requestLogger.With().
    Str("component", "database").
    Logger()

dbLogger.Info().Msg("query executed")
// Includes: request_id, component, and timestamp

Best Practices

1. Create Logger Once

Create loggers once and reuse them. Loggers are lightweight and can be copied by value.

// Good
type Service struct {
    logger zerolog.Logger
}

func NewService(logger zerolog.Logger) *Service {
    return &Service{
        logger: logger.With().Str("service", "my-service").Logger(),
    }
}

2. Check Enabled Before Expensive Operations

Use Enabled() to avoid expensive computations for disabled log levels:

if e := logger.Debug(); e.Enabled() {
    expensiveData := computeExpensiveDebugInfo()
    e.Interface("data", expensiveData).Msg("debug info")
}

3. Use Err() for Errors

Always use Err() instead of Interface() or Str() for error values:

// Good
logger.Error().Err(err).Msg("operation failed")

// Avoid
logger.Error().Interface("error", err).Msg("operation failed")

4. Set Global Level Early

Set the global level during application initialization:

func init() {
    if os.Getenv("DEBUG") == "true" {
        zerolog.SetGlobalLevel(zerolog.DebugLevel)
    } else {
        zerolog.SetGlobalLevel(zerolog.InfoLevel)
    }
}

5. Avoid UpdateContext in Concurrent Code

UpdateContext() is not thread-safe. Use it only during initialization or create new child loggers instead:

// Good - create new logger
requestLogger := logger.With().Str("request_id", id).Logger()

// Avoid in concurrent code
logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
    return c.Str("request_id", id)
})

Performance Considerations

  • Events are pooled and reused to avoid allocations
  • Disabled events short-circuit field evaluation
  • Fields are lazily encoded only when the event is sent
  • Use Enabled() checks for expensive operations
  • Logger copying is cheap (copy by value)

Thread Safety

Loggers are not thread-safe by default. For concurrent access:

  • Use zerolog.SyncWriter() to wrap output writers with mutex
  • Create separate logger instances per goroutine
  • Use diode.Writer for lock-free non-blocking writes

See Writers and Output for thread-safe writers.

See Also