A Go library for embedding and extracting *slog.Logger instances in context.Context with conditional activation and hierarchical scoping.
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.Contextand 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
- 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
- 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
go get github.com/gaebalai/ctxlogctx := 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!")// 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// 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)// 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)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)// 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// Enable logging based on custom condition
logger := ctxlog.From(ctx, ctxlog.WithCond(func() bool {
return time.Now().Hour() < 12 // Only log in morning
}))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")
}
}Scopes use OR logic for activation conditions. A scope is active if ANY of these conditions are met:
- Dynamic enablement:
EnableScope(ctx, scope)orEnableScopeGlobal(scope) - Parent activation: Parent scope is active (for child scopes)
- Environment variables: Any specified environment variable exists (via
EnabledBy)
- Multiple environment variables are checked with OR logic
- Variable existence matters, not value (
export DEBUG=""still activates) - Uses
os.LookupEnv()for checking
- Crypto-secure random: Default sampling uses
crypto/randfor 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
See the examples/ directory for complete working examples:
examples/basic/- Basic logger propagationexamples/scopes/- Scope-based activationexamples/sampling/- Probabilistic samplingexamples/hierarchical/- Hierarchical scopesexamples/testing/- Test utilities
Apache License 2.0