Documentation
¶
Overview ¶
internal/logging/config.go
internal/logging/context.go
Package logging provides structured logging with OpenTelemetry integration.
Overview ¶
Logging package wraps Zap with:
- Custom Trace level (-2, below Debug)
- Dual output (stdout + OpenTelemetry)
- Automatic context field injection (trace_id, tenant, session)
- Defense-in-depth secret redaction
- Level-aware sampling (errors never sampled)
Usage ¶
Create logger from config:
cfg := logging.NewDefaultConfig()
logger, err := logging.NewLogger(cfg, otelProvider)
if err != nil {
log.Fatal(err)
}
defer logger.Sync()
Log with context:
ctx := logging.WithTenant(ctx, &logging.Tenant{OrgID: "acme"})
ctx = logging.WithSessionID(ctx, "sess_123")
logger.Info(ctx, "request processed", zap.Duration("duration", d))
Output includes automatic correlation:
{
"ts": "2025-11-24T10:15:30Z",
"level": "info",
"msg": "request processed",
"trace_id": "abc123",
"tenant.org": "acme",
"session.id": "sess_123",
"duration": "45ms"
}
Configuration Precedence ¶
Configuration follows standard contextd precedence:
- Defaults (NewDefaultConfig)
- File (config.yaml)
- Environment variables (CONTEXTD_LOGGING_*)
Secret Redaction ¶
Secrets are redacted at multiple layers:
- Domain primitives (config.Secret type)
- Encoder-level field name filtering
- Encoder-level pattern matching
Use helpers for manual redaction:
logger.Info(ctx, "auth received",
logging.RedactedString("authorization", authHeader))
Sampling ¶
Level-aware sampling prevents log floods:
- Trace: first 1 per second, drop rest
- Debug: first 10 per second, drop rest
- Info: first 100, then 1 every 10
- Warn: first 100, then 1 every 100
- Error+: never sampled
Disable for debugging:
cfg.Sampling.Enabled = false
Testing ¶
Use TestLogger for test assertions:
tl := logging.NewTestLogger()
tl.Info(ctx, "test message", zap.String("key", "value"))
tl.AssertLogged(t, zapcore.InfoLevel, "test message")
tl.AssertField(t, "test message", "key", "value")
tl.AssertNoSecrets(t)
Concurrency Safety ¶
Logger is safe for concurrent use. Child loggers (With, Named) are independent and do not affect parent or siblings.
Performance ¶
Logging overhead: <1ms per entry in hot paths Zero allocations when level disabled Sampling reduces volume by ~90% in high-throughput scenarios
internal/logging/levels.go
internal/logging/logger.go
internal/logging/otel.go
internal/logging/redact.go
internal/logging/sampling.go
internal/logging/testing.go
Index ¶
- Constants
- func ContextFields(ctx context.Context) []zap.Field
- func DefaultLevelSamplingConfig() map[zapcore.Level]LevelSamplingConfig
- func LevelFromString(level string) (zapcore.Level, error)
- func RedactedString(key, val string) zap.Field
- func RequestIDFromContext(ctx context.Context) string
- func Secret(key string, val config.Secret) zap.Field
- func SessionIDFromContext(ctx context.Context) string
- func WithLogger(ctx context.Context, logger *Logger) context.Context
- func WithRequestID(ctx context.Context, requestID string) context.Context
- func WithSessionID(ctx context.Context, sessionID string) context.Context
- func WithTenant(ctx context.Context, tenant *Tenant) context.Context
- type CallerConfig
- type Config
- type LevelSamplingConfig
- type Logger
- func (l *Logger) DPanic(ctx context.Context, msg string, fields ...zap.Field)
- func (l *Logger) Debug(ctx context.Context, msg string, fields ...zap.Field)
- func (l *Logger) Enabled(level zapcore.Level) bool
- func (l *Logger) Error(ctx context.Context, msg string, fields ...zap.Field)
- func (l *Logger) Fatal(ctx context.Context, msg string, fields ...zap.Field)
- func (l *Logger) Info(ctx context.Context, msg string, fields ...zap.Field)
- func (l *Logger) Named(name string) *Logger
- func (l *Logger) Sync() error
- func (l *Logger) Trace(ctx context.Context, msg string, fields ...zap.Field)
- func (l *Logger) Underlying() *zap.Logger
- func (l *Logger) Warn(ctx context.Context, msg string, fields ...zap.Field)
- func (l *Logger) With(fields ...zap.Field) *Logger
- type OutputConfig
- type RedactingEncoder
- func (e *RedactingEncoder) AddArray(key string, arr zapcore.ArrayMarshaler) error
- func (e *RedactingEncoder) AddBinary(key string, val []byte)
- func (e *RedactingEncoder) AddByteString(key string, val []byte)
- func (e *RedactingEncoder) AddObject(key string, obj zapcore.ObjectMarshaler) error
- func (e *RedactingEncoder) AddReflected(key string, val interface{}) error
- func (e *RedactingEncoder) AddString(key, val string)
- func (e *RedactingEncoder) Clone() zapcore.Encoder
- type RedactionConfig
- type SamplingConfig
- type StacktraceConfig
- type Tenant
- type TestLogger
- func (t *TestLogger) All() []observer.LoggedEntry
- func (t *TestLogger) AssertField(tb testing.TB, msg, key string, expected interface{})
- func (t *TestLogger) AssertLogged(tb testing.TB, level zapcore.Level, msgContains string)
- func (t *TestLogger) AssertNoSecrets(tb testing.TB)
- func (t *TestLogger) AssertNotLogged(tb testing.TB, level zapcore.Level, msgContains string)
- func (t *TestLogger) AssertTraceCorrelation(tb testing.TB, msg string)
- func (t *TestLogger) FilterMessage(msg string) *observer.ObservedLogs
- func (t *TestLogger) Reset()
Constants ¶
const TraceLevel = zapcore.Level(-2)
TraceLevel is a custom level below Debug for ultra-verbose logging. Value: -2 (Debug is -1, Info is 0)
Use for:
- Function entry/exit
- Wire protocol data
- Byte-level details
- Almost always filtered in production
Variables ¶
This section is empty.
Functions ¶
func ContextFields ¶
ContextFields extracts correlation data from context.
func DefaultLevelSamplingConfig ¶
func DefaultLevelSamplingConfig() map[zapcore.Level]LevelSamplingConfig
DefaultLevelSamplingConfig returns default sampling config by level.
func LevelFromString ¶
LevelFromString parses a string into a zapcore.Level, supporting "trace".
func RedactedString ¶
RedactedString creates a Zap field with redacted value and length.
func RequestIDFromContext ¶
RequestIDFromContext extracts request ID from context.
func SessionIDFromContext ¶
SessionIDFromContext extracts session ID from context.
func WithLogger ¶
WithLogger stores logger in context.
func WithRequestID ¶
WithRequestID adds request ID to context. Panics if requestID is empty or contains invalid characters.
func WithSessionID ¶
WithSessionID adds session ID to context. Panics if sessionID is empty or contains invalid characters.
Types ¶
type CallerConfig ¶
CallerConfig controls caller information in logs.
type Config ¶
type Config struct {
Level zapcore.Level `koanf:"level"`
Format string `koanf:"format"`
Output OutputConfig `koanf:"output"`
Sampling SamplingConfig `koanf:"sampling"`
Caller CallerConfig `koanf:"caller"`
Stacktrace StacktraceConfig `koanf:"stacktrace"`
Fields map[string]string `koanf:"fields"`
Redaction RedactionConfig `koanf:"redaction"`
}
Config holds logging configuration.
func NewDefaultConfig ¶
func NewDefaultConfig() *Config
NewDefaultConfig returns config with production-ready defaults.
type LevelSamplingConfig ¶
type LevelSamplingConfig struct {
Initial int `koanf:"initial"`
Thereafter int `koanf:"thereafter"`
}
LevelSamplingConfig defines sampling rate per level.
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger wraps Zap with context-aware methods.
func FromContext ¶
FromContext retrieves logger from context. Returns a default nop logger if not found.
func NewLogger ¶
func NewLogger(cfg *Config, otelProvider log.LoggerProvider) (*Logger, error)
NewLogger creates a logger from config. otelProvider can be nil to disable OTEL output.
func (*Logger) Underlying ¶
Underlying returns the underlying zap.Logger. Useful when integrating with libraries that require a *zap.Logger.
type OutputConfig ¶
OutputConfig controls where logs are written.
type RedactingEncoder ¶
RedactingEncoder wraps a zapcore.Encoder to redact sensitive fields.
func NewRedactingEncoder ¶
func NewRedactingEncoder(base zapcore.Encoder, cfg RedactionConfig) (*RedactingEncoder, error)
NewRedactingEncoder wraps an encoder with redaction rules. Returns error if any redaction pattern fails to compile.
func (*RedactingEncoder) AddArray ¶
func (e *RedactingEncoder) AddArray(key string, arr zapcore.ArrayMarshaler) error
AddArray redacts sensitive field names.
func (*RedactingEncoder) AddBinary ¶
func (e *RedactingEncoder) AddBinary(key string, val []byte)
AddBinary redacts sensitive field names.
func (*RedactingEncoder) AddByteString ¶
func (e *RedactingEncoder) AddByteString(key string, val []byte)
AddByteString redacts sensitive field names.
func (*RedactingEncoder) AddObject ¶
func (e *RedactingEncoder) AddObject(key string, obj zapcore.ObjectMarshaler) error
AddObject redacts sensitive field names.
func (*RedactingEncoder) AddReflected ¶
func (e *RedactingEncoder) AddReflected(key string, val interface{}) error
AddReflected redacts sensitive field names. Note: This redacts the entire reflected value if the key is sensitive. For deep inspection of reflected structs/maps, use explicit zap.Object() with custom marshalers.
func (*RedactingEncoder) AddString ¶
func (e *RedactingEncoder) AddString(key, val string)
AddString redacts sensitive field names and value patterns.
func (*RedactingEncoder) Clone ¶
func (e *RedactingEncoder) Clone() zapcore.Encoder
Clone creates a copy of the encoder.
type RedactionConfig ¶
type RedactionConfig struct {
Enabled bool `koanf:"enabled"`
Fields []string `koanf:"fields"`
Patterns []string `koanf:"patterns"`
}
RedactionConfig controls sensitive data redaction.
type SamplingConfig ¶
type SamplingConfig struct {
Enabled bool `koanf:"enabled"`
Tick config.Duration `koanf:"tick"`
Levels map[zapcore.Level]LevelSamplingConfig `koanf:"levels"`
}
SamplingConfig controls log volume reduction.
type StacktraceConfig ¶
StacktraceConfig controls stacktrace inclusion.
type Tenant ¶
Tenant represents multi-tenant context.
func TenantFromContext ¶
TenantFromContext extracts tenant from context.
type TestLogger ¶
type TestLogger struct {
*Logger
// contains filtered or unexported fields
}
TestLogger wraps Logger with test observation capabilities.
func NewTestLogger ¶
func NewTestLogger() *TestLogger
NewTestLogger creates a logger for testing with full observation.
func (*TestLogger) All ¶
func (t *TestLogger) All() []observer.LoggedEntry
All returns all logged entries.
func (*TestLogger) AssertField ¶
func (t *TestLogger) AssertField(tb testing.TB, msg, key string, expected interface{})
AssertField verifies a field with key and value exists in message.
func (*TestLogger) AssertLogged ¶
AssertLogged verifies a log at level containing message was logged.
func (*TestLogger) AssertNoSecrets ¶
func (t *TestLogger) AssertNoSecrets(tb testing.TB)
AssertNoSecrets verifies no sensitive data leaked in logs.
func (*TestLogger) AssertNotLogged ¶
AssertNotLogged verifies no log at level containing message was logged.
func (*TestLogger) AssertTraceCorrelation ¶
func (t *TestLogger) AssertTraceCorrelation(tb testing.TB, msg string)
AssertTraceCorrelation verifies trace_id present in message.
func (*TestLogger) FilterMessage ¶
func (t *TestLogger) FilterMessage(msg string) *observer.ObservedLogs
FilterMessage returns entries matching message substring.