slog

darvaza.org/slog provides a backend-agnostic interface for structured logging
in Go. It defines a simple, standardised API that libraries can use without
forcing a specific logging implementation on their users.
Features
- Backend-agnostic: Define logging interfaces without forcing
implementation choices.
- Structured logging: Support for typed fields with string keys.
- Method chaining: Fluent API for composing log entries.
- Six log levels: Debug, Info, Warn, Error, Fatal, and Panic.
- Context integration: Store and retrieve loggers from context values.
- Standard library compatible: Adapters for Go's standard
log package.
- Multiple handlers: Pre-built integrations with popular logging libraries.
- Immutable logger instances: Each modification creates a new logger,
enabling safe concurrent use and proper branching behaviour.
Installation
go get darvaza.org/slog
Quick Start
package main
import (
"darvaza.org/slog"
"darvaza.org/slog/handlers/discard"
)
func main() {
logger := discard.New()
logger.Info().Print("Application started")
logger.Debug().
WithField("user", "john").
WithField("action", "login").
Print("User logged in")
logger.Warn().
WithField("retry_count", 3).
Printf("Connection failed, will retry")
}
Interface
The slog.Logger interface provides a fluent API where most methods return a
Logger for method chaining. A log entry is composed by:
- Setting the log level
- Optionally adding fields and call stack information
- Emitting the entry with a Print method
Disabled log entries incur minimal overhead as string formatting and field
collection are skipped.
Log Levels
The library supports six log levels with clear semantics:
- Debug: Detailed information for developers.
- Info: General informational messages.
- Warn: Warning messages for potentially harmful situations.
- Error: Error conditions that allow continued operation.
- Fatal: Critical errors that terminate the program (like
log.Fatal()).
- Panic: Errors that trigger a recoverable panic (like
log.Panic()).
Create log entries using named methods (Debug(), Info(), etc.) or
WithLevel(level).
Enabled State
A log entry is "enabled" if the handler will actually emit it. Operating on
disabled loggers is safe and efficient - string formatting and field collection
are skipped.
Use WithEnabled() to check if a level is enabled:
if log, ok := logger.Debug().WithEnabled(); ok {
log.WithField("details", expensiveOperation()).Print("Debug info")
} else if log, ok := logger.Info().WithEnabled(); ok {
log.Print("Operation completed")
}
Note: Fatal and Panic levels always execute regardless of enabled state.
Fields
Fields are key/value pairs for structured logging:
- Keys must be non-empty strings
- Values can be any type
- Fields are attached using
WithField(key, value)
- Multiple fields can be attached by chaining calls
import "time"
start := time.Now()
logger.Info().
WithField("user_id", 123).
WithField("duration", time.Since(start)).
Print("Request processed")
Branching Behaviour
Each logger instance is immutable. When you call methods like WithField() or
WithLevel(), you get a new logger instance that inherits from the parent:
baseLogger := logger.WithField("service", "api")
userLogger := baseLogger.WithField("handler", "user")
adminLogger := baseLogger.WithField("handler", "admin")
userLogger.Info().Print("Processing user request")
adminLogger.Info().Print("Processing admin request")
baseLogger.Info().Print("Base logger message")
This design ensures:
- Thread-safe concurrent use without locks
- No unintended field pollution between different code paths
- Clear ownership and lifecycle of logger configurations
Call Stack
Attach stack traces to log entries using WithStack(skip):
logger.Error().
WithStack(0).
WithField("error", err).
Print("Operation failed")
The skip parameter specifies how many stack frames to skip (0 = current
function).
Print Methods
Three print methods match the fmt package conventions:
Print(v ...any): Like fmt.Print
Println(v ...any): Like fmt.Println
Printf(format string, v ...any): Like fmt.Printf
These methods emit the log entry with all attached fields.
Standard Library Integration
Integrate with Go's standard log package:
import (
"log"
"net/http"
"darvaza.org/slog"
)
stdLogger := slog.NewStdLogger(logger, "[HTTP]", log.LstdFlags)
server := &http.Server{
ErrorLog: stdLogger,
}
For custom parsing, use NewLogWriter() with a handler function.
Architecture Overview
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β External Dependencies β
βββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ€
β darvaza.org/core β Go Standard Library β
βββββββββββββββ¬ββββββββββββββββ΄ββββββββββββ¬βββββββββββββββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β slog Core β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββ¬ββββββββββββββββββββββββ€
β Logger Interface β Context Integration β Std Library Adapter β
ββββββββββββββββββββββββ΄ββββββββββββββββββββββ΄ββββββββββββββββββββββββ€
β internal.Loglet (field chain management) β
ββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Handlers β
βββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββ¬ββββββββββ€
β logr β logrus β zap β zerolog β cblog β filter β discard β
βββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββ΄ββββββββββ
All handlers use the internal.Loglet type for consistent field chain
management and immutable logger behaviour.
Available Handlers
Adapter Types
Handlers fall into two categories based on their integration capabilities:
Bidirectional Adapters
These handlers allow conversion in both directions - you can use the external
logging library as a slog backend, OR use slog as a backend for the external
library:
- logr:
Full bidirectional adapter for go-logr/logr interface.
logr.Logger β slog.Logger (use logr as slog backend)
slog.Logger β logr.Logger (use slog as logr backend)
logrus:
Bidirectional adapter for Sirupsen/logrus.
logrus.Logger β slog.Logger (use logrus as slog backend)
slog.Logger β logrus.Logger (use slog as logrus backend)
zap:
Bidirectional adapter between Uber's zap and slog. Use zap as a slog backend
or create zap loggers backed by any slog implementation.
Unidirectional Adapters
These handlers only allow using the external logging library as a backend for
slog. They wrap existing loggers but don't provide the reverse conversion:
zerolog:
Wraps rs/zerolog as a slog backend.
Utility Handlers
These handlers provide additional functionality without external dependencies:
-
cblog:
Channel-based handler for custom log processing.
-
filter:
Middleware to filter and transform log entries.
-
mock:
Mock logger implementation that records messages for testing and verification.
Provides a fully functional slog.Logger that captures all log entries with
their levels, messages, and fields for programmatic inspection.
import (
"testing"
"darvaza.org/slog/handlers/mock"
)
func TestMyCode(t *testing.T) {
logger := mock.NewLogger()
myFunction(logger)
messages := logger.GetMessages()
if len(messages) != 1 {
t.Fatalf("expected 1 message, got %d", len(messages))
}
msg := messages[0]
if msg.Level != slog.Info || msg.Message != "expected message" {
t.Errorf("unexpected log entry: %v", msg)
}
}
-
discard:
No-op handler for testing and optional logging.
Adapter Differences
Bidirectional adapters are valuable when:
- Integration with libraries that expect a specific logger interface is
required.
- Gradual migration between logging systems is in progress.
- A common interface is desired across different application components while
maintaining compatibility with existing code.
Unidirectional adapters are simpler and suitable when:
- An existing logger serves as the slog backend without reverse integration.
- New applications can adopt slog as the primary logging interface.
- Libraries expecting the backend's specific interface are not a concern.
Testing
The package provides comprehensive test utilities for handler implementations in
internal/testing. These utilities help ensure consistent testing patterns and
reduce code duplication across handlers.
See internal/testing/README.md for detailed
documentation on using the test utilities, including:
- Test logger for recording and verifying log messages
- Assertion helpers for message verification
- Compliance test suite for interface conformance
- Concurrency testing utilities
Development
See AGENT.md for development guidelines and
LICENCE.txt for licensing information.