ctxlog

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2025 License: Apache-2.0 Imports: 7 Imported by: 9

README

ctxlog

GoDoc Test Lint Security Go Report Card

A Go library for embedding and extracting *slog.Logger instances in context.Context with conditional activation and hierarchical scoping.

Overview

Large Go applications face two critical logging challenges:

Context Preservation: As your application grows, maintaining logging context across function boundaries becomes essential. Without proper logger propagation, you lose valuable correlation between related operations, making debugging and monitoring difficult.

Noise Reduction: In complex codebases, enabling all logging creates overwhelming noise. You need granular control to focus on specific components or features without modifying code or redeploying.

ctxlog addresses these challenges by:

  • Seamless Context Propagation: Embed loggers with request IDs, trace IDs, or user context in context.Context and automatically propagate them through your entire call stack
  • Surgical Debugging: Use scopes to enable detailed logging only for specific components, features, or code paths without affecting the rest of your application
  • Zero-Code Activation: Control logging granularity through environment variables or runtime configuration without touching your application code

Features

Core Functionality
  • Context-based logger propagation: Embed and extract loggers from context
  • Conditional activation: Enable/disable logging based on environment variables
  • Hierarchical scoping: Create parent-child scope relationships with inheritance
Advanced Control
  • Probabilistic sampling: Reduce log volume with configurable sampling rates
    • Crypto-secure random (default) or fast pseudo-random for performance
  • Dynamic control: Runtime scope activation/deactivation
  • Test utilities: Capture log output for testing

Installation

go get github.com/m-mizutani/ctxlog

Basic Usage

Context Logger Propagation
ctx := context.Background()
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

// Embed logger in context
ctx = ctxlog.With(ctx, logger)

// Extract logger from context
logger = ctxlog.From(ctx)
logger.Info("Hello, World!")
Scope-based Conditional Logging
// Create scope activated by environment variable
scope := ctxlog.NewScope("api", ctxlog.EnabledBy("DEBUG_API"))

// Logger is active only when DEBUG_API is set
logger := ctxlog.From(ctx, scope)
logger.Info("API call started") // Only logged if DEBUG_API exists
Multiple Environment Variables
// Scope is active if ANY environment variable is set
scope := ctxlog.NewScope("debug",
    ctxlog.EnabledBy("DEBUG_MODE", "VERBOSE", "DEV_ENV"))

// Active if DEBUG_MODE OR VERBOSE OR DEV_ENV is set
logger := ctxlog.From(ctx, scope)
Hierarchical Scopes
// Create parent scope
apiScope := ctxlog.NewScope("api", ctxlog.EnabledBy("DEBUG_API"))

// Create child scope - inherits parent activation
userScope := apiScope.NewChild("user", ctxlog.EnabledBy("DEBUG_USER"))

// Child is active if parent is active OR own conditions are met
logger := ctxlog.From(ctx, userScope)
Dynamic Scope Control
scope := ctxlog.NewScope("feature")

// Enable scope in context
ctx = ctxlog.EnableScope(ctx, scope)
logger := ctxlog.From(ctx, scope) // Active

// Enable scope globally
ctxlog.EnableScopeGlobal(scope)
logger = ctxlog.From(ctx, scope) // Active globally

// Disable scope globally
ctxlog.DisableScopeGlobal(scope)
Probabilistic Sampling
// Log only 10% of messages
logger := ctxlog.From(ctx, ctxlog.WithSampling(0.1))

// Use fast pseudo-random for better performance
logger = ctxlog.From(ctx, 
    ctxlog.WithSampling(0.1),
    ctxlog.WithFastRand()) // Uses math/rand instead of crypto/rand
Conditional Logging
// Enable logging based on custom condition
logger := ctxlog.From(ctx, ctxlog.WithCond(func() bool {
    return time.Now().Hour() < 12 // Only log in morning
}))
Test Utilities
func TestMyFunction(t *testing.T) {
    ctx := context.Background()
    
    // Capture log output
    ctx, capture := ctxlog.NewCapture(ctx)
    
    // Run function that logs
    MyFunction(ctx)
    
    // Verify log messages
    messages := capture.Messages()
    if len(messages) == 0 {
        t.Error("Expected log messages")
    }
}

Scope Activation Logic

Scopes use OR logic for activation conditions. A scope is active if ANY of these conditions are met:

  1. Dynamic enablement: EnableScope(ctx, scope) or EnableScopeGlobal(scope)
  2. Parent activation: Parent scope is active (for child scopes)
  3. Environment variables: Any specified environment variable exists (via EnabledBy)
Environment Variable Behavior
  • Multiple environment variables are checked with OR logic
  • Variable existence matters, not value (export DEBUG="" still activates)
  • Uses os.LookupEnv() for checking

Performance Considerations

  • Crypto-secure random: Default sampling uses crypto/rand for security
  • Fast random: Use WithFastRand() with sampling for better performance
  • Buffered generation: Crypto random numbers are buffered for efficiency
  • Scope caching: Scope activation results are cached per context

Examples

See the examples/ directory for complete working examples:

  • examples/basic/ - Basic logger propagation
  • examples/scopes/ - Scope-based activation
  • examples/sampling/ - Probabilistic sampling
  • examples/hierarchical/ - Hierarchical scopes
  • examples/testing/ - Test utilities

License

Apache License 2.0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DisableScopeGlobal

func DisableScopeGlobal(scopes ...*Scope)

DisableScopeGlobal disables the given scopes globally

func EnableScope

func EnableScope(ctx context.Context, scopes ...*Scope) context.Context

EnableScope returns a new context with the given scopes enabled

func EnableScopeGlobal

func EnableScopeGlobal(scopes ...*Scope)

EnableScopeGlobal dynamically enables the given scopes globally

func From

func From(ctx context.Context, options ...Option) *slog.Logger

From extracts a logger from the context with optional configuration. If no logger is found, returns slog.Default().

func With

func With(ctx context.Context, logger *slog.Logger) context.Context

With embeds a logger into the context and returns a new context.

Types

type Capture

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

Capture holds captured log records for testing.

func NewCapture

func NewCapture(ctx context.Context) (context.Context, *Capture)

NewCapture creates a new context with log capture capability.

func (*Capture) Messages

func (c *Capture) Messages() []string

Messages returns all captured log messages.

func (*Capture) Records

func (c *Capture) Records() []slog.Record

Records returns all captured log records.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option represents configuration options for logger creation

func WithCond

func WithCond(condition func() bool) Option

WithCond creates an option to enable conditional logging

func WithFastRand

func WithFastRand() Option

WithFastRand creates an option to use fast pseudo-random numbers for sampling instead of cryptographically secure random numbers for better performance

func WithSampling

func WithSampling(rate float64) Option

WithSampling creates an option to enable probabilistic logging

type Scope

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

Scope represents a logging scope with hierarchical support

func GetGlobalEnabledScopes

func GetGlobalEnabledScopes() []*Scope

GetGlobalEnabledScopes returns the globally enabled scopes

func NewScope

func NewScope(name string, options ...ScopeOption) *Scope

NewScope creates a new scope with the given name and options.

Scope activation behavior: Multiple options are combined with OR logic - if ANY condition is met, the scope is active. Available activation conditions: 1. Dynamic enablement via EnableScope(ctx, scope) or EnableScopeGlobal(scope) 2. Parent scope activation (children inherit parent's active state) 3. Environment variable existence (via EnabledBy option)

Combined options examples:

Example 1: Multiple environment variables

scope := ctxlog.NewScope("api", ctxlog.EnabledBy("DEBUG_API", "TRACE_API", "DEV_MODE"))
// Active if ANY of DEBUG_API, TRACE_API, or DEV_MODE is set

Example 2: Single environment variable

scope := ctxlog.NewScope("debug", ctxlog.EnabledBy("DEBUG_MODE"))
// Active if DEBUG_MODE env var is set (any value, even empty)

Example 3: No options (manual activation only)

scope := ctxlog.NewScope("manual")
// Only active via EnableScope(ctx, scope) or EnableScopeGlobal(scope)

func (*Scope) Name

func (s *Scope) Name() string

Name returns the name of the scope

func (*Scope) NewChild

func (s *Scope) NewChild(name string, options ...ScopeOption) *Scope

NewChild creates a child scope with hierarchical naming

type ScopeOption

type ScopeOption func(*scopeConfig)

ScopeOption defines a functional option for Scope configuration

func EnabledBy

func EnabledBy(envVars ...string) ScopeOption

EnabledBy creates a ScopeOption that enables scope activation via environment variables.

Multiple environment variables behavior:

  • If ANY of the specified environment variables is set (even to empty string), the scope will be activated.
  • Environment variables are checked with os.LookupEnv(), so existence matters, not value.

Example:

scope := ctxlog.NewScope("api", ctxlog.EnabledBy("DEBUG_API", "VERBOSE_API"))
// Scope is active if either DEBUG_API OR VERBOSE_API is set

export DEBUG_API=1     # scope is active
export VERBOSE_API=""  # scope is active (empty value still counts)
unset DEBUG_API VERBOSE_API  # scope is inactive

Jump to

Keyboard shortcuts

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