1 - API Reference
Complete API reference for all types and methods in the logging package
Complete API reference for all public types and methods in the logging package.
Core Functions
New
func New(opts ...Option) (*Logger, error)
Creates a new Logger with the given options. Returns an error if configuration is invalid.
Parameters:
opts - Variadic list of configuration options.
Returns:
*Logger - Configured logger instance.error - Configuration error, if any.
Example:
logger, err := logging.New(
logging.WithJSONHandler(),
logging.WithLevel(logging.LevelInfo),
)
if err != nil {
log.Fatalf("failed to create logger: %v", err)
}
MustNew
func MustNew(opts ...Option) *Logger
Creates a new Logger or panics on error. Use for initialization where errors are fatal.
Parameters:
opts - Variadic list of configuration options
Returns:
*Logger - Configured logger instance
Panics:
- If configuration is invalid
Example:
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithLevel(logging.LevelInfo),
)
NewContextLogger
func NewContextLogger(ctx context.Context, logger *Logger) *ContextLogger
Creates a context-aware logger that automatically extracts trace and span IDs from OpenTelemetry context.
Parameters:
ctx - Context to extract trace information from.logger - Base Logger instance.
Returns:
*ContextLogger - Context-aware logger.
Example:
cl := logging.NewContextLogger(ctx, logger)
cl.Info("processing request") // Includes trace_id and span_id
Logger Type
Logging Methods
Debug
func (l *Logger) Debug(msg string, args ...any)
Logs a debug message with structured attributes.
Parameters:
msg - Log messageargs - Key-value pairs (must be even number of arguments)
Example:
logger.Debug("cache lookup", "key", cacheKey, "hit", true)
Info
func (l *Logger) Info(msg string, args ...any)
Logs an informational message with structured attributes.
Parameters:
msg - Log messageargs - Key-value pairs
Example:
logger.Info("server started", "port", 8080, "version", "v1.0.0")
Warn
func (l *Logger) Warn(msg string, args ...any)
Logs a warning message with structured attributes.
Parameters:
msg - Log messageargs - Key-value pairs
Example:
logger.Warn("high memory usage", "used_mb", 8192, "threshold_mb", 10240)
Error
func (l *Logger) Error(msg string, args ...any)
Logs an error message with structured attributes. Errors bypass log sampling.
Parameters:
msg - Log messageargs - Key-value pairs
Example:
logger.Error("database connection failed", "error", err, "retry_count", 3)
Convenience Methods
LogRequest
func (l *Logger) LogRequest(r *http.Request, extra ...any)
Logs an HTTP request with standard fields (method, path, remote, user_agent, query).
Parameters:
r - HTTP requestextra - Additional key-value pairs
Example:
logger.LogRequest(r, "status", 200, "duration_ms", 45)
LogError
func (l *Logger) LogError(err error, msg string, extra ...any)
Logs an error with automatic error field.
Parameters:
err - Error to logmsg - Log messageextra - Additional key-value pairs
Example:
logger.LogError(err, "operation failed", "operation", "INSERT", "table", "users")
LogDuration
func (l *Logger) LogDuration(msg string, start time.Time, extra ...any)
Logs operation duration with automatic duration_ms and duration fields.
Parameters:
msg - Log messagestart - Operation start timeextra - Additional key-value pairs
Example:
start := time.Now()
// ... operation ...
logger.LogDuration("processing completed", start, "items", 100)
ErrorWithStack
func (l *Logger) ErrorWithStack(msg string, err error, includeStack bool, extra ...any)
Logs an error with optional stack trace.
Parameters:
msg - Log messageerr - Error to logincludeStack - Whether to capture and include stack traceextra - Additional key-value pairs
Example:
logger.ErrorWithStack("critical failure", err, true, "user_id", userID)
Context Methods
Logger
func (l *Logger) Logger() *slog.Logger
Returns the underlying slog.Logger for advanced usage.
Returns:
*slog.Logger - Underlying logger
Example:
slogger := logger.Logger()
With
func (l *Logger) With(args ...any) *slog.Logger
Returns a slog.Logger with additional attributes that persist across log calls.
Parameters:
args - Key-value pairs to add as persistent attributes
Returns:
*slog.Logger - Logger with added attributes
Example:
requestLogger := logger.With("request_id", "req-123", "user_id", "user-456")
requestLogger.Info("processing") // Includes request_id and user_id
WithGroup
func (l *Logger) WithGroup(name string) *slog.Logger
Returns a slog.Logger with a group name for nested attributes.
Parameters:
Returns:
*slog.Logger - Logger with group
Example:
dbLogger := logger.WithGroup("database")
dbLogger.Info("query", "sql", "SELECT * FROM users")
// Output: {...,"database":{"sql":"SELECT * FROM users"}}
Configuration Methods
SetLevel
func (l *Logger) SetLevel(level Level) error
Dynamically changes the minimum log level at runtime.
Parameters:
Returns:
error - ErrCannotChangeLevel if using custom logger
Example:
if err := logger.SetLevel(logging.LevelDebug); err != nil {
log.Printf("failed to change level: %v", err)
}
Level
func (l *Logger) Level() Level
Returns the current minimum log level.
Returns:
Level - Current log level
Example:
currentLevel := logger.Level()
fmt.Printf("Current level: %s\n", currentLevel)
ServiceName
func (l *Logger) ServiceName() string
Returns the configured service name.
Returns:
string - Service name, or empty if not configured
ServiceVersion
func (l *Logger) ServiceVersion() string
Returns the configured service version.
Returns:
string - Service version, or empty if not configured
Environment
func (l *Logger) Environment() string
Returns the configured environment.
Returns:
string - Environment, or empty if not configured
Lifecycle Methods
IsEnabled
func (l *Logger) IsEnabled() bool
Returns true if logging is enabled (not shut down).
Returns:
bool - Whether logger is active
DebugInfo
func (l *Logger) DebugInfo() map[string]any
Returns diagnostic information about logger state.
Returns:
map[string]any - Diagnostic information
Example:
info := logger.DebugInfo()
fmt.Printf("Handler: %s\n", info["handler_type"])
fmt.Printf("Level: %s\n", info["level"])
Shutdown
func (l *Logger) Shutdown(ctx context.Context) error
Gracefully shuts down the logger, flushing any buffered logs.
Parameters:
ctx - Context for timeout control
Returns:
error - Shutdown error, if any
Example:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := logger.Shutdown(ctx); err != nil {
fmt.Fprintf(os.Stderr, "shutdown error: %v\n", err)
}
ContextLogger Type
Logging Methods
Debug
func (cl *ContextLogger) Debug(msg string, args ...any)
Logs a debug message with automatic trace correlation.
Info
func (cl *ContextLogger) Info(msg string, args ...any)
Logs an info message with automatic trace correlation.
Warn
func (cl *ContextLogger) Warn(msg string, args ...any)
Logs a warning message with automatic trace correlation.
Error
func (cl *ContextLogger) Error(msg string, args ...any)
Logs an error message with automatic trace correlation.
Context Methods
Logger
func (cl *ContextLogger) Logger() *slog.Logger
Returns the underlying slog.Logger.
Returns:
*slog.Logger - Underlying logger
TraceID
func (cl *ContextLogger) TraceID() string
Returns the trace ID if available.
Returns:
string - Trace ID, or empty if not available
Example:
if traceID := cl.TraceID(); traceID != "" {
fmt.Printf("Trace ID: %s\n", traceID)
}
SpanID
func (cl *ContextLogger) SpanID() string
Returns the span ID if available.
Returns:
string - Span ID, or empty if not available
With
func (cl *ContextLogger) With(args ...any) *slog.Logger
Returns a slog.Logger with additional attributes.
Parameters:
Returns:
*slog.Logger - Logger with added attributes
Next Steps
For usage guides, see the Logging Guide.
2 - Options Reference
Complete reference for all logger configuration options
Complete reference for all configuration options available in the logging package.
Handler Options
Configure the output format for logs.
WithHandlerType
func WithHandlerType(t HandlerType) Option
Sets the logging handler type directly.
Parameters:
t - Handler type. Use JSONHandler, TextHandler, or ConsoleHandler.
Example:
logging.WithHandlerType(logging.JSONHandler)
WithJSONHandler
func WithJSONHandler() Option
Uses JSON structured logging. This is the default. Best for production and log aggregation.
Example:
logger := logging.MustNew(logging.WithJSONHandler())
Output format:
{"time":"2024-01-15T10:30:45.123Z","level":"INFO","msg":"test","key":"value"}
WithTextHandler
func WithTextHandler() Option
Uses text key=value logging. Good for systems that prefer this format.
Example:
logger := logging.MustNew(logging.WithTextHandler())
Output format:
time=2024-01-15T10:30:45.123Z level=INFO msg=test key=value
WithConsoleHandler
func WithConsoleHandler() Option
Uses human-readable console logging with colors. Best for development.
Example:
logger := logging.MustNew(logging.WithConsoleHandler())
Output format:
10:30:45.123 INFO test key=value
Level Options
Configure the minimum log level.
WithLevel
func WithLevel(level Level) Option
Sets the minimum log level.
Parameters:
level - Minimum level (LevelDebug, LevelInfo, LevelWarn, LevelError)
Example:
logger := logging.MustNew(
logging.WithLevel(logging.LevelInfo),
)
WithDebugLevel
func WithDebugLevel() Option
Convenience function to enable debug logging. Equivalent to WithLevel(LevelDebug).
Example:
logger := logging.MustNew(logging.WithDebugLevel())
Output Options
Configure where logs are written.
WithOutput
func WithOutput(w io.Writer) Option
Sets the output destination for logs.
Parameters:
w - io.Writer to write logs to
Default: os.Stdout
Example:
logFile, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
logger := logging.MustNew(
logging.WithOutput(logFile),
)
Multiple outputs:
logger := logging.MustNew(
logging.WithOutput(io.MultiWriter(os.Stdout, logFile)),
)
Configure service identification fields automatically added to every log entry.
WithServiceName
func WithServiceName(name string) Option
Sets the service name, automatically added to all log entries as service field.
Parameters:
Example:
logger := logging.MustNew(
logging.WithServiceName("payment-api"),
)
WithServiceVersion
func WithServiceVersion(version string) Option
Sets the service version, automatically added to all log entries as version field.
Parameters:
version - Service version
Example:
logger := logging.MustNew(
logging.WithServiceVersion("v2.1.0"),
)
WithEnvironment
func WithEnvironment(env string) Option
Sets the environment, automatically added to all log entries as env field.
Parameters:
Example:
logger := logging.MustNew(
logging.WithEnvironment("production"),
)
Combined example:
logger := logging.MustNew(
logging.WithServiceName("payment-api"),
logging.WithServiceVersion("v2.1.0"),
logging.WithEnvironment("production"),
)
// All logs include: "service":"payment-api","version":"v2.1.0","env":"production"
Feature Options
Enable additional logging features.
WithSource
func WithSource(enabled bool) Option
Enables source code location (file and line number) in logs.
Parameters:
enabled - Whether to include source location
Default: false
Example:
logger := logging.MustNew(
logging.WithSource(true),
)
// Output includes: "source":{"file":"main.go","line":42}
Note: Source location adds overhead. Use only for debugging.
WithDebugMode
func WithDebugMode(enabled bool) Option
Enables verbose debugging mode. Automatically enables debug level and source location.
Parameters:
enabled - Whether to enable debug mode
Example:
logger := logging.MustNew(
logging.WithDebugMode(true),
)
// Equivalent to:
// WithDebugLevel() + WithSource(true)
WithGlobalLogger
func WithGlobalLogger() Option
Registers this logger as the global slog default logger. Allows third-party libraries using slog to use your configured logger.
Example:
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithGlobalLogger(),
)
// Now slog.Info() uses this logger
Default: Not registered globally (allows multiple independent loggers)
WithSampling
func WithSampling(cfg SamplingConfig) Option
Enables log sampling to reduce volume in high-traffic scenarios.
Parameters:
cfg - Sampling configuration
Example:
logger := logging.MustNew(
logging.WithSampling(logging.SamplingConfig{
Initial: 1000, // First 1000 logs
Thereafter: 100, // Then 1% sampling
Tick: time.Minute, // Reset every minute
}),
)
SamplingConfig fields:
Initial (int) - Log first N entries unconditionallyThereafter (int) - After Initial, log 1 of every M entries (0 = log all)Tick (time.Duration) - Reset counter every interval (0 = never reset)
Note: Errors (level >= ERROR) always bypass sampling.
Advanced Options
Advanced configuration for specialized use cases.
WithReplaceAttr
func WithReplaceAttr(fn func(groups []string, a slog.Attr) slog.Attr) Option
Sets a custom attribute replacer function for transforming or filtering log attributes.
Parameters:
fn - Function to transform attributes
Example - Custom redaction:
logger := logging.MustNew(
logging.WithReplaceAttr(func(groups []string, a slog.Attr) slog.Attr {
if a.Key == "credit_card" {
return slog.String(a.Key, "***REDACTED***")
}
return a
}),
)
Example - Dropping attributes:
logger := logging.MustNew(
logging.WithReplaceAttr(func(groups []string, a slog.Attr) slog.Attr {
if a.Key == "internal_field" {
return slog.Attr{} // Drop this field
}
return a
}),
)
Example - Transforming values:
logger := logging.MustNew(
logging.WithReplaceAttr(func(groups []string, a slog.Attr) slog.Attr {
if a.Key == "time" {
if t, ok := a.Value.Any().(time.Time); ok {
return slog.String(a.Key, t.Format(time.RFC3339))
}
}
return a
}),
)
WithCustomLogger
func WithCustomLogger(customLogger *slog.Logger) Option
Uses a custom slog.Logger instead of creating one. For advanced use cases where you need full control over the logger.
Parameters:
customLogger - Pre-configured slog.Logger
Example:
customLogger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
AddSource: true,
}))
logger := logging.MustNew(
logging.WithCustomLogger(customLogger),
)
Limitations:
- Dynamic level changes (
SetLevel) not supported - Service metadata must be added to custom logger directly
Configuration Examples
Development Configuration
logger := logging.MustNew(
logging.WithConsoleHandler(),
logging.WithDebugLevel(),
logging.WithSource(true),
)
Production Configuration
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithLevel(logging.LevelInfo),
logging.WithServiceName(os.Getenv("SERVICE_NAME")),
logging.WithServiceVersion(os.Getenv("VERSION")),
logging.WithEnvironment("production"),
logging.WithSampling(logging.SamplingConfig{
Initial: 1000,
Thereafter: 100,
Tick: time.Minute,
}),
)
Testing Configuration
buf := &bytes.Buffer{}
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithOutput(buf),
logging.WithLevel(logging.LevelDebug),
)
File Logging Configuration
logFile, _ := os.OpenFile("app.log",
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithOutput(logFile),
logging.WithServiceName("myapp"),
)
Multiple Output Configuration
logFile, _ := os.OpenFile("app.log",
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithOutput(io.MultiWriter(os.Stdout, logFile)),
)
Next Steps
For usage guides, see the Configuration Guide.
3 - Testing Utilities
Complete reference for logging test utilities and helpers
Complete reference for testing utilities provided by the logging package.
Test Logger Creation
NewTestLogger
func NewTestLogger() (*Logger, *bytes.Buffer)
Creates a Logger for testing with an in-memory buffer. The logger is configured with JSON handler, debug level, and writes to the returned buffer.
Returns:
*Logger - Configured test logger*bytes.Buffer - Buffer containing log output
Example:
func TestMyFunction(t *testing.T) {
logger, buf := logging.NewTestLogger()
myFunction(logger)
entries, err := logging.ParseJSONLogEntries(buf)
require.NoError(t, err)
assert.Len(t, entries, 1)
}
ParseJSONLogEntries
func ParseJSONLogEntries(buf *bytes.Buffer) ([]LogEntry, error)
Parses JSON log entries from buffer into LogEntry slices. Creates a copy of the buffer so the original is not consumed.
Parameters:
buf - Buffer containing JSON log entries (one per line)
Returns:
[]LogEntry - Parsed log entrieserror - Parse error, if any
Example:
entries, err := logging.ParseJSONLogEntries(buf)
require.NoError(t, err)
for _, entry := range entries {
fmt.Printf("%s: %s\n", entry.Level, entry.Message)
}
TestHelper
High-level testing utility with convenience methods.
NewTestHelper
func NewTestHelper(t *testing.T, opts ...Option) *TestHelper
Creates a TestHelper with in-memory logging and additional options.
Parameters:
t - Testing instanceopts - Optional configuration options
Returns:
*TestHelper - Test helper instance
Example:
func TestService(t *testing.T) {
th := logging.NewTestHelper(t)
svc := NewService(th.Logger)
svc.DoSomething()
th.AssertLog(t, "INFO", "operation completed", map[string]any{
"status": "success",
})
}
With custom configuration:
th := logging.NewTestHelper(t,
logging.WithLevel(logging.LevelWarn), // Only warnings and errors
)
TestHelper.Logs
func (th *TestHelper) Logs() ([]LogEntry, error)
Returns all parsed log entries.
Returns:
[]LogEntry - All log entrieserror - Parse error, if any
Example:
logs, err := th.Logs()
require.NoError(t, err)
assert.Len(t, logs, 3)
TestHelper.LastLog
func (th *TestHelper) LastLog() (*LogEntry, error)
Returns the most recent log entry.
Returns:
*LogEntry - Most recent log entryerror - Error if no logs or parse error
Example:
last, err := th.LastLog()
require.NoError(t, err)
assert.Equal(t, "INFO", last.Level)
TestHelper.ContainsLog
func (th *TestHelper) ContainsLog(msg string) bool
Checks if any log entry contains the given message.
Parameters:
msg - Message to search for
Returns:
bool - True if message found
Example:
if !th.ContainsLog("user created") {
t.Error("expected user created log")
}
TestHelper.ContainsAttr
func (th *TestHelper) ContainsAttr(key string, value any) bool
Checks if any log entry contains the given attribute.
Parameters:
key - Attribute keyvalue - Attribute value
Returns:
bool - True if attribute found
Example:
if !th.ContainsAttr("user_id", "123") {
t.Error("expected user_id attribute")
}
TestHelper.CountLevel
func (th *TestHelper) CountLevel(level string) int
Returns the number of log entries at the given level.
Parameters:
level - Log level (“DEBUG”, “INFO”, “WARN”, “ERROR”)
Returns:
int - Count of logs at that level
Example:
errorCount := th.CountLevel("ERROR")
assert.Equal(t, 2, errorCount)
TestHelper.Reset
func (th *TestHelper) Reset()
Clears the buffer for fresh testing.
Example:
th.Reset() // Start fresh for next test phase
TestHelper.AssertLog
func (th *TestHelper) AssertLog(t *testing.T, level, msg string, attrs map[string]any)
Checks that a log entry exists with the given properties. Fails the test if not found.
Parameters:
t - Testing instancelevel - Expected log levelmsg - Expected messageattrs - Expected attributes
Example:
th.AssertLog(t, "INFO", "user created", map[string]any{
"username": "alice",
"email": "alice@example.com",
})
LogEntry Type
type LogEntry struct {
Time time.Time
Level string
Message string
Attrs map[string]any
}
Represents a parsed log entry for testing.
Fields:
Time - Log timestampLevel - Log level (“DEBUG”, “INFO”, “WARN”, “ERROR”)Message - Log messageAttrs - All other fields as map
Example:
entry := logs[0]
assert.Equal(t, "INFO", entry.Level)
assert.Equal(t, "test message", entry.Message)
assert.Equal(t, "value", entry.Attrs["key"])
Mock Writers
MockWriter
Records all writes for inspection.
Type:
type MockWriter struct {
// contains filtered or unexported fields
}
Methods:
Write
func (mw *MockWriter) Write(p []byte) (n int, err error)
Implements io.Writer. Records the write.
WriteCount
func (mw *MockWriter) WriteCount() int
Returns the number of write calls.
BytesWritten
func (mw *MockWriter) BytesWritten() int
Returns total bytes written.
LastWrite
func (mw *MockWriter) LastWrite() []byte
Returns the most recent write.
Reset
func (mw *MockWriter) Reset()
Clears all recorded writes.
Example:
func TestWriteBehavior(t *testing.T) {
mw := &logging.MockWriter{}
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithOutput(mw),
)
logger.Info("test 1")
logger.Info("test 2")
logger.Info("test 3")
assert.Equal(t, 3, mw.WriteCount())
assert.Contains(t, string(mw.LastWrite()), "test 3")
assert.Greater(t, mw.BytesWritten(), 0)
}
CountingWriter
Counts bytes written without storing content.
Type:
type CountingWriter struct {
// contains filtered or unexported fields
}
Methods:
Write
func (cw *CountingWriter) Write(p []byte) (n int, err error)
Implements io.Writer. Counts bytes.
Count
func (cw *CountingWriter) Count() int64
Returns the total bytes written.
Example:
func TestLogVolume(t *testing.T) {
cw := &logging.CountingWriter{}
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithOutput(cw),
)
for i := 0; i < 1000; i++ {
logger.Info("test message", "index", i)
}
bytesLogged := cw.Count()
t.Logf("Total bytes: %d", bytesLogged)
}
SlowWriter
Simulates slow I/O for testing timeouts.
Type:
type SlowWriter struct {
// contains filtered or unexported fields
}
Constructor:
NewSlowWriter
func NewSlowWriter(delay time.Duration, inner io.Writer) *SlowWriter
Creates a writer that delays each write.
Parameters:
delay - Delay duration for each writeinner - Optional inner writer to actually write to
Returns:
*SlowWriter - Slow writer instance
Example:
func TestSlowLogging(t *testing.T) {
buf := &bytes.Buffer{}
sw := logging.NewSlowWriter(100*time.Millisecond, buf)
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithOutput(sw),
)
start := time.Now()
logger.Info("test")
duration := time.Since(start)
assert.GreaterOrEqual(t, duration, 100*time.Millisecond)
}
HandlerSpy
Implements slog.Handler and records all Handle calls.
Type:
type HandlerSpy struct {
// contains filtered or unexported fields
}
Methods:
Enabled
func (hs *HandlerSpy) Enabled(_ context.Context, _ slog.Level) bool
Always returns true.
Handle
func (hs *HandlerSpy) Handle(_ context.Context, r slog.Record) error
Records the log record.
WithAttrs
func (hs *HandlerSpy) WithAttrs(_ []slog.Attr) slog.Handler
Returns the same handler (for compatibility).
WithGroup
func (hs *HandlerSpy) WithGroup(_ string) slog.Handler
Returns the same handler (for compatibility).
Records
func (hs *HandlerSpy) Records() []slog.Record
Returns all captured records.
RecordCount
func (hs *HandlerSpy) RecordCount() int
Returns the number of captured records.
Reset
func (hs *HandlerSpy) Reset()
Clears all captured records.
Example:
func TestHandlerBehavior(t *testing.T) {
spy := &logging.HandlerSpy{}
logger := slog.New(spy)
logger.Info("test", "key", "value")
assert.Equal(t, 1, spy.RecordCount())
records := spy.Records()
assert.Equal(t, "test", records[0].Message)
}
Testing Patterns
Testing Error Logging
func TestErrorHandling(t *testing.T) {
th := logging.NewTestHelper(t)
svc := NewService(th.Logger)
err := svc.DoSomethingThatFails()
require.Error(t, err)
th.AssertLog(t, "ERROR", "operation failed", map[string]any{
"error": "expected failure",
})
}
Table-Driven Tests
func TestLogLevels(t *testing.T) {
tests := []struct {
name string
level logging.Level
expectLogged bool
}{
{"debug at info", logging.LevelInfo, false},
{"info at info", logging.LevelInfo, true},
{"error at warn", logging.LevelWarn, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
th := logging.NewTestHelper(t,
logging.WithLevel(tt.level),
)
th.Logger.Debug("test")
logs, _ := th.Logs()
if tt.expectLogged {
assert.Len(t, logs, 1)
} else {
assert.Len(t, logs, 0)
}
})
}
}
Next Steps
For complete testing patterns, see the Testing Guide.
4 - Troubleshooting
Common issues and solutions for the logging package
Common issues and solutions when using the logging package.
Logs Not Appearing
Debug Logs Not Showing
Problem: Debug logs don’t appear in output.
Cause: Log level is set higher than Debug.
Solution: Enable debug level:
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithDebugLevel(), // Enable debug logs
)
Or check current level:
currentLevel := logger.Level()
fmt.Printf("Current level: %s\n", currentLevel)
No Logs at All
Problem: No logs appear, even errors.
Possible causes:
- Logger shutdown: Check if logger was shut down.
if !logger.IsEnabled() {
fmt.Println("Logger is shut down")
}
- Wrong output: Verify output destination.
logger := logging.MustNew(
logging.WithOutput(os.Stdout), // Not stderr
)
- Sampling too aggressive: Check sampling configuration.
info := logger.DebugInfo()
if sampling, ok := info["sampling"]; ok {
fmt.Printf("Sampling: %+v\n", sampling)
}
Logs Disappear After Some Time
Problem: Logs stop appearing after initial burst.
Cause: Log sampling is dropping logs.
Solution: Adjust sampling or disable:
// Less aggressive sampling
logger := logging.MustNew(
logging.WithSampling(logging.SamplingConfig{
Initial: 1000,
Thereafter: 10, // 10% instead of 1%
Tick: time.Minute,
}),
)
// Or disable sampling
logger := logging.MustNew(
logging.WithJSONHandler(),
// No WithSampling() call
)
Sensitive Data Issues
Sensitive Data Not Redacted
Problem: Custom sensitive fields not being redacted.
Cause: Only built-in fields are automatically redacted.
Solution: Add custom redaction:
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithReplaceAttr(func(groups []string, a slog.Attr) slog.Attr {
// Redact custom fields
if a.Key == "credit_card" || a.Key == "ssn" {
return slog.String(a.Key, "***REDACTED***")
}
return a
}),
)
Built-in redacted fields:
passwordtokensecretapi_keyauthorization
Too Much Redaction
Problem: Fields being redacted unnecessarily.
Cause: Field names match redaction patterns.
Solution: Rename fields to avoid keywords:
// Instead of "token" (redacted)
log.Info("processing", "request_token_id", tokenID)
// Instead of "secret" (redacted)
log.Info("config", "shared_secret_name", secretName)
Trace Correlation Issues
No Trace IDs in Logs
Problem: Logs don’t include trace_id and span_id.
Possible causes:
- Tracing not initialized:
// Initialize tracing
tracer := tracing.MustNew(
tracing.WithOTLP("localhost:4317"),
)
defer tracer.Shutdown(context.Background())
- Not using ContextLogger:
// Wrong - plain logger
logger.Info("message")
// Right - context logger
cl := logging.NewContextLogger(ctx, logger)
cl.Info("message") // Includes trace_id and span_id
- Context has no active span:
// Start a span
ctx, span := tracer.Start(context.Background(), "operation")
defer span.End()
cl := logging.NewContextLogger(ctx, logger)
cl.Info("message") // Now includes trace IDs
Wrong Trace IDs
Problem: Trace IDs don’t match distributed trace.
Cause: Context not properly propagated.
Solution: Ensure context flows through call chain:
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // Get context with trace
// Pass context down
result := processRequest(ctx)
w.Write(result)
}
func processRequest(ctx context.Context) []byte {
// Use context
cl := logging.NewContextLogger(ctx, logger)
cl.Info("processing")
return data
}
High CPU Usage
Problem: Logging causes high CPU usage.
Possible causes:
- Logging in tight loops:
// Bad - logs thousands of times
for _, item := range items {
logger.Debug("processing", "item", item)
}
// Good - log summary
logger.Info("processing batch", "count", len(items))
- Source location enabled in production:
// Bad for production
logger := logging.MustNew(
logging.WithSource(true), // Adds overhead
)
// Good for production
logger := logging.MustNew(
logging.WithJSONHandler(),
// No source location
)
- Debug level in production:
// Bad - debug logs have overhead even if filtered
logger := logging.MustNew(
logging.WithDebugLevel(),
)
// Good - appropriate level
logger := logging.MustNew(
logging.WithLevel(logging.LevelInfo),
)
High Memory Usage
Problem: Memory usage grows over time.
Possible causes:
- No log rotation: Logs written to file without rotation.
Solution: Use external log rotation (logrotate) or rotate in code:
// Use external tool like logrotate
// Or implement rotation
- Buffered output not flushed: Buffers growing without flush.
Solution: Ensure proper shutdown:
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
logger.Shutdown(ctx)
}()
Configuration Issues
Cannot Change Log Level
Problem: SetLevel returns error.
Cause: Using custom logger.
Error:
err := logger.SetLevel(logging.LevelDebug)
if errors.Is(err, logging.ErrCannotChangeLevel) {
// Custom logger doesn't support dynamic level changes
}
Solution: Control level in custom logger:
var levelVar slog.LevelVar
levelVar.Set(slog.LevelInfo)
customLogger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: &levelVar,
}))
// Change level directly
levelVar.Set(slog.LevelDebug)
Problem: Service name, version, or environment not in logs.
Cause: Not configured or using custom logger.
Solution: Configure service metadata:
logger := logging.MustNew(
logging.WithJSONHandler(),
logging.WithServiceName("my-api"),
logging.WithServiceVersion("v1.0.0"),
logging.WithEnvironment("production"),
)
For custom logger, add metadata manually:
customLogger := slog.New(handler).With(
"service", "my-api",
"version", "v1.0.0",
"env", "production",
)
Router Integration Issues
Access Log Not Working
Problem: HTTP requests not being logged.
Possible causes:
- Logger not set on router:
r := router.MustNew()
logger := logging.MustNew(logging.WithJSONHandler())
r.SetLogger(logger) // Must set logger
- Middleware not applied:
import "rivaas.dev/router/middleware/accesslog"
r.Use(accesslog.New()) // Apply middleware
- Path excluded:
r.Use(accesslog.New(
accesslog.WithExcludePaths("/health", "/metrics"),
))
// /health and /metrics won't be logged
Context Logger Not Working
Problem: Router context logger has no trace IDs.
Cause: Tracing not initialized or middleware not applied.
Solution: Initialize tracing:
a, _ := app.New(
app.WithServiceName("my-api"),
app.WithObservability(
app.WithLogging(logging.WithJSONHandler()),
app.WithTracing(tracing.WithOTLP("localhost:4317")),
),
)
Testing Issues
Test Logs Not Captured
Problem: Logs not appearing in test buffer.
Cause: Using wrong logger instance.
Solution: Use TestHelper or ensure buffer is captured:
func TestMyFunction(t *testing.T) {
th := logging.NewTestHelper(t)
myFunction(th.Logger) // Pass test logger
logs, _ := th.Logs()
assert.Len(t, logs, 1)
}
Parse Errors
Problem: ParseJSONLogEntries returns error.
Cause: Non-JSON output or malformed JSON.
Solution: Ensure JSON handler:
th := logging.NewTestHelper(t,
logging.WithJSONHandler(), // Must be JSON
)
Error Types
ErrNilLogger
var ErrNilLogger = errors.New("custom logger is nil")
When: Providing nil custom logger.
Solution:
if customLogger != nil {
logger := logging.MustNew(
logging.WithCustomLogger(customLogger),
)
}
ErrInvalidHandler
var ErrInvalidHandler = errors.New("invalid handler type")
When: Invalid handler type specified.
Solution: Use valid handler types:
logging.WithHandlerType(logging.JSONHandler)
logging.WithHandlerType(logging.TextHandler)
logging.WithHandlerType(logging.ConsoleHandler)
ErrLoggerShutdown
var ErrLoggerShutdown = errors.New("logger is shut down")
When: Operations after shutdown.
Solution: Don’t use logger after shutdown:
defer logger.Shutdown(context.Background())
// Don't log after this point
ErrInvalidLevel
var ErrInvalidLevel = errors.New("invalid log level")
When: Invalid log level provided.
Solution: Use valid levels:
logging.LevelDebug
logging.LevelInfo
logging.LevelWarn
logging.LevelError
ErrCannotChangeLevel
var ErrCannotChangeLevel = errors.New("cannot change level on custom logger")
When: Calling SetLevel on custom logger.
Solution: Control level in custom logger directly or don’t use custom logger.
Getting Help
If you encounter issues not covered here:
- Check the API Reference for method details
- Review Examples for patterns
- See Best Practices for recommendations
- Check the GitHub issues
Debugging Tips
Enable Debug Info
info := logger.DebugInfo()
fmt.Printf("Logger state: %+v\n", info)
Check Sampling State
info := logger.DebugInfo()
if sampling, ok := info["sampling"]; ok {
fmt.Printf("Sampling config: %+v\n", sampling)
}
Verify Configuration
fmt.Printf("Service: %s\n", logger.ServiceName())
fmt.Printf("Version: %s\n", logger.ServiceVersion())
fmt.Printf("Environment: %s\n", logger.Environment())
fmt.Printf("Level: %s\n", logger.Level())
fmt.Printf("Enabled: %v\n", logger.IsEnabled())
Next Steps