loggergo

package module
v1.8.2 Latest Latest
Warning

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

Go to latest
Published: Jan 15, 2026 License: Apache-2.0 Imports: 8 Imported by: 8

README

LoggerGo

GitHub tag (with filter) Go Reference Maintainability

A lightweight, customizable logging library for Go, designed to provide flexible and production-ready logging capabilities with OpenTelemetry integration.

Features

  • 🚀 Simple API - Easy-to-use interface built on Go's standard log/slog
  • 📊 Multiple Output Modes - Console, OpenTelemetry, or fanout to both
  • 🎨 Flexible Formats - JSON, text, or OTEL format with dev-friendly flavors
  • 🔍 OpenTelemetry Integration - Automatic trace ID and span ID injection
  • 🧵 Thread-Safe - Safe for concurrent use across goroutines
  • 🛡️ Robust Error Handling - Graceful degradation and comprehensive validation
  • 📝 Context-Aware - Automatic extraction of values from context
  • Dynamic Level Changes - Adjust log levels at runtime
  • Production-Ready - Extensively tested with property-based tests

Architecture

flowchart TD
    A[User Code] --> B[Config.Validate]
    B -->|valid| C[loggergo.Init]
    B -->|invalid| D[Validation Error]
    
    C --> E[Thread-Safe Config Access]
    E --> F{Output Mode}
    
    F -->|console| G[Console Mode + Error Handling]
    F -->|otel| H[OTEL Mode + Fallback]
    F -->|fanout| I[Fanout Mode + Partial Failure Handling]
    
    G --> J[Handler Selection]
    H --> K[OTEL Bridge + Retry Logic]
    I --> L[Multi-Handler with Error Isolation]
    
    J --> M[Context Handler Wrapper]
    K --> M
    L --> M
    
    M --> N[slog.Logger]
    N --> O[Output Destinations]
    
    style B fill:#b2f2bb
    style D fill:#ffc9c9
    style E fill:#a5d8ff
    style K fill:#ffec99

Installation

go get github.com/wasilak/loggergo

Quick Start

package main

import (
    "context"
    "log/slog"
    "github.com/wasilak/loggergo"
)

func main() {
    ctx := context.Background()
    
    // Minimal configuration
    config := loggergo.Config{
        Level: slog.LevelInfo,
    }
    
    ctx, logger, err := loggergo.Init(ctx, config)
    if err != nil {
        panic(err)
    }
    
    logger.Info("Hello, LoggerGo!")
    logger.Debug("This won't be logged (level is Info)")
    logger.Error("Something went wrong", "error", "example error")
}

Common Usage Patterns

Console Logging with JSON Format
config := loggergo.Config{
    Level:  slog.LevelInfo,
    Format: loggergo.Types.LogFormatJSON,
    Output: loggergo.Types.OutputConsole,
}
ctx, logger, err := loggergo.Init(ctx, config)
Development Mode with Pretty Output
config := loggergo.Config{
    Level:     slog.LevelDebug,
    Format:    loggergo.Types.LogFormatText,
    Output:    loggergo.Types.OutputConsole,
    DevMode:   true,
    DevFlavor: loggergo.Types.DevFlavorTint, // or DevFlavorSlogor, DevFlavorDevslog
}
ctx, logger, err := loggergo.Init(ctx, config)
OpenTelemetry Integration
config := loggergo.Config{
    Level:              slog.LevelInfo,
    Output:             loggergo.Types.OutputOtel,
    OtelLoggerName:     "myapp/logger",
    OtelServiceName:    "myapp",
    OtelTracingEnabled: true,
}
ctx, logger, err := loggergo.Init(ctx, config)
// Logs will include trace_id and span_id when available
Fanout Mode (Console + OTEL)
config := loggergo.Config{
    Level:           slog.LevelInfo,
    Format:          loggergo.Types.LogFormatJSON,
    Output:          loggergo.Types.OutputFanout,
    OtelLoggerName:  "myapp/logger",
    OtelServiceName: "myapp",
}
ctx, logger, err := loggergo.Init(ctx, config)
// Logs go to both console and OTEL
Context-Aware Logging
type contextKey string
const requestIDKey contextKey = "request_id"

config := loggergo.Config{
    Level:              slog.LevelInfo,
    ContextKeys:        []interface{}{requestIDKey},
    ContextKeysDefault: "unknown",
}
ctx, logger, err := loggergo.Init(ctx, config)

// Add value to context
ctx = context.WithValue(ctx, requestIDKey, "req-123")

// This log will automatically include request_id: "req-123"
logger.InfoContext(ctx, "Processing request")
Dynamic Log Level Changes
ctx, logger, err := loggergo.Init(ctx, config)

// Get the level accessor
levelVar := loggergo.GetLogLevelAccessor()

// Change level at runtime (thread-safe)
levelVar.Set(slog.LevelDebug)
logger.Debug("Now this will be logged")

levelVar.Set(slog.LevelWarn)
logger.Info("This won't be logged anymore")

Configuration Reference

Field Type Default Description
Level slog.Leveler slog.LevelInfo Log level (Debug, Info, Warn, Error)
Format LogFormat LogFormatJSON Output format (JSON, Text, OTEL)
Output OutputType OutputConsole Output mode (Console, OTEL, Fanout)
DevMode bool false Enable development mode with pretty output
DevFlavor DevFlavor DevFlavorTint Dev format flavor (Tint, Slogor, Devslog)
OutputStream io.Writer os.Stdout Output destination
SetAsDefault bool true Register as slog.Default()
OtelTracingEnabled bool true Enable OTEL tracing
OtelLoggerName string "my/pkg/name" OTEL logger name (required for OTEL/Fanout)
OtelServiceName string "my-service" OTEL service name (required for OTEL/Fanout)
ContextKeys []interface{} [] Keys to extract from context
ContextKeysDefault interface{} nil Default value for missing context keys
Configuration Validation

All configurations are automatically validated before initialization:

config := loggergo.Config{
    Level:  nil, // Invalid: Level cannot be nil
    Output: loggergo.Types.OutputOtel,
    // Missing: OtelLoggerName and OtelServiceName required for OTEL
}

ctx, logger, err := loggergo.Init(ctx, config)
if err != nil {
    // err will be *types.InitError with detailed validation failures
    fmt.Printf("Configuration error: %v\n", err)
}

Troubleshooting

Issue: OTEL initialization fails

Symptom: Warning message "OTEL initialization failed, falling back to console mode"

Solution: LoggerGo automatically falls back to console mode when OTEL setup fails. Check:

  • OTEL endpoint is reachable
  • OtelLoggerName and OtelServiceName are set correctly
  • OTEL environment variables are configured
Issue: Boolean fields not merging correctly

Symptom: Boolean config values (DevMode, SetAsDefault) unexpectedly change to false

Solution: When using partial config overrides, explicitly set all boolean fields:

// ❌ Wrong: DevMode will be false (Go default)
override := loggergo.Config{Level: slog.LevelDebug}

// ✅ Correct: Explicitly preserve DevMode
override := loggergo.Config{
    Level:   slog.LevelDebug,
    DevMode: true, // Explicitly set
}
Issue: Context values not appearing in logs

Symptom: Context keys configured but values don't appear in log output

Solution: Ensure you're using *Context methods:

// ❌ Wrong: Context not passed
logger.Info("message")

// ✅ Correct: Context passed
logger.InfoContext(ctx, "message")
Issue: Logs not appearing

Symptom: No log output

Solution: Check log level filtering:

config := loggergo.Config{
    Level: slog.LevelWarn, // Only Warn and Error will be logged
}
logger.Info("This won't appear") // Info < Warn
logger.Error("This will appear") // Error >= Warn

Migration Guide

Migrating from v1.x to v2.0
Breaking Changes
  1. Configuration Validation: Configs are now validated automatically. Invalid configs will return errors instead of panicking.
// v1.x: Would panic on invalid config
ctx, logger, _ := loggergo.Init(ctx, invalidConfig)

// v2.0: Returns error
ctx, logger, err := loggergo.Init(ctx, invalidConfig)
if err != nil {
    // Handle validation error
}
  1. Error Types: Init now returns *types.InitError with detailed context.
// v2.0: Check error type
if err != nil {
    var initErr *types.InitError
    if errors.As(err, &initErr) {
        fmt.Printf("Failed at %s: %v\n", initErr.Stage, initErr.Cause)
    }
}
  1. Thread-Safe Config Access: Use GetConfig() instead of accessing global variables.
// v1.x: Direct access (not thread-safe)
// currentLevel := lib.libConfig.Level

// v2.0: Thread-safe accessor
config := loggergo.GetConfig()
currentLevel := config.Level
New Features in v2.0
  • ✅ Automatic configuration validation
  • ✅ Graceful OTEL fallback
  • ✅ Thread-safe configuration management
  • ✅ Comprehensive error handling
  • ✅ Property-based testing
  • ✅ Enhanced documentation

Examples

See the examples/ directory for complete working examples:

Migration Guide

Migrating from v1.x to v2.x

Version 2.0 introduces several improvements while maintaining backward compatibility for most use cases. Here's what you need to know:

Breaking Changes

1. Configuration Validation

v2.0 introduces automatic configuration validation. Invalid configurations that previously failed silently or at runtime now fail early during initialization:

// v1.x - Invalid config might fail later or behave unexpectedly
config := loggergo.Config{
    // Missing required Level field
    Output: types.OutputConsole,
}
ctx, logger, err := loggergo.Init(ctx, config)

// v2.x - Validation catches this immediately
config := loggergo.Config{
    // Missing required Level field
    Output: types.OutputConsole,
}
ctx, logger, err := loggergo.Init(ctx, config)
// err will be: "configuration validation failed: Level cannot be nil"

Fix: Ensure all required fields are set:

config := loggergo.Config{
    Level:  slog.LevelInfo,  // Now required
    Output: types.OutputConsole,
}

2. Deprecated Functions Moved

Helper functions have been moved from lib package to types package:

// v1.x
import "github.com/wasilak/loggergo/lib"

flavor := lib.DevFlavorFromString("tint")
level := lib.LogLevelFromString("info")
format := lib.LogFormatFromString("json")
output := lib.OutputTypeFromString("console")

// v2.x - Preferred approach
import "github.com/wasilak/loggergo/lib/types"

flavor := types.DevFlavorFromString("tint")
level := types.LogLevelFromString("info")
format := types.LogFormatFromString("json")
output := types.OutputTypeFromString("console")

Note: The old lib.*FromString functions still work but are deprecated and will be removed in v3.0.

3. Error Handling Improvements

Errors now include more context and are wrapped for better debugging:

// v1.x - Generic error
err := loggergo.Init(ctx, config)
// err: "invalid configuration"

// v2.x - Detailed error with context
err := loggergo.Init(ctx, config)
// err: "logger initialization failed at validation: Level cannot be nil"

// You can unwrap to get the underlying error
var initErr *loggergo.InitError
if errors.As(err, &initErr) {
    fmt.Printf("Failed at stage: %s\n", initErr.Stage)
    fmt.Printf("Cause: %v\n", initErr.Cause)
}
New Features

1. Configuration Validation Method

You can now validate configuration before initialization:

config := loggergo.Config{
    Level:  slog.LevelInfo,
    Output: types.OutputConsole,
}

// Validate before using
if err := config.Validate(); err != nil {
    log.Fatalf("Invalid config: %v", err)
}

ctx, logger, err := loggergo.Init(ctx, config)

2. Thread-Safe Configuration Access

Configuration is now protected by RWMutex for safe concurrent access:

// Safe to call from multiple goroutines
currentConfig := loggergo.GetConfig()

// Safe to update from multiple goroutines
loggergo.SetConfig(newConfig)

3. Graceful OTEL Degradation

If OpenTelemetry initialization fails, the logger now falls back to console mode instead of failing completely:

config := loggergo.Config{
    Level:  slog.LevelInfo,
    Output: types.OutputOtel,
    OtelServiceName: "my-service",
    // If OTEL endpoint is unreachable, falls back to console
}

4. Enhanced Context Handling

Context extraction is now more robust with nil-safety and better error handling:

config := loggergo.Config{
    Level:       slog.LevelInfo,
    Output:      types.OutputConsole,
    ContextKeys: []interface{}{"request_id", "user_id"},
    ContextKeysDefault: "unknown",  // Used when key is missing
}

// Safe even with nil context
logger.InfoContext(nil, "message")  // Won't panic
Version Compatibility Matrix
Version Go Version Status Breaking Changes Notes
v2.1.x 1.21+ Current None Added migration guide
v2.0.x 1.21+ Stable Configuration validation, deprecated functions Recommended for new projects
v1.x.x 1.20+ Maintenance N/A Security fixes only
  1. Update imports - Change lib.*FromString to types.*FromString
  2. Add validation - Ensure all required config fields are set
  3. Test thoroughly - Run go test -race ./... to catch any issues
  4. Update error handling - Take advantage of new wrapped errors
  5. Review logs - Check for deprecation warnings in your logs
Getting Help

If you encounter issues during migration:

  1. Check the examples/ directory for updated patterns
  2. Review the API documentation
  3. Open an issue on GitHub

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes with tests
  4. Run go test ./... and go test -race ./...
  5. Submit a pull request

License

See LICENSE file for details.

Documentation

Overview

Package loggergo provides a lightweight, customizable logging library for Go applications.

LoggerGo is built on top of Go's standard log/slog package and provides:

  • Multiple output modes (console, OpenTelemetry, fanout)
  • Multiple log formats (JSON, text, OTEL)
  • Development mode with different flavors (tint, slogor, devslog)
  • Context-aware logging with automatic value extraction
  • OpenTelemetry integration with trace/span ID injection
  • Dynamic log level adjustment
  • Thread-safe configuration management
  • Comprehensive error handling with graceful degradation

Quick Start:

import (
    "context"
    "log/slog"
    "github.com/wasilak/loggergo"
)

func main() {
    ctx := context.Background()
    config := loggergo.Config{
        Level:  slog.LevelInfo,
        Format: loggergo.LogFormatJSON,
        Output: loggergo.OutputConsole,
    }
    ctx, logger, err := loggergo.Init(ctx, config)
    if err != nil {
        panic(err)
    }
    logger.Info("Hello, LoggerGo!")
}

For more examples, see the examples/ directory in the repository.

Index

Constants

This section is empty.

Variables

View Source
var Types = struct {
	AllDevFlavors       func() []types.DevFlavor
	DevFlavorFromString func(string) types.DevFlavor
	DevFlavorTint       types.DevFlavor
	DevFlavorSlogor     types.DevFlavor
	DevFlavorDevslog    types.DevFlavor

	AllLogFormats       func() []types.LogFormat
	LogFormatFromString func(string) types.LogFormat
	LogFormatText       types.LogFormat
	LogFormatJSON       types.LogFormat
	LogFormatOtel       types.LogFormat

	AllLogLevels       func() []slog.Level
	LogLevelFromString func(string) slog.Level

	AllOutputTypes       func() []types.OutputType
	OutputTypeFromString func(string) types.OutputType
	OutputConsole        types.OutputType
	OutputOtel           types.OutputType
	OutputFanout         types.OutputType
}{
	AllDevFlavors:       types.AllDevFlavors,
	DevFlavorFromString: types.DevFlavorFromString,
	DevFlavorTint:       types.DevFlavorTint,
	DevFlavorSlogor:     types.DevFlavorSlogor,
	DevFlavorDevslog:    types.DevFlavorDevslog,

	AllLogFormats:       types.AllLogFormats,
	LogFormatFromString: types.LogFormatFromString,
	LogFormatText:       types.LogFormatText,
	LogFormatJSON:       types.LogFormatJSON,
	LogFormatOtel:       types.LogFormatOtel,

	AllLogLevels:       types.AllLogLevels,
	LogLevelFromString: types.LogLevelFromString,

	AllOutputTypes:       types.AllOutputTypes,
	OutputTypeFromString: types.OutputTypeFromString,
	OutputConsole:        types.OutputConsole,
	OutputOtel:           types.OutputOtel,
	OutputFanout:         types.OutputFanout,
}

Types provides access to all type constants, enums, and conversion functions.

This struct exports all the type-related functionality from the internal types package, making it easy to access log formats, output types, dev flavors, and their conversion functions.

Example usage:

// Using constants
config := loggergo.Config{
    Format:    loggergo.Types.LogFormatJSON,
    Output:    loggergo.Types.OutputConsole,
    DevFlavor: loggergo.Types.DevFlavorTint,
}

// Using conversion functions
format := loggergo.Types.LogFormatFromString("json")
output := loggergo.Types.OutputTypeFromString("console")

// Getting all available values
allFormats := loggergo.Types.AllLogFormats()
for _, format := range allFormats {
    fmt.Println(format)
}

Functions

func GetConfig added in v1.8.0

func GetConfig() types.Config

GetConfig returns the current logger configuration. This function is thread-safe and returns a copy of the configuration.

func GetLogLevelAccessor added in v1.5.5

func GetLogLevelAccessor() *slog.LevelVar

GetLogLevelAccessor returns the log level accessor for dynamic level changes.

Thread-Safety Guarantees: This function is safe for concurrent use. The returned *slog.LevelVar uses atomic operations internally, making it safe to call Set() and Level() methods from multiple goroutines simultaneously without additional synchronization.

Example usage:

levelVar := loggergo.GetLogLevelAccessor()
levelVar.Set(slog.LevelDebug) // Thread-safe level change

func Init added in v1.6.0

func Init(ctx context.Context, config types.Config, additionalAttrs ...any) (retCtx context.Context, retLogger *slog.Logger, retErr error)

Init initializes a logger with the provided configuration and additional attributes.

It validates the configuration, creates the appropriate handler based on the output mode, and returns the updated context, configured logger, and any error encountered.

The logger is automatically set as slog.Default() if Config.SetAsDefault is true (default).

Parameters:

  • ctx: The context to use for initialization and OTEL setup
  • config: The logger configuration (see Config for details)
  • additionalAttrs: Optional additional attributes to add to all log entries

Returns:

  • context.Context: Updated context (may include OTEL trace context)
  • *slog.Logger: Configured logger instance
  • error: InitError if initialization fails, nil on success

Error Handling:

Init never panics. All panics are recovered and returned as InitError with stage "panic_recovery". If OTEL mode fails, Init gracefully degrades to console mode with a warning to stderr.

Thread Safety:

Init is safe to call concurrently from multiple goroutines. However, only one initialization should typically be performed per application.

Example:

config := loggergo.Config{
    Level:  slog.LevelInfo,
    Format: loggergo.LogFormatJSON,
    Output: loggergo.OutputConsole,
}
ctx, logger, err := loggergo.Init(ctx, config)
if err != nil {
    log.Fatalf("Failed to initialize logger: %v", err)
}
logger.Info("Logger initialized successfully")

func Shutdown added in v1.8.0

func Shutdown() error

Shutdown performs cleanup of all registered resources.

This function should be called when the application is shutting down to ensure proper cleanup of resources like OTEL providers, file handles, etc.

Cleanup functions are called in reverse order of registration (LIFO). If any cleanup function returns an error, Shutdown continues with remaining cleanup functions and returns a combined error at the end.

Thread Safety:

Shutdown is safe to call concurrently, but should typically only be called once during application shutdown.

Example:

ctx, logger, err := loggergo.Init(ctx, config)
if err != nil {
    panic(err)
}
defer loggergo.Shutdown()

// Use logger...

Types

type Config added in v1.2.4

type Config = types.Config

Config represents the configuration options for the logger. It is an alias for types.Config and is exported for external usage.

See types.Config for detailed field documentation and usage examples.

type CustomContextAttributeHandler added in v1.6.0

type CustomContextAttributeHandler struct {
	ContextKeysDefault interface{}
	// contains filtered or unexported fields
}

CustomContextAttributeHandler wraps an existing slog.Handler and automatically extracts values from context.Context to add as attributes to all log records.

This handler enables context-aware logging by extracting specified keys from the context and including them in every log entry. It handles nil contexts, missing keys, and type conversion gracefully.

Thread Safety:

CustomContextAttributeHandler is safe for concurrent use. Multiple goroutines can call Handle, Enabled, WithAttrs, and WithGroup simultaneously without additional synchronization.

func NewCustomContextAttributeHandler added in v1.6.0

func NewCustomContextAttributeHandler(handler slog.Handler, keys []interface{}, contextKeysDefault interface{}) *CustomContextAttributeHandler

NewCustomContextAttributeHandler creates a new handler that wraps the given handler and automatically extracts context values.

Parameters:

  • handler: The underlying slog.Handler to wrap
  • keys: Slice of context keys to extract from context.Context
  • contextKeysDefault: Default value to use when a key is not found in context (can be nil)

Returns:

  • *CustomContextAttributeHandler: A new handler that extracts context values

Example:

type contextKey string
const requestIDKey contextKey = "request_id"

handler := slog.NewJSONHandler(os.Stdout, nil)
contextHandler := NewCustomContextAttributeHandler(
    handler,
    []interface{}{requestIDKey},
    "unknown",
)
logger := slog.New(contextHandler)

ctx := context.WithValue(context.Background(), requestIDKey, "req-123")
logger.InfoContext(ctx, "Processing request") // Will include request_id: "req-123"

func (*CustomContextAttributeHandler) Enabled added in v1.6.0

Enabled reports whether the handler handles records at the given level. It delegates the check to the inner handler.

func (*CustomContextAttributeHandler) Handle added in v1.6.0

func (h *CustomContextAttributeHandler) Handle(ctx context.Context, record slog.Record) (err error)

Handle processes a log record by extracting context values and delegating to the inner handler.

It extracts values for all configured keys from the context and adds them as attributes to the log record. If a key is not found, it uses the default value (if configured) or omits the field.

Error Handling:

Handle never panics. All panics are recovered and returned as errors. If the context is nil, context.Background() is used as a fallback.

Thread Safety:

Handle is safe to call concurrently from multiple goroutines.

func (*CustomContextAttributeHandler) WithAttrs added in v1.6.0

func (h *CustomContextAttributeHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs returns a new handler with the given attributes added. The new handler preserves the context extraction behavior.

func (*CustomContextAttributeHandler) WithGroup added in v1.6.0

func (h *CustomContextAttributeHandler) WithGroup(name string) slog.Handler

WithGroup returns a new handler with the given group name. The new handler preserves the context extraction behavior.

Directories

Path Synopsis
examples
advanced command
context command
otel command
simple command
lib
Package lib provides internal configuration management for the logger.
Package lib provides internal configuration management for the logger.

Jump to

Keyboard shortcuts

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