slog

package module
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2025 License: MIT Imports: 5 Imported by: 49

README

slog

Go Reference Go Report Card codecov Socket Badge

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() {
    // Create a logger (using discard handler for example)
    logger := discard.New()

    // Log with different levels
    logger.Info().Print("Application started")

    // Add fields
    logger.Debug().
        WithField("user", "john").
        WithField("action", "login").
        Print("User logged in")

    // Use Printf-style formatting
    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:

  1. Setting the log level
  2. Optionally adding fields and call stack information
  3. 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:

  1. Debug: Detailed information for developers.
  2. Info: General informational messages.
  3. Warn: Warning messages for potentially harmful situations.
  4. Error: Error conditions that allow continued operation.
  5. Fatal: Critical errors that terminate the program (like log.Fatal()).
  6. 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 {
    // Expensive debug logging
    log.WithField("details", expensiveOperation()).Print("Debug info")
} else if log, ok := logger.Info().WithEnabled(); ok {
    // Simpler info logging
    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()
// ... perform some work ...

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:

// Create a base logger with common fields
baseLogger := logger.WithField("service", "api")

// Branch off for different request handlers
userLogger := baseLogger.WithField("handler", "user")
adminLogger := baseLogger.WithField("handler", "admin")

// Each logger maintains its own field chain
userLogger.Info().Print("Processing user request")
// Output includes: service=api, handler=user

adminLogger.Info().Print("Processing admin request")
// Output includes: service=api, handler=admin

// Original logger is unchanged
baseLogger.Info().Print("Base logger message") // only has service=api

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).  // 0 = current function
    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"
)

// Assuming you have a slog logger instance
// var logger slog.Logger

// Create a standard logger that writes to slog
stdLogger := slog.NewStdLogger(logger, "[HTTP]", log.LstdFlags)

// Use with libraries expecting *log.Logger
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.Loggerslog.Logger (use logr as slog backend)
    • slog.Loggerlogr.Logger (use slog as logr backend)
  • logrus: Bidirectional adapter for Sirupsen/logrus.
    • logrus.Loggerslog.Logger (use logrus as slog backend)
    • slog.Loggerlogrus.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()
    
        // Use logger in your code
        myFunction(logger)
    
        // Verify what was logged
        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.

Documentation

Overview

Package slog provides a backend agnostic interface for structured logs

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewStdLogger

func NewStdLogger(l Logger, prefix string, flags int) *log.Logger

NewStdLogger creates a standard *log.Logger using a slog.Logger behind the scenes

func WithLogger added in v0.5.13

func WithLogger(ctx context.Context, logger Logger) context.Context

WithLogger attaches a Logger to the given context.

Types

type Fields

type Fields map[string]any

Fields is sugar syntax for WithFields() for those who believe log.WithFields(slog.Fields{foo: bar}) is nicer than log.WithFields(map[string]any{foo: var})

type LogLevel

type LogLevel int8

LogLevel represents the level of criticality of a log entry

const (
	// UndefinedLevel is a placeholder for the zero-value when no level has been set
	UndefinedLevel LogLevel = iota

	// Panic represents a log entry for a fatal problem that could be stopped by defer/recover
	Panic
	// Fatal represents a log entry for a problem we can't recover
	Fatal
	// Error represents a log entry for a problem we can recover
	Error
	// Warn represents a log entry for something that might not a problem but it's worth mentioning
	Warn
	// Info represents a log entry just to tell what we are doing
	Info
	// Debug represents a log entry that contains information important mostly only to developers
	Debug

	// ErrorFieldName is the preferred field label for errors
	ErrorFieldName = "error"
)

type LogWriter

type LogWriter struct {
	// contains filtered or unexported fields
}

LogWriter is a io.Writer that calls a given function to log each Write() call

func NewLogWriter

func NewLogWriter(l Logger, fn LogWriterFunc) *LogWriter

NewLogWriter creates a new LogWriter with the given slog.Logger and handler function

func (*LogWriter) Write

func (w *LogWriter) Write(p []byte) (n int, err error)

type LogWriterFunc

type LogWriterFunc func(Logger, string) error

LogWriterFunc is the prototype of the functions used to process and log Write() calls

type Logger

type Logger interface {
	Debug() Logger // Debug is an alias of WithLevel(Debug)
	Info() Logger  // Info is an alias of WithLevel(Info)
	Warn() Logger  // Warn is an alias of WithLevel(Warn)
	Error() Logger // Error is an alias of WithLevel(Error)
	Fatal() Logger // Fatal is an alias of WithLevel(Fatal)
	Panic() Logger // Panic is an alias of WithLevel(Panic)

	// Print adds a log entry handled in the manner of fmt.Print
	Print(...any)
	// Println adds a log entry handled in the manner of fmt.Println
	Println(...any)
	// Printf adds a log entry handled in the manner of fmt.Printf
	Printf(string, ...any)

	// WithLevel returns a new log context set to add entries to the specified level
	WithLevel(LogLevel) Logger

	// WithStack attaches a call stack a log context
	WithStack(int) Logger
	// WithField attaches a field to a log context
	WithField(string, any) Logger
	// WithFields attaches a set of fields to a log context
	WithFields(map[string]any) Logger

	// Enabled tells if the Logger would actually log
	Enabled() bool

	// WithEnabled tells if Enabled but also passes a reference to
	// the logger for convenience when choosing what to log
	//
	// e.g.
	// if log, ok := logger.Debug().WithEnabled(); ok {
	//    log.Print("Let's write detailed debug stuff")
	// } elseif log, ok := logger.Info().WithEnabled(); ok {
	//    log.Print("Let's write info stuff instead")
	// }
	WithEnabled() (Logger, bool)
}

Logger is a backend agnostic interface for structured logs

func GetLogger added in v0.5.13

func GetLogger(ctx context.Context) (Logger, bool)

GetLogger attempts to extract a Logger from the given context.

Directories

Path Synopsis
handlers
mock
Package mock provides a mock logger implementation for testing slog handlers.
Package mock provides a mock logger implementation for testing slog handlers.
cblog module
discard module
filter module
logr module
logrus module
zap module
zerolog module
Package internal provides core data structures and utilities for the slog library.
Package internal provides core data structures and utilities for the slog library.
testing
Package testing provides shared test utilities for slog handler testing.
Package testing provides shared test utilities for slog handler testing.

Jump to

Keyboard shortcuts

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