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.
import "github.com/rs/zerolog"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
}// Start building context for child logger
func (l Logger) With() ContextExample:
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"}// Return logger with accumulated context
func (c Context) Logger() LoggerThis method returns the configured logger. Always call Logger() to finalize the context.
// Remove all context fields and return empty context
func (c Context) Reset() ContextExample:
ctx := logger.With().
Str("a", "1").
Str("b", "2").
Reset(). // Removes a and b
Str("c", "3").
Logger()
// Only "c":"3" remains in contextContext 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.
// 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) ContextExample:
logger := logger.With().
Str("component", "database").
Strs("tags", []string{"prod", "primary"}).
Logger()// 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) ContextExample:
jsonData := []byte(`{"nested":"value"}`)
logger := logger.With().
RawJSON("metadata", jsonData).
Logger()// Add boolean field
func (c Context) Bool(key string, b bool) Context
// Add boolean array field
func (c Context) Bools(key string, b []bool) ContextExample:
logger := logger.With().
Bool("production", true).
Bool("debug", false).
Logger()// 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) ContextExample:
logger := logger.With().
Int("worker_id", 5).
Int64("session_id", 123456789).
Logger()// 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) ContextExample:
logger := logger.With().
Uint16("port", 8080).
Uint64("request_count", 1000000).
Logger()// 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) ContextExample:
logger := logger.With().
Float64("version", 1.2).
Float64("threshold", 0.95).
Logger()// 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() ContextExample:
startTime := time.Now()
logger := logger.With().
Timestamp().
Time("start_time", startTime).
Logger()// 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) ContextExample:
ip := net.ParseIP("192.168.1.1")
logger := logger.With().
IPAddr("server_ip", ip).
Logger()// 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() ContextExample:
lastError := getLastError()
logger := logger.With().
AnErr("last_error", lastError).
Logger()// 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{}) ContextExample:
// 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()// Add caller file:line information
func (c Context) Caller() Context
// Add caller with frame skip count
func (c Context) CallerWithSkipFrameCount(skipFrameCount int) ContextExample:
logger := logger.With().
Caller().
Logger()
logger.Info().Msg("logged with caller info")
// Output includes: "caller":"/path/to/file.go:42"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")
}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
}// 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, componentfunc 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
}// 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"}// 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()// 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()
}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) *LoggerExample:
// 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")
}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")
}
}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()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()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)
})Use Go's context.Context to propagate loggers through application layers:
ctx := logger.WithContext(ctx)
// Pass ctx through function calls
result := processRequest(ctx, request)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()Context creation is thread-safe. The resulting logger can be safely copied across goroutines. However:
UpdateContext() is NOT thread-safeSyncWriter() for thread-safe output