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.
import "github.com/rs/zerolog/log"// 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()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.EventExamples:
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 == nilFunctions 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.LoggerExamples:
// 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")// Create child logger with context
func With() zerolog.ContextExample:
// 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"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.
// Get logger from context.Context
func Ctx(ctx context.Context) *zerolog.LoggerExample:
// 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).
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
}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")
}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()
}
}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
}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
}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")
}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
}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(),
}
}Configure the global logger during initialization:
func init() {
// Configure before any logging
log.Logger = zerolog.New(os.Stdout).With().
Timestamp().
Logger()
}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")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")
}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)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")
}Global Logger Advantages:
Instance Logger Advantages:
Recommendation:
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
}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")