Documentation
¶
Overview ¶
Package logger provides logging functionality with zerolog adapter
Package logger provides filtering capabilities for sensitive data in log output.
Package logger defines the logging interface used throughout the application. It provides a contract for structured logging implementations.
Index ¶
- Constants
- func AddAMQPElapsed(ctx context.Context, nanos int64)
- func AddDBElapsed(ctx context.Context, nanos int64)
- func GetAMQPCounter(ctx context.Context) int64
- func GetAMQPElapsed(ctx context.Context) int64
- func GetDBCounter(ctx context.Context) int64
- func GetDBElapsed(ctx context.Context) int64
- func IncrementAMQPCounter(ctx context.Context)
- func IncrementDBCounter(ctx context.Context)
- func ResolvePretty(format string, legacyPretty, otlpLogsActive, isTerminal bool) bool
- func StdoutIsTerminal() bool
- func WithAMQPCounter(ctx context.Context) context.Context
- func WithDBCounter(ctx context.Context) context.Context
- func WithRequestCounters(ctx context.Context) context.Context
- func WithSeverityHook(ctx context.Context, hook func(zerolog.Level)) context.Context
- type FilterConfig
- type LogEvent
- type LogEventAdapter
- func (lea *LogEventAdapter) Bool(key string, value bool) LogEvent
- func (lea *LogEventAdapter) Bytes(key string, val []byte) LogEvent
- func (lea *LogEventAdapter) Dur(key string, d time.Duration) LogEvent
- func (lea *LogEventAdapter) Enabled() bool
- func (lea *LogEventAdapter) Err(err error) LogEvent
- func (lea *LogEventAdapter) Int(key string, value int) LogEvent
- func (lea *LogEventAdapter) Int64(key string, value int64) LogEvent
- func (lea *LogEventAdapter) Interface(key string, i any) LogEvent
- func (lea *LogEventAdapter) Msg(msg string)
- func (lea *LogEventAdapter) Msgf(format string, args ...any)
- func (lea *LogEventAdapter) Str(key, value string) LogEvent
- func (lea *LogEventAdapter) Uint64(key string, value uint64) LogEvent
- type Logger
- type OTelBridge
- type OTelProvider
- type Redactor
- type SensitiveDataFilter
- type ZeroLogger
- func (l *ZeroLogger) Debug() LogEvent
- func (l *ZeroLogger) Error() LogEvent
- func (l *ZeroLogger) Fatal() LogEvent
- func (l *ZeroLogger) Info() LogEvent
- func (l *ZeroLogger) Warn() LogEvent
- func (l *ZeroLogger) WithContext(ctx any) Logger
- func (l *ZeroLogger) WithFields(fields map[string]any) Logger
- func (l *ZeroLogger) WithOTelProvider(provider OTelProvider) *ZeroLogger
Constants ¶
const ( LevelTrace = "trace" LevelDebug = "debug" LevelInfo = "info" LevelWarn = "warn" LevelError = "error" LevelFatal = "fatal" LevelPanic = "panic" )
Log level string constants matching zerolog level names. Exported so other packages (server, app, config) can reuse the canonical level identifiers without redefining them.
const ( FieldCorrelationID = "correlation_id" FieldTraceID = "trace_id" FieldSpanID = "span_id" )
Identity field keys. Two identifier spaces share every log line and hold different values by design:
- FieldCorrelationID is the framework's own cross-service id: the inbound X-Request-ID when one arrived, else the trace id derived from a traceparent, else a UUID minted at the boundary. It is always present, travels on both messaging lanes' outcome lines, and survives with tracing switched off.
- FieldTraceID and FieldSpanID are the OpenTelemetry span identifiers of the span the line was written under. They appear only while a tracer provider is registered and never equal the correlation id.
Every stamping site uses these names so the key has one definition; the long-form rationale lives in wiki/observability.md ("Correlation Fields and Exemplars"). observability/dual_processor.go reads the OTel two as OTLP record attributes by literal to stay free of a logger import.
const ( // DefaultMaxDepth is the default maximum recursion depth for filtering DefaultMaxDepth = 8 // DefaultMaxPayloadBytes is the default ceiling on an opaque payload the // filter will parse. 64 KiB is comfortably above the request and response // bodies services log in practice and well below the size at which decoding // on the logging path becomes the expensive part of handling a request. // A payload above it is masked whole rather than shipped unread. DefaultMaxPayloadBytes = 64 * 1024 )
const ( FormatAuto = "auto" FormatConsole = "console" FormatJSON = "json" )
Log format constants used by config.OutputConfig.Format. "pretty" and "structured" are accepted aliases for "console" and "json".
const DefaultMaskValue = "***"
DefaultMaskValue is the value used to mask sensitive data.
Variables ¶
This section is empty.
Functions ¶
func AddAMQPElapsed ¶
AddAMQPElapsed adds elapsed nanoseconds to the AMQP elapsed time in the context
func AddDBElapsed ¶
AddDBElapsed adds elapsed nanoseconds to the database elapsed time in the context
func GetAMQPCounter ¶
GetAMQPCounter returns the current AMQP message count from the context
func GetAMQPElapsed ¶
GetAMQPElapsed returns the current AMQP elapsed time in nanoseconds from the context
func GetDBCounter ¶
GetDBCounter returns the current database operation count from the context
func GetDBElapsed ¶
GetDBElapsed returns the current database elapsed time in nanoseconds from the context
func IncrementAMQPCounter ¶
IncrementAMQPCounter increments the AMQP message counter in the context
func IncrementDBCounter ¶
IncrementDBCounter increments the database operation counter in the context
func ResolvePretty ¶ added in v0.32.0
ResolvePretty decides whether the logger should run in pretty (console) mode.
legacyPretty=true wins over format because users opting in via the older log.pretty boolean expect their preference to stick. Combined with otlpLogsActive=true it will panic at startup via WithOTelProvider — that fail-fast is intentional: silently muting the log shipping pipeline would be a worse outcome than a clear startup error.
func StdoutIsTerminal ¶ added in v0.32.0
func StdoutIsTerminal() bool
StdoutIsTerminal reports whether os.Stdout is attached to a terminal.
func WithAMQPCounter ¶
WithAMQPCounter seeds the shared per-request counters. Retained for backward compatibility; prefer WithRequestCounters (both seed the same struct).
func WithDBCounter ¶
WithDBCounter seeds the shared per-request counters. Retained for backward compatibility; prefer WithRequestCounters (both seed the same struct).
func WithRequestCounters ¶ added in v0.41.0
WithRequestCounters attaches the shared per-request counters struct (AMQP and DB operation counts and elapsed times) exactly once. It is idempotent — a second call returns the context unchanged, never resetting recorded values — and nil-safe (a nil context is returned as-is). This is the single seeder; a context seeded once exposes all four counters regardless of which name was used.
Types ¶
type FilterConfig ¶
type FilterConfig struct {
// SensitiveFields contains field names that should be masked in logs
SensitiveFields []string
// MaskValue is the value used to replace sensitive data (default: "***")
MaskValue string
// ErrorRedactor, when non-nil, replaces the message written by
// LogEvent.Err(err) with its return value. Field-name masking cannot see
// inside an error message, and the framework calls Err with
// consumer-authored errors at dozens of sites, so a consumer-side scrub
// helper never reaches those lines without this seam. Nil (the default,
// and what DefaultFilterConfig returns) keeps Err byte-identical to
// zerolog's own rendering. Code door only: app.Options.LoggerFilterConfig,
// which replaces the whole config — start from DefaultFilterConfig() and
// set this field. The YAML log.sensitivefields merge path leaves it nil,
// the value being a function.
ErrorRedactor func(error) string
// MaxPayloadBytes caps how large an opaque payload may be before the filter
// stops trying to read it. A JSON-LOOKING payload past the cap is masked
// whole — it is opaque and unread, which is the case the mask exists for.
// A payload that is not JSON-shaped passes through UNCHANGED whatever its
// size, since it was never going to be parsed; the cap still bounds the PEM
// header scan over it, so an oversized blob is not scanned end to end on
// the logging path. Parsing is the only way to
// see inside bytes, and parsing is linear in their size, so an unbounded
// cap would let one oversized payload — a bulk export, a base64 blob that
// happens to open with a brace — do arbitrary decode work on the logging
// path. Zero (the default, and what DefaultFilterConfig returns) means
// DefaultMaxPayloadBytes; a negative value disables the payload door
// entirely, leaving bytes and strings judged by NAME alone as they were
// before ADR-086.
MaxPayloadBytes int
}
FilterConfig defines the configuration for sensitive data filtering
func DefaultFilterConfig ¶
func DefaultFilterConfig() *FilterConfig
DefaultFilterConfig returns a default configuration with common sensitive field names
type LogEvent ¶
type LogEvent interface {
Msg(msg string)
Msgf(format string, args ...any)
Err(err error) LogEvent
Str(key, value string) LogEvent
Int(key string, value int) LogEvent
Int64(key string, value int64) LogEvent
Uint64(key string, value uint64) LogEvent
Dur(key string, d time.Duration) LogEvent
// Interface adds an arbitrary value. Rendering it can panic — the filter walks
// it by reflection and the encoder marshals it — so a call inside a defer that
// has already spent its recover() must wrap this in a nested guard, or the
// panic escapes that defer and skips whatever the handler had left to do.
Interface(key string, i any) LogEvent
Bytes(key string, val []byte) LogEvent
Bool(key string, value bool) LogEvent
// Enabled reports whether the event will actually be emitted at its level
// (i.e. not dropped by the logger's level or sampling). Callers can use it to
// skip building expensive fields when the event would be discarded.
Enabled() bool
}
LogEvent represents a structured log event that can be built with fields and sent. It provides methods for adding various field types and sending the final log message.
type LogEventAdapter ¶
type LogEventAdapter struct {
// contains filtered or unexported fields
}
LogEventAdapter adapts zerolog events to our logger interface
func (*LogEventAdapter) Bool ¶ added in v0.26.0
func (lea *LogEventAdapter) Bool(key string, value bool) LogEvent
Bool adds a boolean field to the log event
func (*LogEventAdapter) Bytes ¶
func (lea *LogEventAdapter) Bytes(key string, val []byte) LogEvent
Bytes adds a byte slice field to the log event.
A byte slice is an OPAQUE payload: the name filter sees one leaf called by this key, however many named fields the bytes carry of their own. So the payload goes through the filter rather than straight to the encoder, and what comes back decides the door — an untouched payload keeps zerolog's own Bytes rendering, while a masked one is written as the value the filter chose (#1133).
It calls the payload door DIRECTLY rather than through FilterValue, whose parameter is `any`: a slice header does not fit in an interface word, so boxing one costs an allocation on every byte-slice field logged — paid before the cheap not-JSON rejection inside the door has even run. The door answers without boxing on the untouched path, which is the common one.
func (*LogEventAdapter) Dur ¶
func (lea *LogEventAdapter) Dur(key string, d time.Duration) LogEvent
Dur adds a duration field to the log event
func (*LogEventAdapter) Enabled ¶ added in v0.41.0
func (lea *LogEventAdapter) Enabled() bool
Enabled reports whether the underlying zerolog event will be emitted. It is nil-safe: zerolog returns a nil *Event for disabled levels, and *Event.Enabled returns false on a nil receiver.
func (*LogEventAdapter) Err ¶
func (lea *LogEventAdapter) Err(err error) LogEvent
Err adds an error to the log event
func (*LogEventAdapter) Int ¶
func (lea *LogEventAdapter) Int(key string, value int) LogEvent
Int adds an integer field to the log event
func (*LogEventAdapter) Int64 ¶
func (lea *LogEventAdapter) Int64(key string, value int64) LogEvent
Int64 adds an int64 field to the log event
func (*LogEventAdapter) Interface ¶
func (lea *LogEventAdapter) Interface(key string, i any) LogEvent
Interface adds an any field to the log event
func (*LogEventAdapter) Msgf ¶
func (lea *LogEventAdapter) Msgf(format string, args ...any)
Msgf logs a formatted message
func (*LogEventAdapter) Str ¶
func (lea *LogEventAdapter) Str(key, value string) LogEvent
Str adds a string field to the log event
type Logger ¶
type Logger interface {
Info() LogEvent
Error() LogEvent
Debug() LogEvent
Warn() LogEvent
Fatal() LogEvent
WithContext(ctx any) Logger
WithFields(fields map[string]any) Logger
}
Logger defines the contract for structured logging throughout the application. It provides methods for creating log events at different severity levels and for contextual logging.
type OTelBridge ¶ added in v0.13.0
type OTelBridge struct {
// contains filtered or unexported fields
}
OTelBridge converts zerolog JSON output to OpenTelemetry log records. It implements io.Writer to intercept zerolog's output stream.
func NewOTelBridge ¶ added in v0.13.0
func NewOTelBridge(provider *sdklog.LoggerProvider) *OTelBridge
NewOTelBridge creates a new bridge that converts zerolog logs to OTel log records.
type OTelProvider ¶ added in v0.13.0
type OTelProvider interface {
// LoggerProvider returns the configured logger provider.
// Returns nil if logging is disabled.
LoggerProvider() *sdklog.LoggerProvider
// ShouldDisableStdout returns true if stdout should be disabled when OTLP is enabled.
// This method is implemented via type assertion to avoid exposing internal config.
ShouldDisableStdout() bool
}
OTelProvider is a minimal interface for accessing OpenTelemetry logger provider and configuration. This interface allows the logger package to integrate with observability without creating circular dependencies.
type Redactor ¶ added in v0.65.0
type Redactor interface {
RedactedForLog() any
}
Redactor is implemented by a value that knows its own log-safe shape. The filter logs RedactedForLog's result in place of the value, at every depth and through both Interface and WithFields, and still applies the needle list to that result. A value under a sensitive key is masked whole without calling it.
Implement it with a VALUE receiver: a pointer-receiver method leaves a bare value of the type unrecognized, so it would be walked field by field instead.
type SensitiveDataFilter ¶
type SensitiveDataFilter struct {
// contains filtered or unexported fields
}
SensitiveDataFilter filters sensitive data from logs. Filtering is enforced by the adapter layer (LogEventAdapter) — never add a Zerolog() accessor to ZeroLogger as that would create a bypass path around this filter boundary.
func NewSensitiveDataFilter ¶
func NewSensitiveDataFilter(config *FilterConfig) *SensitiveDataFilter
NewSensitiveDataFilter creates a new filter with the given configuration
func (*SensitiveDataFilter) FilterFields ¶
func (f *SensitiveDataFilter) FilterFields(fields map[string]any) map[string]any
FilterFields filters a map of fields for sensitive data
func (*SensitiveDataFilter) FilterString ¶
func (f *SensitiveDataFilter) FilterString(key, value string) string
FilterString filters sensitive data from string values
func (*SensitiveDataFilter) FilterValue ¶
func (f *SensitiveDataFilter) FilterValue(key string, value any) any
FilterValue filters sensitive data from any values
type ZeroLogger ¶
type ZeroLogger struct {
// contains filtered or unexported fields
}
ZeroLogger wraps zerolog.Logger to implement the Logger interface. It provides structured logging functionality with configurable output formatting.
func New ¶
func New(level string, pretty bool) *ZeroLogger
New creates a new ZeroLogger instance with the specified log level and formatting options. If pretty is true, output will be formatted for human readability.
func NewWithFilter ¶
func NewWithFilter(level string, pretty bool, filterConfig *FilterConfig) *ZeroLogger
NewWithFilter creates a new ZeroLogger instance with custom filter configuration. This allows applications to customize which fields are considered sensitive.
func (*ZeroLogger) Debug ¶
func (l *ZeroLogger) Debug() LogEvent
Debug creates a debug-level log event
func (*ZeroLogger) Error ¶
func (l *ZeroLogger) Error() LogEvent
func (*ZeroLogger) Fatal ¶
func (l *ZeroLogger) Fatal() LogEvent
Fatal creates a fatal-level log event
func (*ZeroLogger) Warn ¶
func (l *ZeroLogger) Warn() LogEvent
Warn creates a warning-level log event
func (*ZeroLogger) WithContext ¶
func (l *ZeroLogger) WithContext(ctx any) Logger
WithContext returns a logger with context information attached. It follows a two-phase approach for maximum flexibility:
Phase 1 (Explicit): If the context contains an explicit zerolog logger (set via zerolog.Ctx), that logger takes precedence. This maintains backward compatibility with existing code that uses zerolog's context pattern.
Phase 2 (Automatic): If no explicit logger is found, automatically extracts trace_id and span_id from the OpenTelemetry span context and adds them as fields. This enables automatic trace correlation without requiring explicit logger management in every handler.
This hybrid approach provides:
- Deterministic behavior: same context always produces same logger
- Backward compatibility: existing zerolog.Ctx usage continues to work
- Automatic correlation: trace IDs appear in logs without boilerplate
func (*ZeroLogger) WithFields ¶
func (l *ZeroLogger) WithFields(fields map[string]any) Logger
WithFields returns a logger with additional fields attached to all log entries.
func (*ZeroLogger) WithOTelProvider ¶ added in v0.13.0
func (l *ZeroLogger) WithOTelProvider(provider OTelProvider) *ZeroLogger
WithOTelProvider attaches an OpenTelemetry logger provider for OTLP log export. Returns the same logger if provider is nil/disabled, or creates a new logger with dual output (stdout + OTLP) or OTLP-only based on the provider's configuration.
IMPORTANT: OTLP export requires JSON mode (pretty=false). This method fails fast with a panic if pretty mode is active, ensuring configuration errors are caught during initialization rather than silently degrading observability.
Configuration conflict detection:
- If logger is created with pretty=true AND OTLP export is enabled, panics with clear error message directing user to fix their configuration.
Output modes:
- DisableStdout=false (default): logs go to both stdout and OTLP (useful for dev)
- DisableStdout=true: logs only go to OTLP (production efficiency)