slog
Structured, contextual, extensible, composable logging library for Rust. Designed to be superset of standard log crate. Composition is strong selling point of slog, enabling flexible logger construction.
Library
slog
Overview
slog is "a structured logging library for Go" that was added to the standard library in Go 1.21 as a modern logging solution. With the concept of "machine-readable logs," it moves away from traditional text-based logging to provide structured logs in key-value pair format. Through high-performance and type-safe logging functionality, it efficiently handles log analysis, search, and aggregation, establishing itself as the next-generation logging standard for improving observability in large-scale applications.
Details
slog 2025 edition has established a solid position as the definitive structured logging solution in the Go ecosystem. With integration into the standard library from Go 1.21, it achieves enterprise-level logging functionality without third-party dependencies. It provides comprehensive logging features required in modern microservice environments including clear log level control with DEBUG, INFO, WARN, ERROR levels, flexible output format support through custom handlers, and automatic integration of context information.
Key Features
- Structured Logging: Machine-readable logs in key-value pair format
- High-Performance Design: Zero-allocation optimization and lazy evaluation
- Flexible Handlers: Support for Text, JSON, and custom output formats
- Type Safety: Safe log recording using Go's type system
- Context Integration: Complete integration with context.Context
- Dynamic Level Control: Runtime log level modification functionality
Pros and Cons
Pros
- Stability and long-term support guarantee as Go standard library
- Enterprise-level structured logging functionality without third-party dependencies
- Low overhead operation in production environments through high-performance design
- Intuitive and consistent API design following Go language conventions
- Rich output options and extensibility with JSON and text formats
- Microservice support through natural integration with context.Context
- Flexible extension and customization through custom handlers
Cons
- Only available in Go 1.21 and later (not available in older Go versions)
- Feature limitations compared to mature third-party libraries (zap, logrus, etc.)
- Lack of advanced features like log rotation, filtering, and transformation
- Learning cost and compatibility issues when migrating existing projects
- No standard provision of large-scale log management or archive features
- Need for compatibility adjustments with some ecosystem libraries
Reference Pages
- log/slog Package Official Documentation
- Go 1.21 Release Notes - Structured Logging
- Structured Logging with slog - Go Blog
Code Examples
Installation and Setup (Import Only as Standard Library)
package main
import (
"log/slog"
"os"
"context"
"time"
)
func main() {
// Most basic setup (default handler)
// TextHandler is automatically used
slog.Info("application started")
// Initialization with custom handler
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
// Handler with configuration options
options := &slog.HandlerOptions{
Level: slog.LevelDebug, // Output from debug level
AddSource: true, // Add source file information
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
// Customize timestamp format
if a.Key == slog.TimeKey {
return slog.String("timestamp", a.Value.Time().Format(time.RFC3339))
}
return a
},
}
textHandler := slog.NewTextHandler(os.Stdout, options)
customLogger := slog.New(textHandler)
customLogger.Info("custom handler initialization complete",
"handler_type", "TextHandler",
"debug_enabled", true,
"source_enabled", true,
)
}
Basic Log Output
package main
import (
"log/slog"
"os"
"time"
"errors"
)
func main() {
// Basic setup with JSON handler
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
slog.SetDefault(logger)
// Basic log level usage
slog.Debug("detailed debug information", "function", "main", "step", 1)
slog.Info("general information", "app_name", "example_app", "version", "1.0.0")
slog.Warn("warning message", "config_missing", true, "using_defaults", true)
slog.Error("error occurred", "error", errors.New("sample error"), "critical", false)
// Structured logging with key-value pairs
slog.Info("user action",
"user_id", 12345,
"action", "login",
"ip_address", "192.168.1.100",
"user_agent", "Mozilla/5.0...",
"timestamp", time.Now(),
"success", true,
)
// Example using different data types
slog.Info("data type samples",
"string_value", "Hello, World!",
"int_value", 42,
"float_value", 3.14159,
"bool_value", true,
"time_value", time.Now(),
"duration_value", 250*time.Millisecond,
"slice_value", []string{"apple", "banana", "cherry"},
"map_value", map[string]int{"red": 1, "green": 2, "blue": 3},
)
// Error handling and logging
err := performOperation()
if err != nil {
slog.Error("operation failed",
"operation", "data_processing",
"error", err,
"retry_count", 3,
"fatal", false,
)
} else {
slog.Info("operation succeeded",
"operation", "data_processing",
"duration", "150ms",
"items_processed", 1000,
)
}
}
func performOperation() error {
// Simulation of some process
return nil // or errors.New("processing error")
}
Structured Logging (Key-Value Pairs)
package main
import (
"log/slog"
"os"
"time"
"context"
)
func main() {
// Output in JSON format
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
// Type-safe structured logging using slog.Attr
slog.Info("HTTP request details",
slog.String("method", "POST"),
slog.String("endpoint", "/api/v1/users"),
slog.Int("status_code", 201),
slog.Duration("response_time", 120*time.Millisecond),
slog.Int64("request_size", 1024),
slog.Int64("response_size", 512),
slog.Bool("authenticated", true),
)
// Hierarchical log structure using Group
slog.Info("database operation",
slog.Group("database",
slog.String("driver", "postgres"),
slog.String("host", "localhost"),
slog.Int("port", 5432),
slog.String("database", "myapp"),
),
slog.Group("query",
slog.String("operation", "SELECT"),
slog.String("table", "users"),
slog.String("conditions", "WHERE active = true"),
slog.Int("limit", 100),
),
slog.Group("performance",
slog.Duration("execution_time", 45*time.Millisecond),
slog.Int("rows_returned", 85),
slog.Bool("cache_hit", false),
),
)
// Logging complex data structures
requestData := map[string]interface{}{
"headers": map[string]string{
"Content-Type": "application/json",
"Authorization": "Bearer <redacted>",
"User-Agent": "MyApp/1.0",
},
"payload": map[string]interface{}{
"username": "user123",
"email": "[email protected]",
"settings": map[string]bool{
"notifications": true,
"marketing": false,
},
},
}
slog.Info("API call",
"endpoint", "/api/users",
"method", "POST",
slog.Group("request",
slog.Any("headers", requestData["headers"]),
slog.Any("payload", requestData["payload"]),
),
slog.Group("metadata",
slog.String("trace_id", "abc123def456"),
slog.String("span_id", "789xyz"),
slog.Time("started_at", time.Now()),
),
)
// Business logic specific logging
order := struct {
ID string `json:"id"`
UserID int `json:"user_id"`
Total float64 `json:"total"`
Items int `json:"items"`
Created time.Time `json:"created"`
Shipping string `json:"shipping"`
}{
ID: "ORD-2024-001",
UserID: 12345,
Total: 99.99,
Items: 3,
Created: time.Now(),
Shipping: "express",
}
slog.Info("order processing",
slog.Group("order",
slog.String("order_id", order.ID),
slog.Int("user_id", order.UserID),
slog.Float64("total_amount", order.Total),
slog.Int("item_count", order.Items),
slog.Time("created_at", order.Created),
slog.String("shipping_method", order.Shipping),
),
slog.Group("processing",
slog.String("status", "completed"),
slog.Duration("processing_time", 2*time.Second),
slog.Bool("payment_verified", true),
slog.Bool("inventory_updated", true),
),
)
}
Custom Logger Creation
package main
import (
"context"
"log/slog"
"os"
"io"
"fmt"
"strings"
"time"
)
// Application-specific custom logger
type AppLogger struct {
*slog.Logger
appName string
version string
instanceID string
}
// Create custom logger
func NewAppLogger(appName, version, instanceID string, handler slog.Handler) *AppLogger {
// Create logger with common attributes
baseLogger := slog.New(handler).With(
"app_name", appName,
"app_version", version,
"instance_id", instanceID,
)
return &AppLogger{
Logger: baseLogger,
appName: appName,
version: version,
instanceID: instanceID,
}
}
// Log method for business logic
func (al *AppLogger) LogUserAction(ctx context.Context, userID int, action string, details map[string]interface{}) {
attrs := []slog.Attr{
slog.Int("user_id", userID),
slog.String("action", action),
slog.Time("timestamp", time.Now()),
}
if details != nil {
detailAttrs := make([]slog.Attr, 0, len(details))
for k, v := range details {
detailAttrs = append(detailAttrs, slog.Any(k, v))
}
attrs = append(attrs, slog.Group("details", detailAttrs...))
}
al.Logger.LogAttrs(ctx, slog.LevelInfo, "user action", attrs...)
}
func (al *AppLogger) LogAPICall(ctx context.Context, method, endpoint string, statusCode int, duration time.Duration) {
level := slog.LevelInfo
if statusCode >= 400 {
level = slog.LevelWarn
}
if statusCode >= 500 {
level = slog.LevelError
}
al.Logger.LogAttrs(ctx, level, "API call",
slog.String("http_method", method),
slog.String("endpoint", endpoint),
slog.Int("status_code", statusCode),
slog.Duration("response_time", duration),
slog.Group("classification",
slog.Bool("success", statusCode < 400),
slog.Bool("client_error", statusCode >= 400 && statusCode < 500),
slog.Bool("server_error", statusCode >= 500),
),
)
}
func (al *AppLogger) LogError(ctx context.Context, err error, operation string, metadata map[string]interface{}) {
attrs := []slog.Attr{
slog.String("error", err.Error()),
slog.String("operation", operation),
}
if metadata != nil {
metaAttrs := make([]slog.Attr, 0, len(metadata))
for k, v := range metadata {
metaAttrs = append(metaAttrs, slog.Any(k, v))
}
attrs = append(attrs, slog.Group("metadata", metaAttrs...))
}
al.Logger.LogAttrs(ctx, slog.LevelError, "error occurred", attrs...)
}
// Custom handler: output in specific format
type CustomFormatHandler struct {
writer io.Writer
level slog.Level
}
func NewCustomFormatHandler(w io.Writer, level slog.Level) *CustomFormatHandler {
return &CustomFormatHandler{writer: w, level: level}
}
func (h *CustomFormatHandler) Enabled(ctx context.Context, level slog.Level) bool {
return level >= h.level
}
func (h *CustomFormatHandler) Handle(ctx context.Context, r slog.Record) error {
// Custom format: [TIME] LEVEL APP_NAME: MESSAGE key1=value1 key2=value2
var builder strings.Builder
builder.WriteString("[")
builder.WriteString(r.Time.Format("2006-01-02 15:04:05"))
builder.WriteString("] ")
builder.WriteString(r.Level.String())
builder.WriteString(" ")
// Get app information
appName := "unknown"
r.Attrs(func(a slog.Attr) bool {
if a.Key == "app_name" {
appName = a.Value.String()
}
return true
})
builder.WriteString(appName)
builder.WriteString(": ")
builder.WriteString(r.Message)
// Add attributes in key=value format
r.Attrs(func(a slog.Attr) bool {
if a.Key != "app_name" && a.Key != "app_version" && a.Key != "instance_id" {
builder.WriteString(" ")
builder.WriteString(a.Key)
builder.WriteString("=")
builder.WriteString(fmt.Sprintf("%v", a.Value.Any()))
}
return true
})
builder.WriteString("\n")
_, err := h.writer.Write([]byte(builder.String()))
return err
}
func (h *CustomFormatHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
// For simplicity, return a new handler instance
return h
}
func (h *CustomFormatHandler) WithGroup(name string) slog.Handler {
// For simplicity, return the same handler
return h
}
func main() {
// Example with standard JSON handler
jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
AddSource: false,
})
appLogger1 := NewAppLogger("MyWebApp", "1.2.3", "inst-001", jsonHandler)
ctx := context.Background()
// Using business logic log methods
appLogger1.LogUserAction(ctx, 12345, "login", map[string]interface{}{
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0...",
"method": "oauth2",
})
appLogger1.LogAPICall(ctx, "GET", "/api/users/12345", 200, 150*time.Millisecond)
appLogger1.LogAPICall(ctx, "POST", "/api/orders", 400, 50*time.Millisecond)
appLogger1.LogAPICall(ctx, "GET", "/api/reports", 500, 2*time.Second)
// Error logging example
err := fmt.Errorf("database connection error")
appLogger1.LogError(ctx, err, "user_data_fetch", map[string]interface{}{
"user_id": 12345,
"retry_count": 3,
"timeout": "30s",
})
fmt.Println("\n--- Custom Format Handler ---")
// Example with custom format handler
customHandler := NewCustomFormatHandler(os.Stdout, slog.LevelInfo)
appLogger2 := NewAppLogger("CustomApp", "2.0.0", "inst-002", customHandler)
appLogger2.Info("application started")
appLogger2.LogUserAction(ctx, 67890, "purchase", map[string]interface{}{
"amount": 99.99,
"currency": "USD",
"items": 3,
})
// Regular slog methods are also available
appLogger2.Logger.Warn("high memory usage",
"memory_usage_mb", 1024,
"threshold_mb", 800,
"action_required", true,
)
}
Handler Configuration
package main
import (
"context"
"fmt"
"log/slog"
"os"
"strings"
"time"
"sync"
"path/filepath"
)
// Multi-output handler: simultaneous output to multiple destinations
type MultiHandler struct {
handlers []slog.Handler
}
func NewMultiHandler(handlers ...slog.Handler) *MultiHandler {
return &MultiHandler{handlers: handlers}
}
func (h *MultiHandler) Enabled(ctx context.Context, level slog.Level) bool {
for _, handler := range h.handlers {
if handler.Enabled(ctx, level) {
return true
}
}
return false
}
func (h *MultiHandler) Handle(ctx context.Context, r slog.Record) error {
for _, handler := range h.handlers {
if handler.Enabled(ctx, r.Level) {
if err := handler.Handle(ctx, r); err != nil {
return err
}
}
}
return nil
}
func (h *MultiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
newHandlers := make([]slog.Handler, len(h.handlers))
for i, handler := range h.handlers {
newHandlers[i] = handler.WithAttrs(attrs)
}
return &MultiHandler{handlers: newHandlers}
}
func (h *MultiHandler) WithGroup(name string) slog.Handler {
newHandlers := make([]slog.Handler, len(h.handlers))
for i, handler := range h.handlers {
newHandlers[i] = handler.WithGroup(name)
}
return &MultiHandler{handlers: newHandlers}
}
// Level-based file output handler
type LevelFileHandler struct {
mu sync.Mutex
files map[slog.Level]*os.File
baseHandler slog.Handler
}
func NewLevelFileHandler(baseDir string) (*LevelFileHandler, error) {
if err := os.MkdirAll(baseDir, 0755); err != nil {
return nil, err
}
handler := &LevelFileHandler{
files: make(map[slog.Level]*os.File),
}
levels := []slog.Level{slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError}
levelNames := []string{"debug", "info", "warn", "error"}
for i, level := range levels {
filename := filepath.Join(baseDir, fmt.Sprintf("%s.log", levelNames[i]))
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
// Close already opened files
for _, f := range handler.files {
f.Close()
}
return nil, err
}
handler.files[level] = file
}
// Use JSON format as base handler
handler.baseHandler = slog.NewJSONHandler(os.Stdout, nil)
return handler, nil
}
func (h *LevelFileHandler) Enabled(ctx context.Context, level slog.Level) bool {
return true // Enable all levels
}
func (h *LevelFileHandler) Handle(ctx context.Context, r slog.Record) error {
h.mu.Lock()
defer h.mu.Unlock()
// Select appropriate file
var targetFile *os.File
if r.Level >= slog.LevelError {
targetFile = h.files[slog.LevelError]
} else if r.Level >= slog.LevelWarn {
targetFile = h.files[slog.LevelWarn]
} else if r.Level >= slog.LevelInfo {
targetFile = h.files[slog.LevelInfo]
} else {
targetFile = h.files[slog.LevelDebug]
}
if targetFile != nil {
// Create handler for file
fileHandler := slog.NewJSONHandler(targetFile, nil)
fileHandler.Handle(ctx, r)
}
// Also output to stdout
return h.baseHandler.Handle(ctx, r)
}
func (h *LevelFileHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &LevelFileHandler{
files: h.files,
baseHandler: h.baseHandler.WithAttrs(attrs),
}
}
func (h *LevelFileHandler) WithGroup(name string) slog.Handler {
return &LevelFileHandler{
files: h.files,
baseHandler: h.baseHandler.WithGroup(name),
}
}
func (h *LevelFileHandler) Close() error {
h.mu.Lock()
defer h.mu.Unlock()
for _, file := range h.files {
if err := file.Close(); err != nil {
return err
}
}
return nil
}
// Dynamic level control handler
type DynamicLevelHandler struct {
levelVar *slog.LevelVar
handler slog.Handler
}
func NewDynamicLevelHandler(initialLevel slog.Level, handler slog.Handler) *DynamicLevelHandler {
levelVar := &slog.LevelVar{}
levelVar.Set(initialLevel)
return &DynamicLevelHandler{
levelVar: levelVar,
handler: handler,
}
}
func (h *DynamicLevelHandler) Enabled(ctx context.Context, level slog.Level) bool {
return level >= h.levelVar.Level()
}
func (h *DynamicLevelHandler) Handle(ctx context.Context, r slog.Record) error {
if !h.Enabled(ctx, r.Level) {
return nil
}
return h.handler.Handle(ctx, r)
}
func (h *DynamicLevelHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &DynamicLevelHandler{
levelVar: h.levelVar,
handler: h.handler.WithAttrs(attrs),
}
}
func (h *DynamicLevelHandler) WithGroup(name string) slog.Handler {
return &DynamicLevelHandler{
levelVar: h.levelVar,
handler: h.handler.WithGroup(name),
}
}
func (h *DynamicLevelHandler) SetLevel(level slog.Level) {
h.levelVar.Set(level)
}
func (h *DynamicLevelHandler) GetLevel() slog.Level {
return h.levelVar.Level()
}
func main() {
fmt.Println("=== Handler Configuration Examples ===")
// 1. Basic handler configuration
fmt.Println("\n1. Basic JSON Handler:")
jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
AddSource: true,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
// Mask sensitive information
if a.Key == "password" || a.Key == "token" {
return slog.String(a.Key, "[REDACTED]")
}
// Change timestamp format
if a.Key == slog.TimeKey {
return slog.String("timestamp", a.Value.Time().Format("2006-01-02 15:04:05.000"))
}
return a
},
})
logger1 := slog.New(jsonHandler)
logger1.Info("user authentication",
"username", "user123",
"password", "secret123", // This will be masked
"token", "jwt-token-abc", // This will also be masked
"success", true,
)
// 2. Text handler configuration
fmt.Println("\n2. Custom Text Handler:")
textHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
AddSource: false,
})
logger2 := slog.New(textHandler)
logger2.Debug("debug information", "module", "auth", "step", "validation")
logger2.Info("processing complete", "duration", 150*time.Millisecond)
// 3. Multi-handler usage (multiple output destinations)
fmt.Println("\n3. Multi-handler (stdout + file):")
// File output handler
logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
panic(err)
}
defer logFile.Close()
fileHandler := slog.NewJSONHandler(logFile, nil)
consoleHandler := slog.NewTextHandler(os.Stdout, nil)
multiHandler := NewMultiHandler(fileHandler, consoleHandler)
logger3 := slog.New(multiHandler)
logger3.Info("multi-output test", "destination", "both file and console")
// 4. Level-based file output
fmt.Println("\n4. Level-based File Output:")
levelFileHandler, err := NewLevelFileHandler("logs")
if err != nil {
panic(err)
}
defer levelFileHandler.Close()
logger4 := slog.New(levelFileHandler)
logger4.Debug("debug message")
logger4.Info("info message")
logger4.Warn("warning message")
logger4.Error("error message")
// 5. Dynamic level control
fmt.Println("\n5. Dynamic Level Control:")
baseHandler := slog.NewTextHandler(os.Stdout, nil)
dynamicHandler := NewDynamicLevelHandler(slog.LevelInfo, baseHandler)
logger5 := slog.New(dynamicHandler)
logger5.Debug("debug (not shown)")
logger5.Info("info (shown)")
// Change level to DEBUG
dynamicHandler.SetLevel(slog.LevelDebug)
logger5.Debug("debug (now shown)")
// Change level to ERROR
dynamicHandler.SetLevel(slog.LevelError)
logger5.Info("info (not shown)")
logger5.Error("error (shown)")
// 6. Context-aware handler
fmt.Println("\n6. Context-aware Handler:")
// Logger with request-specific information
baseCtxHandler := slog.NewJSONHandler(os.Stdout, nil)
requestLogger := slog.New(baseCtxHandler).With(
"request_id", "req-12345",
"user_id", 67890,
"session_id", "sess-abcdef",
)
requestLogger.Info("request started")
requestLogger.Info("authentication check", "auth_method", "bearer_token")
requestLogger.Info("data fetch", "table", "users", "rows", 150)
requestLogger.Info("response sent", "status", 200, "size_bytes", 2048)
fmt.Println("\n=== Log files created ===")
fmt.Println("- app.log (multi-handler output)")
fmt.Println("- logs/debug.log (debug level)")
fmt.Println("- logs/info.log (info level)")
fmt.Println("- logs/warn.log (warning level)")
fmt.Println("- logs/error.log (error level)")
}
Performance Optimization
package main
import (
"context"
"fmt"
"log/slog"
"os"
"runtime"
"sync"
"time"
"unsafe"
)
// High-performance buffering handler
type BufferedHandler struct {
mu sync.Mutex
handler slog.Handler
buffer []slog.Record
bufSize int
flushInterval time.Duration
lastFlush time.Time
}
func NewBufferedHandler(handler slog.Handler, bufSize int, flushInterval time.Duration) *BufferedHandler {
bh := &BufferedHandler{
handler: handler,
buffer: make([]slog.Record, 0, bufSize),
bufSize: bufSize,
flushInterval: flushInterval,
lastFlush: time.Now(),
}
// Start periodic flushing
go bh.periodicFlush()
return bh
}
func (h *BufferedHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.handler.Enabled(ctx, level)
}
func (h *BufferedHandler) Handle(ctx context.Context, r slog.Record) error {
h.mu.Lock()
defer h.mu.Unlock()
h.buffer = append(h.buffer, r)
// Flush when buffer is full
if len(h.buffer) >= h.bufSize {
return h.flushLocked(ctx)
}
return nil
}
func (h *BufferedHandler) flushLocked(ctx context.Context) error {
for _, record := range h.buffer {
if err := h.handler.Handle(ctx, record); err != nil {
return err
}
}
h.buffer = h.buffer[:0] // Clear buffer
h.lastFlush = time.Now()
return nil
}
func (h *BufferedHandler) periodicFlush() {
ticker := time.NewTicker(h.flushInterval)
defer ticker.Stop()
for range ticker.C {
h.mu.Lock()
if len(h.buffer) > 0 && time.Since(h.lastFlush) >= h.flushInterval {
h.flushLocked(context.Background())
}
h.mu.Unlock()
}
}
func (h *BufferedHandler) Flush() error {
h.mu.Lock()
defer h.mu.Unlock()
return h.flushLocked(context.Background())
}
func (h *BufferedHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &BufferedHandler{
handler: h.handler.WithAttrs(attrs),
buffer: make([]slog.Record, 0, h.bufSize),
bufSize: h.bufSize,
flushInterval: h.flushInterval,
lastFlush: time.Now(),
}
}
func (h *BufferedHandler) WithGroup(name string) slog.Handler {
return &BufferedHandler{
handler: h.handler.WithGroup(name),
buffer: make([]slog.Record, 0, h.bufSize),
bufSize: h.bufSize,
flushInterval: h.flushInterval,
lastFlush: time.Now(),
}
}
// Zero-allocation optimization example
func optimizedLoggingExample() {
fmt.Println("=== Zero-allocation Optimization ===")
// Prepare attributes in advance to reduce allocations
attrs := []slog.Attr{
slog.String("service", "web-api"),
slog.String("version", "1.0.0"),
slog.String("environment", "production"),
}
handler := slog.NewJSONHandler(os.Stdout, nil)
logger := slog.New(handler).With(attrs...)
// Optimized logging using LogAttrs
ctx := context.Background()
logger.LogAttrs(ctx, slog.LevelInfo, "optimized logging",
slog.Int("user_id", 12345),
slog.String("action", "login"),
slog.Duration("elapsed", 100*time.Millisecond),
)
// Conditional logging to avoid unnecessary processing
if logger.Enabled(ctx, slog.LevelDebug) {
// Heavy processing only when debug level is enabled
expensiveData := gatherExpensiveDebugData()
logger.LogAttrs(ctx, slog.LevelDebug, "detailed debug information",
slog.Any("debug_data", expensiveData),
)
}
}
func gatherExpensiveDebugData() map[string]interface{} {
// Simulation of heavy processing
time.Sleep(10 * time.Millisecond)
return map[string]interface{}{
"memory_stats": runtime.MemStats{},
"goroutines": runtime.NumGoroutine(),
"cpu_count": runtime.NumCPU(),
}
}
// Performance test
func performanceTest() {
fmt.Println("\n=== Performance Test ===")
// Test configuration
iterations := 100000
// 1. Standard handler test
jsonHandler := slog.NewJSONHandler(os.Stdout, nil)
standardLogger := slog.New(jsonHandler)
var m1, m2 runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&m1)
start := time.Now()
for i := 0; i < iterations; i++ {
standardLogger.Info("test message",
"iteration", i,
"timestamp", time.Now().Unix(),
)
}
standardDuration := time.Since(start)
runtime.GC()
runtime.ReadMemStats(&m2)
fmt.Printf("Standard handler:\n")
fmt.Printf(" Time: %v (%v ns/op)\n",
standardDuration, standardDuration.Nanoseconds()/int64(iterations))
fmt.Printf(" Memory: %d bytes allocated\n", m2.TotalAlloc-m1.TotalAlloc)
// 2. Buffering handler test
bufferedHandler := NewBufferedHandler(
slog.NewJSONHandler(os.Stdout, nil),
1000, // Buffer size
100*time.Millisecond, // Flush interval
)
bufferedLogger := slog.New(bufferedHandler)
runtime.GC()
runtime.ReadMemStats(&m1)
start = time.Now()
for i := 0; i < iterations; i++ {
bufferedLogger.Info("test message",
"iteration", i,
"timestamp", time.Now().Unix(),
)
}
bufferedHandler.Flush() // Explicit flush
bufferedDuration := time.Since(start)
runtime.GC()
runtime.ReadMemStats(&m2)
fmt.Printf("Buffering handler:\n")
fmt.Printf(" Time: %v (%v ns/op)\n",
bufferedDuration, bufferedDuration.Nanoseconds()/int64(iterations))
fmt.Printf(" Memory: %d bytes allocated\n", m2.TotalAlloc-m1.TotalAlloc)
fmt.Printf(" Improvement: %.2fx faster\n",
float64(standardDuration)/float64(bufferedDuration))
}
// Concurrent logging test
func concurrentLoggingTest() {
fmt.Println("\n=== Concurrent Logging Test ===")
handler := slog.NewJSONHandler(os.Stdout, nil)
logger := slog.New(handler)
var wg sync.WaitGroup
goroutines := 10
messagesPerGoroutine := 1000
start := time.Now()
for i := 0; i < goroutines; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < messagesPerGoroutine; j++ {
logger.Info("concurrent message",
"goroutine", id,
"message", j,
"thread_safe", true,
)
}
}(i)
}
wg.Wait()
duration := time.Since(start)
totalMessages := goroutines * messagesPerGoroutine
fmt.Printf("Concurrent logging: %d goroutines × %d messages = %d total\n",
goroutines, messagesPerGoroutine, totalMessages)
fmt.Printf("Execution time: %v (%v ns/message)\n",
duration, duration.Nanoseconds()/int64(totalMessages))
}
// Memory efficiency test
func memoryEfficiencyTest() {
fmt.Println("\n=== Memory Efficiency Test ===")
// Large object logging
largeData := make([]map[string]interface{}, 1000)
for i := range largeData {
largeData[i] = map[string]interface{}{
"id": i,
"name": fmt.Sprintf("item_%d", i),
"value": float64(i) * 3.14159,
"tags": []string{"tag1", "tag2", "tag3"},
}
}
handler := slog.NewJSONHandler(os.Stdout, nil)
logger := slog.New(handler)
var m1, m2 runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&m1)
// Inefficient method (serialize large object every time)
start := time.Now()
for i := 0; i < 100; i++ {
logger.Info("large data", "data", largeData)
}
inefficientDuration := time.Since(start)
runtime.GC()
runtime.ReadMemStats(&m2)
inefficientMemory := m2.TotalAlloc - m1.TotalAlloc
// Efficient method (log metadata only)
runtime.GC()
runtime.ReadMemStats(&m1)
start = time.Now()
for i := 0; i < 100; i++ {
logger.Info("large data (metadata only)",
"data_size", len(largeData),
"sample_id", largeData[0]["id"],
"sample_name", largeData[0]["name"],
"total_memory", unsafe.Sizeof(largeData),
)
}
efficientDuration := time.Since(start)
runtime.GC()
runtime.ReadMemStats(&m2)
efficientMemory := m2.TotalAlloc - m1.TotalAlloc
fmt.Printf("Inefficient logging:\n")
fmt.Printf(" Time: %v\n", inefficientDuration)
fmt.Printf(" Memory: %d bytes\n", inefficientMemory)
fmt.Printf("Efficient logging:\n")
fmt.Printf(" Time: %v\n", efficientDuration)
fmt.Printf(" Memory: %d bytes\n", efficientMemory)
fmt.Printf("Improvement:\n")
fmt.Printf(" Time: %.2fx faster\n",
float64(inefficientDuration)/float64(efficientDuration))
fmt.Printf(" Memory: %.2fx less\n",
float64(inefficientMemory)/float64(efficientMemory))
}
func main() {
optimizedLoggingExample()
performanceTest()
concurrentLoggingTest()
memoryEfficiencyTest()
fmt.Println("\n=== Performance Test Complete ===")
}