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

global-logger.mddocs/

Global Logger

The log subpackage provides a pre-configured global logger with convenience functions for quick logging without creating logger instances. This is perfect for simple applications, scripts, and quick debugging.

Package Imports

import "github.com/rs/zerolog/log"

Global Logger Variable

// Pre-configured global logger (writes to stderr with timestamp)
var Logger = zerolog.New(os.Stderr).With().Timestamp().Logger()

The global Logger variable is initialized to write JSON logs to stderr with timestamps. You can reassign it to customize behavior:

// Replace global logger
log.Logger = zerolog.New(os.Stdout).With().
    Str("app", "myapp").
    Logger()

Level Functions

All level functions return *zerolog.Event that must be terminated with Msg(), Msgf(), or Send().

// Start trace level message
func Trace() *zerolog.Event

// Start debug level message
func Debug() *zerolog.Event

// Start info level message
func Info() *zerolog.Event

// Start warn level message
func Warn() *zerolog.Event

// Start error level message
func Error() *zerolog.Event

// Start fatal level message (calls os.Exit(1) after logging)
func Fatal() *zerolog.Event

// Start panic level message (calls panic() after logging)
func Panic() *zerolog.Event

// Start message with no level
func Log() *zerolog.Event

// Start message with specified level
func WithLevel(level zerolog.Level) *zerolog.Event

// Start error or info level based on whether err is nil
func Err(err error) *zerolog.Event

Examples:

import "github.com/rs/zerolog/log"

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

log.Debug().
    Str("config", "default").
    Int("port", 8080).
    Msg("server configuration")
// Output: {"level":"debug","time":"2024-01-15T10:30:00Z","config":"default","port":8080,"message":"server configuration"}

log.Error().
    Err(err).
    Str("operation", "database_connect").
    Msg("operation failed")
// Output: {"level":"error","time":"2024-01-15T10:30:00Z","error":"connection timeout","operation":"database_connect","message":"operation failed"}

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

Configuration Functions

Functions that return a new logger based on the global logger:

// Create logger with different output
func Output(w io.Writer) zerolog.Logger

// Create logger with minimum level
func Level(level zerolog.Level) zerolog.Logger

// Create logger with sampler
func Sample(s zerolog.Sampler) zerolog.Logger

// Create logger with hook
func Hook(h zerolog.Hook) zerolog.Logger

Examples:

// Write to file instead of stderr
file, _ := os.Create("app.log")
fileLogger := log.Output(file)
fileLogger.Info().Msg("logged to file")

// Create debug logger
debugLogger := log.Level(zerolog.DebugLevel)
debugLogger.Debug().Msg("debug message")

// Create sampled logger
sampledLogger := log.Sample(&zerolog.BasicSampler{N: 10})
for i := 0; i < 100; i++ {
    sampledLogger.Info().Int("i", i).Msg("sampled message")
}
// Only ~10 messages logged

// Create logger with hook
hook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, msg string) {
    e.Str("app", "myapp")
})
hookedLogger := log.Hook(hook)
hookedLogger.Info().Msg("with hook")

Context Function

// Create child logger with context
func With() zerolog.Context

Example:

// Add persistent fields
logger := log.With().
    Str("service", "api").
    Str("version", "1.0.0").
    Logger()

logger.Info().Msg("service started")
// Output includes: "service":"api","version":"1.0.0"

Printf-Style Functions

For compatibility with standard library logger:

// Printf-style logging at debug level
func Print(v ...interface{})

// Printf-style formatted logging at debug level
func Printf(format string, v ...interface{})

Examples:

log.Print("application started on port", 8080)
// Output: {"level":"debug","time":"2024-01-15T10:30:00Z","message":"application started on port 8080"}

log.Printf("server listening on port %d", 8080)
// Output: {"level":"debug","time":"2024-01-15T10:30:00Z","message":"server listening on port 8080"}

Note: Print and Printf log at debug level, not info level like fmt.Print.

Context Integration

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

Example:

// Attach logger to context (elsewhere in code)
ctx := log.Logger.WithContext(context.Background())

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

This is a convenience wrapper around zerolog.Ctx(ctx).

Usage Examples

Simple Application

package main

import "github.com/rs/zerolog/log"

func main() {
    log.Info().Msg("application started")

    if err := run(); err != nil {
        log.Fatal().Err(err).Msg("application failed")
    }

    log.Info().Msg("application stopped")
}

func run() error {
    log.Debug().Msg("initializing")
    // ... application logic ...
    return nil
}

Custom Global Logger

package main

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

func init() {
    // Customize global logger
    log.Logger = zerolog.New(os.Stdout).With().
        Timestamp().
        Str("app", "myapp").
        Str("version", "1.0.0").
        Logger()

    // Set global level
    zerolog.SetGlobalLevel(zerolog.InfoLevel)
}

func main() {
    log.Info().Msg("application started")
}

Development vs Production

func init() {
    if os.Getenv("ENV") == "production" {
        // Production: JSON to stdout
        log.Logger = zerolog.New(os.Stdout).With().
            Timestamp().
            Logger()
    } else {
        // Development: pretty console output
        log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().
            Timestamp().
            Caller().
            Logger()
    }
}

With Contextual Fields

func main() {
    // Add service info to global logger
    log.Logger = log.With().
        Str("service", "api").
        Str("version", "1.0.0").
        Int("pid", os.Getpid()).
        Logger()

    log.Info().Msg("service started")
    // All logs include service, version, and pid
}

Error Handling

func processFile(filename string) error {
    log.Info().Str("file", filename).Msg("processing file")

    data, err := os.ReadFile(filename)
    if err != nil {
        log.Error().
            Err(err).
            Str("file", filename).
            Msg("failed to read file")
        return err
    }

    log.Info().
        Str("file", filename).
        Int("bytes", len(data)).
        Msg("file processed")

    return nil
}

Structured Logging

func handleRequest(w http.ResponseWriter, r *http.Request) {
    start := time.Now()

    log.Info().
        Str("method", r.Method).
        Str("path", r.URL.Path).
        Str("remote", r.RemoteAddr).
        Msg("request started")

    // ... handle request ...

    log.Info().
        Str("method", r.Method).
        Str("path", r.URL.Path).
        Int("status", 200).
        Dur("elapsed", time.Since(start)).
        Msg("request completed")
}

Conditional Logging

func compute() {
    result := expensiveComputation()

    log.Err(result.Error).
        Str("operation", "compute").
        Int("result", result.Value).
        Msg("computation completed")
    // Logs at error level if result.Error != nil, info level otherwise
}

Best Practices

1. Use for Simple Applications

The global logger is perfect for simple applications and scripts:

// Good for simple apps
package main

import "github.com/rs/zerolog/log"

func main() {
    log.Info().Msg("starting")
    // ... simple logic ...
    log.Info().Msg("done")
}

For complex applications with multiple components, consider using explicit logger instances:

// Better for complex apps
package main

import "github.com/rs/zerolog"

type Service struct {
    logger zerolog.Logger
}

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

2. Configure Early

Configure the global logger during initialization:

func init() {
    // Configure before any logging
    log.Logger = zerolog.New(os.Stdout).With().
        Timestamp().
        Logger()
}

3. Don't Mix Styles

Choose either the global logger or explicit instances, don't mix:

// Good - consistent use of global logger
log.Info().Msg("message 1")
log.Info().Msg("message 2")

// Good - consistent use of instance logger
logger.Info().Msg("message 1")
logger.Info().Msg("message 2")

// Avoid - mixing styles
log.Info().Msg("message 1")
logger.Info().Msg("message 2")

4. Add Context for Complex Apps

Even with the global logger, add context for better logs:

func main() {
    // Add app-level context
    log.Logger = log.With().
        Str("service", "api").
        Str("version", version).
        Logger()

    log.Info().Msg("service started")
}

5. Use Structured Fields

Take advantage of structured logging:

// Good - structured fields
log.Info().
    Str("user", username).
    Int("count", count).
    Msg("user action")

// Avoid - interpolated message
log.Info().Msgf("user %s performed action with count %d", username, count)

6. Consider Migration Path

If starting with the global logger, structure your code to easily migrate to instances:

// Easy to migrate later
func processData(data []byte) {
    logger := &log.Logger // Can easily change to parameter
    logger.Info().Int("bytes", len(data)).Msg("processing")
}

// Would need to refactor
func processData(data []byte) {
    log.Info().Int("bytes", len(data)).Msg("processing")
}

Comparison with Instance Loggers

Global Logger Advantages:

  • Quick to use, no setup required
  • Good for simple applications and scripts
  • Less boilerplate code
  • Easy migration from standard library logger

Instance Logger Advantages:

  • Better for dependency injection
  • Easier testing (can inject mock loggers)
  • Better for complex applications with multiple components
  • More explicit (clear where logger comes from)
  • No global state

Recommendation:

  • Use global logger for scripts, CLIs, and simple applications
  • Use instance loggers for libraries, services, and complex applications

Thread Safety

The global logger is thread-safe (same as any zerolog.Logger). Multiple goroutines can safely call the convenience functions:

for i := 0; i < 10; i++ {
    go func(id int) {
        log.Info().Int("worker", id).Msg("processing")
    }(i)
}

However, reassigning log.Logger itself is not thread-safe. Do this only during initialization:

// Safe - during init
func init() {
    log.Logger = customLogger
}

// Not safe - during execution with concurrent goroutines
func configureLogger() {
    log.Logger = customLogger  // Race condition
}

Migration from Standard Library

The global logger makes it easy to migrate from the standard library log package:

Standard library:

import "log"

log.Println("message")
log.Printf("count: %d", count)

Zerolog global logger:

import "github.com/rs/zerolog/log"

log.Info().Msg("message")
log.Info().Msgf("count: %d", count)
// or better:
log.Info().Int("count", count).Msg("message")

See Also

  • Core Logging - Logger instances and methods
  • Contextual Logging - Adding persistent context
  • Global Configuration - Configure global behavior
  • Writers and Output - Customize output