Documentation
¶
Overview ¶
Package log provides a structured, context-aware logging system with distributed tracing support.
The package is designed around explicit dependency injection and context propagation, avoiding global state and encouraging clean, testable code.
Core Types ¶
The package centers around the Logger interface, which provides structured logging methods:
type Logger interface {
Debug(msg string, keysAndValues ...any)
Info(msg string, keysAndValues ...any)
Warn(msg string, keysAndValues ...any)
Error(msg string, keysAndValues ...any)
Fatal(msg string, keysAndValues ...any)
WithKV(key string, value any) Logger
GetAllKV() []any
WithName(name string) Logger
Name() string
AddCallerSkip(skip int) Logger
}
Three implementations are provided:
- ZapLogger: A production-ready logger based on Uber's zap library
- NoopLogger: A logger that discards all messages (useful for testing)
- SpanLogger: A decorator that records logs to both a wrapped logger and a trace span
Basic Usage ¶
Create a logger and use it directly:
conf := log.Config{
Format: "json",
Level: log.LevelInfo,
Output: "stderr",
}
logger := log.NewZapLogger(conf)
logger.Info("Application started", "version", "1.0.0")
Context Integration ¶
The package provides context-aware logging with automatic span integration:
// Store logger in context ctx = log.SetContextLogger(ctx, logger) // Retrieve logger from context logger := log.FromContext(ctx)
When SetContextLogger is called with a context containing a valid OpenTelemetry span, the logger is automatically wrapped with a SpanLogger that records events to both the logger output and the trace span.
Structured Logging ¶
All logging methods accept key-value pairs for structured data:
logger.Info("User action",
"userID", user.ID,
"action", "login",
"ip", request.RemoteAddr,
)
Logger Enrichment ¶
Create derived loggers with additional context:
// Add a name hierarchy
serviceLogger := logger.WithName("auth-service")
// Add persistent key-value pairs
userLogger := serviceLogger.WithKV("userID", userID)
OpenTelemetry Integration ¶
The package seamlessly integrates with OpenTelemetry tracing. When a logger is set in a context with an active span, log events are automatically recorded as span events:
ctx, span := tracer.Start(ctx, "operation")
defer span.End()
ctx = log.SetContextLogger(ctx, logger)
log.FromContext(ctx).Info("Operation started") // Recorded in both log and span
Error and Fatal level logs are recorded as span errors with appropriate status codes.
Using AddCallerSkip for Helper Functions ¶
When you wrap logging calls in helper functions, use AddCallerSkip(1) to ensure the log output reports the correct source line from your application code, not the helper itself.
func handleError(logger log.Logger, err error) {
// Skip this helper frame so the log points to the real caller
logger.AddCallerSkip(1).Error("operation failed", "err", err)
}
func doSomething(logger log.Logger) {
err := someOperation()
if err != nil {
handleError(logger, err) // Log will point here, not inside handleError
}
}
Testing ¶
For unit tests, use NoopLogger to avoid log output:
func TestSomething(t *testing.T) {
logger := log.NewNoopLogger()
service := NewService(logger)
// ... test service
}
Environment Configuration ¶
The Config struct supports environment variables:
- LOG_FORMAT: Output format (console, logfmt, json)
- LOG_LEVEL: Minimum log level (debug, info, warn, error, fatal)
- LOG_OUTPUT: Output destination (stderr, stdout, or file path)
Index ¶
- func SetContextLogger(ctx context.Context, lg Logger) context.Context
- type Config
- type Level
- type Logger
- type NoopLogger
- func (n NoopLogger) AddCallerSkip(skip int) Logger
- func (n NoopLogger) Debug(msg string, keysAndValues ...any)
- func (n NoopLogger) Error(msg string, keysAndValues ...any)
- func (n NoopLogger) Fatal(msg string, keysAndValues ...any)
- func (n NoopLogger) GetAllKV() []any
- func (n NoopLogger) Info(msg string, keysAndValues ...any)
- func (n NoopLogger) Name() string
- func (n NoopLogger) Warn(msg string, keysAndValues ...any)
- func (n NoopLogger) WithKV(key string, value any) Logger
- func (n NoopLogger) WithName(name string) Logger
- type OtelSpanEventRecorder
- type SpanEventRecorder
- type SpanLogger
- func (sl SpanLogger) AddCallerSkip(skip int) Logger
- func (sl SpanLogger) Debug(msg string, keysAndValues ...any)
- func (sl SpanLogger) Error(msg string, keysAndValues ...any)
- func (sl SpanLogger) Fatal(msg string, keysAndValues ...any)
- func (sl SpanLogger) GetAllKV() []any
- func (sl SpanLogger) Info(msg string, keysAndValues ...any)
- func (sl SpanLogger) Name() string
- func (sl SpanLogger) Warn(msg string, keysAndValues ...any)
- func (sl SpanLogger) WithKV(key string, value any) Logger
- func (sl SpanLogger) WithName(name string) Logger
- type ZapLogger
- func (l *ZapLogger) AddCallerSkip(skip int) Logger
- func (l *ZapLogger) Debug(msg string, keysAndValues ...any)
- func (l *ZapLogger) Error(msg string, keysAndValues ...any)
- func (l *ZapLogger) Fatal(msg string, keysAndValues ...any)
- func (l *ZapLogger) GetAllKV() []any
- func (l *ZapLogger) Info(msg string, keysAndValues ...any)
- func (l *ZapLogger) Name() string
- func (l *ZapLogger) Warn(msg string, keysAndValues ...any)
- func (l *ZapLogger) WithKV(key string, value any) Logger
- func (l *ZapLogger) WithName(name string) Logger
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func SetContextLogger ¶
SetContextLogger attaches the provided logger to the context. If the context contains a valid OpenTelemetry span, the logger is wrapped with a SpanLogger that automatically records log events to the span. If logger is nil, a NoopLogger is used.
Types ¶
type Config ¶
type Config struct {
Format string `env:"LOG_FORMAT" env-default:"console"` // console, logfmt or json
Level Level `env:"LOG_LEVEL" env-default:"info"` // debug, info, warn, error, fatal, trace
Output string `env:"LOG_OUTPUT" env-default:"stderr"` // stderr, stdout or file path
}
Config is used to configure the ZapLogger. It supports environment variable configuration with default values.
type Level ¶
type Level string
Level represents the severity level of a log message. It can be used to filter log output based on importance.
const ( // LevelDebug is the most verbose level, used for debugging purposes. LevelDebug Level = "debug" // LevelInfo is used for informational messages. LevelInfo Level = "info" // LevelWarn is used for warning messages that indicate potential issues. LevelWarn Level = "warn" // LevelError is used for error messages that indicate something went wrong. LevelError Level = "error" // LevelFatal is used for fatal errors that typically cause the program to exit. LevelFatal Level = "fatal" )
type Logger ¶
type Logger interface {
// Debug logs a message for low-level debugging.
// Use for detailed information useful during development.
// keysAndValues lets you add structured context (e.g., "user", id).
Debug(msg string, keysAndValues ...any)
// Info logs general information about application progress.
// Use for routine events or state changes.
// keysAndValues lets you add structured context (e.g., "module", name).
Info(msg string, keysAndValues ...any)
// Warn logs a message for unexpected situations that aren't errors.
// Use when something might be wrong but the app can continue.
// keysAndValues lets you add structured context (e.g., "attempt", n).
Warn(msg string, keysAndValues ...any)
// Error logs an error that prevents normal operation.
// Use for failures or problems that need attention.
// keysAndValues lets you add structured context (e.g., "error", err).
Error(msg string, keysAndValues ...any)
// Fatal logs a critical error and may terminate the program.
// Use for unrecoverable failures.
// keysAndValues lets you add structured context (e.g., "reason", reason).
Fatal(msg string, keysAndValues ...any)
// WithKV returns a logger with an extra key-value pair for all future logs.
// Use to add persistent context (e.g., component, request ID).
WithKV(key string, value any) Logger
// GetAllKV returns all persistent key-value pairs for this logger.
// Use to inspect logger context.
GetAllKV() []any
// WithName returns a logger with a specific name (e.g., module or component).
// Use to identify the source of logs.
WithName(name string) Logger
// Name returns the logger's name.
Name() string
// AddCallerSkip returns a logger that skips extra stack frames when reporting log source.
// Use when wrapping the logger in helpers; returns itself if unsupported.
AddCallerSkip(skip int) Logger
}
Logger is a logger interface.
func FromContext ¶
FromContext retrieves the logger stored in the context. If no logger is found in the context, it returns a NoopLogger as a safe default.
func NewNoopLogger ¶
func NewNoopLogger() Logger
NewNoopLogger creates a new NoopLogger instance. All logging operations on the returned logger will be silently discarded.
func NewSpanLogger ¶
func NewSpanLogger(lg Logger, ser SpanEventRecorder) Logger
NewSpanLogger creates a new SpanLogger that wraps the provided logger and records events to the given SpanEventRecorder. The wrapped logger's caller skip is incremented by 1 to account for the SpanLogger wrapper.
func NewZapLogger ¶
func NewZapLogger(conf Config, extraWriters ...zapcore.WriteSyncer) Logger
NewZapLogger creates a new ZapLogger with the given configuration. It supports multiple output formats (console, logfmt, json) and destinations (stderr, stdout, file). Additional write syncers can be provided to write logs to multiple destinations.
type NoopLogger ¶
type NoopLogger struct{}
NoopLogger is a logger implementation that discards all log messages. It implements the Logger interface but performs no actual logging operations. This is useful for testing or when logging needs to be disabled.
func (NoopLogger) AddCallerSkip ¶
func (n NoopLogger) AddCallerSkip(skip int) Logger
AddCallerSkip implements Logger.AddCallerSkip but returns the same NoopLogger instance.
func (NoopLogger) Debug ¶
func (n NoopLogger) Debug(msg string, keysAndValues ...any)
Debug implements Logger.Debug but performs no operation.
func (NoopLogger) Error ¶
func (n NoopLogger) Error(msg string, keysAndValues ...any)
Error implements Logger.Error but performs no operation.
func (NoopLogger) Fatal ¶
func (n NoopLogger) Fatal(msg string, keysAndValues ...any)
Fatal implements Logger.Fatal but performs no operation.
func (NoopLogger) GetAllKV ¶
func (n NoopLogger) GetAllKV() []any
GetAllKV implements Logger.GetAllKV and returns an empty slice.
func (NoopLogger) Info ¶
func (n NoopLogger) Info(msg string, keysAndValues ...any)
Info implements Logger.Info but performs no operation.
func (NoopLogger) Name ¶
func (n NoopLogger) Name() string
Name implements Logger.Name and always returns "noop".
func (NoopLogger) Warn ¶
func (n NoopLogger) Warn(msg string, keysAndValues ...any)
Warn implements Logger.Warn but performs no operation.
func (NoopLogger) WithKV ¶
func (n NoopLogger) WithKV(key string, value any) Logger
WithKV implements Logger.WithKV but returns the same NoopLogger instance.
func (NoopLogger) WithName ¶
func (n NoopLogger) WithName(name string) Logger
WithName implements Logger.WithName but returns the same NoopLogger instance.
type OtelSpanEventRecorder ¶
type OtelSpanEventRecorder struct {
// contains filtered or unexported fields
}
OtelSpanEventRecorder is a SpanEventRecorder implementation that records events to an OpenTelemetry span. It converts log messages and their associated key-value pairs into span events and attributes.
func NewOtelSpanEventRecorder ¶
func NewOtelSpanEventRecorder(span trace.Span) *OtelSpanEventRecorder
NewOtelSpanEventRecorder creates a new OtelSpanEventRecorder that will record events to the provided OpenTelemetry span.
func (*OtelSpanEventRecorder) RecordError ¶
func (ser *OtelSpanEventRecorder) RecordError(name string, keysAndValues ...any)
RecordError records an error event to the span with the given name and attributes. It also sets the span status to error.
func (*OtelSpanEventRecorder) RecordEvent ¶
func (ser *OtelSpanEventRecorder) RecordEvent(name string, keysAndValues ...any)
RecordEvent records an event to the span with the given name and attributes. The keysAndValues are converted to OpenTelemetry attributes.
func (*OtelSpanEventRecorder) SpanID ¶
func (ser *OtelSpanEventRecorder) SpanID() string
SpanID returns the span ID of the span as a string.
func (*OtelSpanEventRecorder) TraceID ¶
func (ser *OtelSpanEventRecorder) TraceID() string
TraceID returns the trace ID of the span as a string.
type SpanEventRecorder ¶
type SpanEventRecorder interface {
// TraceID returns the trace ID of the span.
TraceID() string
// SpanID returns the span ID of the span.
SpanID() string
// RecordEvent records an event to the span.
// keysAndValues are treated as key-value pairs (e.g., "key1", value1, "key2", value2).
RecordEvent(name string, keysAndValues ...any)
// RecordError records an error to the span.
// keysAndValues are treated as key-value pairs (e.g., "key1", value1, "key2", value2).
RecordError(name string, keysAndValues ...any)
}
SpanEventRecorder is an interface for recording events and errors to a span.
type SpanLogger ¶
type SpanLogger struct {
// contains filtered or unexported fields
}
SpanLogger is a logger that wraps another logger and additionally records log events to a span using a SpanEventRecorder. This allows log messages to be correlated with distributed traces.
func (SpanLogger) AddCallerSkip ¶
func (sl SpanLogger) AddCallerSkip(skip int) Logger
AddCallerSkip returns a new SpanLogger with increased caller skip on the wrapped logger.
func (SpanLogger) Debug ¶
func (sl SpanLogger) Debug(msg string, keysAndValues ...any)
Debug logs a debug message to both the wrapped logger and the span.
func (SpanLogger) Error ¶
func (sl SpanLogger) Error(msg string, keysAndValues ...any)
Error logs an error message to both the wrapped logger and the span. The error is recorded as an error event in the span.
func (SpanLogger) Fatal ¶
func (sl SpanLogger) Fatal(msg string, keysAndValues ...any)
Fatal logs a fatal message to both the wrapped logger and the span. The error is recorded as an error event in the span.
func (SpanLogger) GetAllKV ¶
func (sl SpanLogger) GetAllKV() []any
GetAllKV returns all key-value pairs from the wrapped logger.
func (SpanLogger) Info ¶
func (sl SpanLogger) Info(msg string, keysAndValues ...any)
Info logs an info message to both the wrapped logger and the span.
func (SpanLogger) Name ¶
func (sl SpanLogger) Name() string
Name returns the name of the wrapped logger.
func (SpanLogger) Warn ¶
func (sl SpanLogger) Warn(msg string, keysAndValues ...any)
Warn logs a warning message to both the wrapped logger and the span.
func (SpanLogger) WithKV ¶
func (sl SpanLogger) WithKV(key string, value any) Logger
WithKV returns a new SpanLogger with the key-value pair added to the wrapped logger. The SpanEventRecorder remains the same.
func (SpanLogger) WithName ¶
func (sl SpanLogger) WithName(name string) Logger
WithName returns a new SpanLogger with the given name set on the wrapped logger. The SpanEventRecorder remains the same.
type ZapLogger ¶
type ZapLogger struct {
// contains filtered or unexported fields
}
ZapLogger is a logger implementation backed by Uber's zap logger. It provides structured logging with high performance and supports various output formats and destinations.
func (*ZapLogger) AddCallerSkip ¶
AddCallerSkip returns a new ZapLogger that skips additional stack frames when determining the caller.
func (*ZapLogger) GetAllKV ¶
GetAllKV returns all key-value pairs that have been added to this logger instance.