logging

package
v0.24.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2025 License: MIT Imports: 8 Imported by: 0

README

Structured Logging with Go's log/slog

This package provides structured logging capabilities using Go's log/slog package, following best practices from the Better Stack Community Guide.

Features

  • Structured logging with levels (DEBUG, INFO, WARN, ERROR, plus custom TRACE/FATAL)
  • Strongly-typed attributes using slog.String(), slog.Int(), etc.
  • Child loggers for consistent contextual information
  • LogValuer interface implementations for custom types
  • Stack traces for error logging with source information
  • Environment-based configuration (dev/prod/test)
  • Dynamic log level control at runtime
  • Grouped contextual attributes for structured data
  • Operation logging with duration tracking
  • Context extraction for request/trace IDs

Quick Start

Basic Usage
package main

import (
    "context"
    "log/slog"
    "github.com/c0deZ3R0/go-sync-kit/logging"
)

func main() {
    // Initialize with environment-based configuration
    logging.Init(logging.GetConfigFromEnv())
    
    // Basic logging
    logging.Info("Application started",
        slog.String("version", "1.0.0"),
        slog.Int("port", 8080),
    )
    
    // Contextual logging
    ctx := context.Background()
    logging.InfoContext(ctx, "Processing request",
        slog.String("user_id", "123"),
        slog.Duration("timeout", time.Second*30),
    )
}
Child Loggers
// Create component-specific loggers
serverLogger := logging.WithComponent(logging.Component("http-server"))
dbLogger := logging.WithComponent(logging.Component("database"))

serverLogger.Info("Server starting", slog.String("address", ":8080"))
dbLogger.Info("Connected to database", slog.String("host", "localhost"))
Error Logging with Stack Traces
err := fmt.Errorf("database connection failed")
logging.LogError(ctx, err, "Failed to initialize database",
    slog.String("host", "localhost"),
    slog.Int("port", 5432),
)
Operation Logging
err := logging.LogOperation(ctx, 
    logging.Operation("user-authentication"), 
    logging.Component("auth-service"),
    func() error {
        // Your operation logic here
        return authenticateUser(userID)
    },
)

Configuration

Environment Variables
Variable Description Default
LOG_LEVEL Logging level (trace, debug, info, warn, error, fatal) info
LOG_FORMAT Output format (text, json) json
LOG_ADD_SOURCE Include source file information (true, false) true
ENVIRONMENT Environment (development, production, test) dev
Configuration Examples
# Development
export ENVIRONMENT=development
export LOG_LEVEL=debug
export LOG_FORMAT=text
export LOG_ADD_SOURCE=true

# Production
export ENVIRONMENT=production
export LOG_LEVEL=info
export LOG_FORMAT=json
export LOG_ADD_SOURCE=false
Programmatic Configuration
config := logging.Config{
    Level:       "debug",
    Format:      "json",
    AddSource:   true,
    Environment: "production",
}

logging.Init(config)

Advanced Features

Custom Log Levels
// Use custom TRACE level (more verbose than DEBUG)
logging.Trace("Detailed debugging information",
    slog.Any("request", detailedRequest),
)

// Use FATAL level (exits program)
logging.Fatal("Critical system error",
    slog.String("component", "core"),
)
Dynamic Level Changes
logger, levelVar := logging.NewLoggerWithDynamicLevel(config)

// Change level at runtime
levelVar.SetFromString("debug")  // Enable debug logging
levelVar.SetFromString("error")  // Only show errors
Structured Error Logging

The logging package automatically detects and structures SyncError types:

syncErr := &errors.SyncError{
    Op:        errors.OpStore,
    Component: "sqlite",
    Code:      errors.ErrCodeStorageFailure,
    Kind:      errors.KindInternal,
    Err:       fmt.Errorf("disk full"),
    Retryable: true,
    Metadata: map[string]interface{}{
        "disk_usage": "95%",
        "table":      "events",
    },
}

logging.LogError(ctx, syncErr, "Storage operation failed")

Output (JSON format):

{
  "time": "2023-12-07T10:30:00.000Z",
  "level": "ERROR",
  "msg": "Storage operation failed",
  "sync_error": {
    "operation": "store",
    "component": "sqlite",
    "code": "STORAGE_FAILURE",
    "kind": "INTERNAL",
    "retryable": true,
    "error": "disk full",
    "metadata": {
      "disk_usage": "95%",
      "table": "events"
    }
  },
  "caller": {
    "file": "/path/to/file.go",
    "line": 42,
    "function": "main.handleRequest"
  }
}
Grouped Attributes
logging.Info("HTTP request completed",
    slog.Group("request",
        slog.String("method", "POST"),
        slog.String("path", "/api/users"),
        slog.Int("status", 201),
    ),
    slog.Group("timing",
        slog.Duration("total", 150*time.Millisecond),
        slog.Duration("db_query", 50*time.Millisecond),
    ),
)
Context Integration
// Extract request/trace IDs from context
ctx = context.WithValue(ctx, "request_id", "req-123")
ctx = context.WithValue(ctx, "trace_id", "trace-456")

contextLogger := logging.WithContext(ctx)
contextLogger.Info("Processing with context")
// Automatically includes request_id and trace_id

Best Practices

1. Use Strongly-Typed Attributes
// ✅ Good - strongly typed
logging.Info("User created",
    slog.String("user_id", userID),
    slog.Int("age", user.Age),
    slog.Bool("verified", user.Verified),
)

// ❌ Avoid - loosely typed
logging.Info("User created", "user_id", userID, "age", user.Age)
2. Create Component Loggers
// ✅ Good - component-specific logger
dbLogger := logging.WithComponent(logging.Component("database"))
dbLogger.Info("Connection established")

// ✅ Also good - operation-specific logger
syncLogger := logging.WithOperation(logging.Operation("data-sync"))
3. Use Structured Error Context
// ✅ Good - structured error information
logging.LogError(ctx, err, "Failed to process payment",
    slog.String("payment_id", paymentID),
    slog.String("user_id", userID),
    slog.Float64("amount", amount),
)
// ✅ Good - grouped attributes
logging.Info("API call completed",
    slog.Group("request",
        slog.String("method", method),
        slog.String("endpoint", endpoint),
    ),
    slog.Group("response",
        slog.Int("status", status),
        slog.Duration("latency", latency),
    ),
)

Performance Considerations

  • The logging package uses slog's efficient attribute handling
  • Structured logging has minimal performance overhead
  • Child loggers reuse underlying logger instances
  • JSON format is optimized for production log processing
  • Source information (AddSource: true) has small performance cost

Integration with Existing Code

To migrate from standard log package:

// Before
log.Printf("User %s logged in", userID)

// After
logging.Info("User logged in", slog.String("user_id", userID))

To migrate from other logging libraries:

// Before (logrus example)
logrus.WithFields(logrus.Fields{
    "user_id": userID,
    "action":  "login",
}).Info("User action")

// After
logging.Info("User action",
    slog.String("user_id", userID),
    slog.String("action", "login"),
)

Testing

The package includes comprehensive tests:

go test ./logging -v
go test ./logging -bench=.  # Run benchmarks

Example Output

Development (Text Format)
time=2023-12-07T10:30:00.000Z level=INFO msg="HTTP server starting" component=server address=:8080 endpoints./.="Dashboard UI" endpoints./sync="Sync endpoint"
Production (JSON Format)
{
  "time": "2023-12-07T10:30:00.000Z",
  "level": "INFO",
  "msg": "HTTP server starting",
  "component": "server",
  "address": ":8080",
  "endpoints": {
    "/": "Dashboard UI",
    "/sync": "Sync endpoint",
    "/metrics": "Metrics endpoint",
    "/health": "Health check"
  }
}

This structured logging implementation provides a solid foundation for observability and debugging in production systems while maintaining excellent performance and developer experience.

Documentation

Overview

Package logging provides structured logging capabilities using Go's log/slog package.

It includes a Logger wrapper with convenience methods for operations and components, structured error logging for SyncError types, and utilities for context propagation. Initialize with Init() to set the global logger instance.

See also:

Package logging provides structured logging capabilities using Go's log/slog package following best practices from the Better Stack Community Guide.

Index

Constants

View Source
const (
	EnvDevelopment = "development"
	EnvProduction  = "production"
	EnvTest        = "test"
)

Environment types

Variables

View Source
var DefaultConfig = Config{
	Level:       "info",
	Format:      "json",
	AddSource:   true,
	Environment: "dev",
}

Default configuration

Functions

func Debug

func Debug(msg string, attrs ...slog.Attr)

Convenience methods that use the default logger

func DebugContext

func DebugContext(ctx context.Context, msg string, attrs ...slog.Attr)

func Error

func Error(msg string, attrs ...slog.Attr)

func ErrorContext

func ErrorContext(ctx context.Context, msg string, attrs ...slog.Attr)

func Fatal

func Fatal(msg string, attrs ...slog.Attr)

Fatal logs at fatal level and exits the program

func FatalContext

func FatalContext(ctx context.Context, msg string, attrs ...slog.Attr)

FatalContext logs at fatal level with context and exits the program

func Info

func Info(msg string, attrs ...slog.Attr)

func InfoContext

func InfoContext(ctx context.Context, msg string, attrs ...slog.Attr)

func Init

func Init(config Config)

Init initializes the global logger with the provided configuration

func LogError

func LogError(ctx context.Context, err error, msg string, attrs ...slog.Attr)

func LogOperation

func LogOperation(ctx context.Context, op Operation, component Component, fn func() error) error

func NewLoggerWithDynamicLevel

func NewLoggerWithDynamicLevel(config Config) (*Logger, *DynamicLevelVar)

NewLoggerWithDynamicLevel creates a logger with dynamic level support

func Trace

func Trace(msg string, attrs ...slog.Attr)

Trace logs at trace level using the default logger

func TraceContext

func TraceContext(ctx context.Context, msg string, attrs ...slog.Attr)

TraceContext logs at trace level with context using the default logger

func Warn

func Warn(msg string, attrs ...slog.Attr)

func WarnContext

func WarnContext(ctx context.Context, msg string, attrs ...slog.Attr)

Types

type Component

type Component string

func (Component) LogValue

func (c Component) LogValue() slog.Value

type Config

type Config struct {
	Level       string `json:"level"`       // debug, info, warn, error
	Format      string `json:"format"`      // text, json
	AddSource   bool   `json:"add_source"`  // whether to add source code information
	Environment string `json:"environment"` // dev, prod, test
}

Config holds logger configuration

func GetConfigFromEnv

func GetConfigFromEnv() Config

GetConfigFromEnv creates a logger configuration based on environment variables

type CustomLevel

type CustomLevel slog.Level

CustomLevel defines a custom log level between existing ones

const (
	LevelTrace CustomLevel = CustomLevel(slog.LevelDebug - 4) // Even more verbose than debug
	LevelFatal CustomLevel = CustomLevel(slog.LevelError + 4) // More severe than error
)

Custom levels between the standard ones

func (CustomLevel) String

func (l CustomLevel) String() string

String returns the string representation of the custom level

type DynamicLevelVar

type DynamicLevelVar struct {
	*slog.LevelVar
}

DynamicLevelVar allows changing log level at runtime

func NewDynamicLevelVar

func NewDynamicLevelVar(initialLevel slog.Level) *DynamicLevelVar

NewDynamicLevelVar creates a new dynamic level variable

func (*DynamicLevelVar) SetFromString

func (d *DynamicLevelVar) SetFromString(level string) bool

SetFromString sets the level from a string representation

type Logger

type Logger struct {
	*slog.Logger
}

Logger is our wrapper around slog.Logger with additional convenience methods

func Default

func Default() *Logger

Default returns the default logger instance

func NewLogger

func NewLogger(config Config) *Logger

NewLogger creates a new logger with the provided configuration

func WithComponent

func WithComponent(component Component) *Logger

func WithContext

func WithContext(ctx context.Context, attrs ...slog.Attr) *Logger

func WithOperation

func WithOperation(op Operation) *Logger

func (*Logger) LogError

func (l *Logger) LogError(ctx context.Context, err error, msg string, attrs ...slog.Attr)

LogError logs an error with stack trace information and structured attributes

func (*Logger) LogOperation

func (l *Logger) LogOperation(ctx context.Context, op Operation, component Component, fn func() error) error

LogOperation logs the start and end of an operation with duration tracking

func (*Logger) WithComponent

func (l *Logger) WithComponent(component Component) *Logger

WithComponent creates a child logger with component context

func (*Logger) WithContext

func (l *Logger) WithContext(ctx context.Context, attrs ...slog.Attr) *Logger

WithContext creates a child logger with key-value context

func (*Logger) WithOperation

func (l *Logger) WithOperation(op Operation) *Logger

WithOperation creates a child logger with operation context

type Operation

type Operation string

LogValuer implementations for consistent representation of custom types

func (Operation) LogValue

func (o Operation) LogValue() slog.Value

type SyncErrorValuer

type SyncErrorValuer struct {
	*errors.SyncError
}

SyncErrorValuer provides structured logging for SyncError

func (SyncErrorValuer) LogValue

func (e SyncErrorValuer) LogValue() slog.Value

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL