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

contextual-logging.mddocs/

Contextual Logging

Contextual logging allows you to create child loggers with persistent fields that automatically appear in all subsequent log events. This is perfect for adding request IDs, user information, component names, or any other fields that should be present across multiple log statements.

Imports

import "github.com/rs/zerolog"

Context Type

The Context type is used to build child loggers with persistent contextual fields. You create a Context by calling With() on a Logger, add fields using the same methods available on Event, then finalize it by calling Logger().

type Context struct {
    // unexported fields
}

Creating Context

// Start building context for child logger
func (l Logger) With() Context

Example:

logger := zerolog.New(os.Stderr)

// Create child logger with context
childLogger := logger.With().
    Str("service", "api").
    Str("version", "1.0.0").
    Logger()

childLogger.Info().Msg("started")
// Output: {"level":"info","service":"api","version":"1.0.0","message":"started"}

Finalizing Context

// Return logger with accumulated context
func (c Context) Logger() Logger

This method returns the configured logger. Always call Logger() to finalize the context.

// Remove all context fields and return empty context
func (c Context) Reset() Context

Example:

ctx := logger.With().
    Str("a", "1").
    Str("b", "2").
    Reset(). // Removes a and b
    Str("c", "3").
    Logger()
// Only "c":"3" remains in context

Field Methods

Context supports the same field methods as Event. All methods return Context for chaining and the fields persist across all log events from the resulting logger.

String Fields

// Add string field
func (c Context) Str(key, val string) Context

// Add string array field
func (c Context) Strs(key string, vals []string) Context

// Add fmt.Stringer field (or null if val is nil)
func (c Context) Stringer(key string, val fmt.Stringer) Context

// Add fmt.Stringer array field
func (c Context) Stringers(key string, vals []fmt.Stringer) Context

Example:

logger := logger.With().
    Str("component", "database").
    Strs("tags", []string{"prod", "primary"}).
    Logger()

Binary Fields

// Add bytes as string field
func (c Context) Bytes(key string, val []byte) Context

// Add bytes as hex string field
func (c Context) Hex(key string, val []byte) Context

// Add pre-encoded JSON field
func (c Context) RawJSON(key string, b []byte) Context

// Add pre-encoded CBOR field
func (c Context) RawCBOR(key string, b []byte) Context

Example:

jsonData := []byte(`{"nested":"value"}`)
logger := logger.With().
    RawJSON("metadata", jsonData).
    Logger()

Boolean Fields

// Add boolean field
func (c Context) Bool(key string, b bool) Context

// Add boolean array field
func (c Context) Bools(key string, b []bool) Context

Example:

logger := logger.With().
    Bool("production", true).
    Bool("debug", false).
    Logger()

Integer Fields

// Add int field
func (c Context) Int(key string, i int) Context
func (c Context) Int8(key string, i int8) Context
func (c Context) Int16(key string, i int16) Context
func (c Context) Int32(key string, i int32) Context
func (c Context) Int64(key string, i int64) Context

// Add int array fields
func (c Context) Ints(key string, i []int) Context
func (c Context) Ints8(key string, i []int8) Context
func (c Context) Ints16(key string, i []int16) Context
func (c Context) Ints32(key string, i []int32) Context
func (c Context) Ints64(key string, i []int64) Context

Example:

logger := logger.With().
    Int("worker_id", 5).
    Int64("session_id", 123456789).
    Logger()

Unsigned Integer Fields

// Add uint field
func (c Context) Uint(key string, i uint) Context
func (c Context) Uint8(key string, i uint8) Context
func (c Context) Uint16(key string, i uint16) Context
func (c Context) Uint32(key string, i uint32) Context
func (c Context) Uint64(key string, i uint64) Context

// Add uint array fields
func (c Context) Uints(key string, i []uint) Context
func (c Context) Uints8(key string, i []uint8) Context
func (c Context) Uints16(key string, i []uint16) Context
func (c Context) Uints32(key string, i []uint32) Context
func (c Context) Uints64(key string, i []uint64) Context

Example:

logger := logger.With().
    Uint16("port", 8080).
    Uint64("request_count", 1000000).
    Logger()

Float Fields

// Add float32 field
func (c Context) Float32(key string, f float32) Context

// Add float64 field
func (c Context) Float64(key string, f float64) Context

// Add float32 array field
func (c Context) Floats32(key string, f []float32) Context

// Add float64 array field
func (c Context) Floats64(key string, f []float64) Context

Example:

logger := logger.With().
    Float64("version", 1.2).
    Float64("threshold", 0.95).
    Logger()

Time Fields

// Add time field
func (c Context) Time(key string, t time.Time) Context

// Add time array field
func (c Context) Times(key string, t []time.Time) Context

// Add duration field
func (c Context) Dur(key string, d time.Duration) Context

// Add duration array field
func (c Context) Durs(key string, d []time.Duration) Context

// Add time difference as duration
func (c Context) TimeDiff(key string, t, start time.Time) Context

// Add timestamp with configured field name
func (c Context) Timestamp() Context

Example:

startTime := time.Now()
logger := logger.With().
    Timestamp().
    Time("start_time", startTime).
    Logger()

Network Fields

// Add IP address field
func (c Context) IPAddr(key string, ip net.IP) Context

// Add IP prefix/CIDR field
func (c Context) IPPrefix(key string, pfx net.IPNet) Context

// Add MAC address field
func (c Context) MACAddr(key string, ha net.HardwareAddr) Context

Example:

ip := net.ParseIP("192.168.1.1")
logger := logger.With().
    IPAddr("server_ip", ip).
    Logger()

Error Fields

// Add error with default error field name
func (c Context) Err(err error) Context

// Add error with custom key
func (c Context) AnErr(key string, err error) Context

// Add error array field
func (c Context) Errs(key string, errs []error) Context

// Add stack trace (requires ErrorStackMarshaler to be set)
func (c Context) Stack() Context

Example:

lastError := getLastError()
logger := logger.With().
    AnErr("last_error", lastError).
    Logger()

Complex Fields

// Add array field using LogArrayMarshaler
func (c Context) Array(key string, arr LogArrayMarshaler) Context

// Add nested dictionary field
func (c Context) Dict(key string, dict *Event) Context

// Add object field using LogObjectMarshaler
func (c Context) Object(key string, obj LogObjectMarshaler) Context

// Embed object fields at top level
func (c Context) EmbedObject(obj LogObjectMarshaler) Context

// Add interface using reflection
func (c Context) Interface(key string, i interface{}) Context

// Alias for Interface
func (c Context) Any(key string, i interface{}) Context

// Add multiple fields from map or struct
func (c Context) Fields(fields interface{}) Context

Example:

// Using Dict
metadata := zerolog.Dict().
    Str("region", "us-west").
    Int("shard", 3)

logger := logger.With().
    Dict("metadata", metadata).
    Logger()

// Using Fields with map
logger := logger.With().
    Fields(map[string]interface{}{
        "service": "api",
        "version": "1.0",
        "port":    8080,
    }).
    Logger()

Caller Information

// Add caller file:line information
func (c Context) Caller() Context

// Add caller with frame skip count
func (c Context) CallerWithSkipFrameCount(skipFrameCount int) Context

Example:

logger := logger.With().
    Caller().
    Logger()

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

Usage Patterns

Request-Scoped Logger

func handleRequest(w http.ResponseWriter, r *http.Request) {
    requestID := generateRequestID()

    // Create request-scoped logger
    reqLogger := logger.With().
        Str("request_id", requestID).
        Str("method", r.Method).
        Str("path", r.URL.Path).
        Logger()

    reqLogger.Info().Msg("request started")

    // Pass to other functions
    processRequest(reqLogger, r)

    reqLogger.Info().Msg("request completed")
}

func processRequest(logger zerolog.Logger, r *http.Request) {
    // All log statements automatically include request_id, method, path
    logger.Debug().Msg("processing request")
}

Component Logger

type Database struct {
    logger zerolog.Logger
}

func NewDatabase(logger zerolog.Logger, name string) *Database {
    return &Database{
        logger: logger.With().
            Str("component", "database").
            Str("db_name", name).
            Logger(),
    }
}

func (db *Database) Query(sql string) error {
    // All database logs include component and db_name
    db.logger.Debug().Str("sql", sql).Msg("executing query")
    // ...
    return nil
}

Hierarchical Loggers

// Application logger
appLogger := zerolog.New(os.Stderr).With().
    Str("app", "myapp").
    Str("version", "1.0.0").
    Timestamp().
    Logger()

// Service logger (inherits app fields)
serviceLogger := appLogger.With().
    Str("service", "api").
    Logger()

// Component logger (inherits app and service fields)
dbLogger := serviceLogger.With().
    Str("component", "database").
    Logger()

dbLogger.Info().Msg("query executed")
// Includes: app, version, timestamp, service, component

User Context

func withUserContext(logger zerolog.Logger, userID string, role string) zerolog.Logger {
    return logger.With().
        Str("user_id", userID).
        Str("role", role).
        Logger()
}

func handleAuthenticatedRequest(logger zerolog.Logger, user User) {
    userLogger := withUserContext(logger, user.ID, user.Role)

    userLogger.Info().Msg("user action started")
    // All subsequent logs include user_id and role
}

Timestamp Context

// Add timestamp to all log events
logger := zerolog.New(os.Stderr).With().Timestamp().Logger()

logger.Info().Msg("message")
// Output: {"level":"info","time":"2024-01-15T10:30:00Z","message":"message"}

Multiple Contexts

// Base logger with common fields
baseLogger := zerolog.New(os.Stderr).With().
    Str("environment", "production").
    Str("datacenter", "us-west-1").
    Timestamp().
    Logger()

// Different service contexts
apiLogger := baseLogger.With().Str("service", "api").Logger()
workerLogger := baseLogger.With().Str("service", "worker").Logger()
cronLogger := baseLogger.With().Str("service", "cron").Logger()

Updating Context

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

Warning: This method is not thread-safe. Only use during initialization or in single-threaded scenarios.

Example:

// During initialization (safe)
func (s *Service) SetRequestID(id string) {
    s.logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
        return c.Str("request_id", id)
    })
}

// Better approach: create new logger
func (s *Service) SetRequestID(id string) zerolog.Logger {
    return s.logger.With().Str("request_id", id).Logger()
}

Context with Go context.Context

Zerolog integrates with Go's context.Context for passing loggers through application layers.

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

// Retrieve logger from context.Context
func Ctx(ctx context.Context) *Logger

Example:

// Attach logger to context
ctx := logger.With().
    Str("request_id", requestID).
    Logger().
    WithContext(context.Background())

// Pass context through application
handleRequest(ctx)

// Retrieve logger from context
func handleRequest(ctx context.Context) {
    logger := zerolog.Ctx(ctx)
    logger.Info().Msg("handling request")
}

With HTTP handlers:

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Create request logger
        reqLogger := logger.With().
            Str("request_id", generateID()).
            Str("path", r.URL.Path).
            Logger()

        // Attach to request context
        ctx := reqLogger.WithContext(r.Context())

        // Pass to next handler
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func handler(w http.ResponseWriter, r *http.Request) {
    // Retrieve logger from context
    logger := zerolog.Ctx(r.Context())
    logger.Info().Msg("handling request")
}

Best Practices

1. Create Context Once

Create child loggers once and reuse them:

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

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

// Avoid creating context repeatedly
func (s *Service) Process() {
    // Don't do this in a loop
    for i := 0; i < 1000; i++ {
        logger := s.logger.With().Int("iteration", i).Logger()
        logger.Info().Msg("processing")
    }
}

2. Use Hierarchical Context

Build logger hierarchies that mirror your application structure:

appLogger := baseLogger.With().Str("app", "myapp").Logger()
serviceLogger := appLogger.With().Str("service", "api").Logger()
componentLogger := serviceLogger.With().Str("component", "auth").Logger()

3. Request-Scoped Context

Create request-scoped loggers with request metadata:

reqLogger := logger.With().
    Str("request_id", id).
    Str("user_id", userID).
    Str("method", method).
    Str("path", path).
    Logger()

4. Avoid UpdateContext

Prefer creating new child loggers over using UpdateContext():

// Good - thread-safe
newLogger := logger.With().Str("key", value).Logger()

// Avoid - not thread-safe
logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
    return c.Str("key", value)
})

5. Use Context Propagation

Use Go's context.Context to propagate loggers through application layers:

ctx := logger.WithContext(ctx)
// Pass ctx through function calls
result := processRequest(ctx, request)

6. Keep Context Lean

Only add fields to context that will appear in most/all log statements:

// Good - common fields
logger := logger.With().
    Str("service", "api").
    Str("version", version).
    Logger()

// Avoid - transient fields
logger := logger.With().
    Int("request_count", count). // Changes frequently
    Logger()

Performance Considerations

  • Context fields are evaluated once when the child logger is created
  • Context fields add minimal overhead to each log event
  • Logger copying is cheap (copy by value)
  • Creating context repeatedly has allocation costs
  • Reuse child loggers where possible

Thread Safety

Context creation is thread-safe. The resulting logger can be safely copied across goroutines. However:

  • UpdateContext() is NOT thread-safe
  • Multiple goroutines can safely use the same logger for logging
  • Wrap writers with SyncWriter() for thread-safe output

See Also

  • Core Logging - Logger and Event basics
  • Field Methods - Complete field method reference
  • HTTP Middleware - Request-scoped logging with hlog
  • Global Configuration - Customize field names