Documentation
¶
Overview ¶
Package logger provides structured logging functionality for Go applications.
The logger package is designed to provide a standardized logging approach with features such as log levels, contextual logging, distributed tracing integration, and flexible output formatting. It integrates with the fx dependency injection framework for easy incorporation into applications.
Architecture ¶
This package follows the "accept interfaces, return structs" design pattern:
- Logger interface: Defines the contract for logging operations
- LoggerClient struct: Concrete implementation of the Logger interface
- NewLoggerClient constructor: Returns *LoggerClient (concrete type)
- FX module: Provides both *LoggerClient and Logger interface for dependency injection
Core Features:
- Structured logging with key-value pairs
- Support for multiple log levels (Debug, Info, Warn, Error, etc.)
- Context-aware logging for request tracing
- Distributed tracing integration with OpenTelemetry
- Automatic trace and span ID extraction from context
- Configurable output formats (JSON, text)
- Integration with common log collection systems
Direct Usage (Without FX) ¶
For simple applications or tests, create a logger directly:
import "github.com/Aleph-Alpha/std/v1/logger"
// Create a new logger (returns concrete *LoggerClient)
log := logger.NewLoggerClient(logger.Config{
Level: "info",
EnableTracing: true,
})
// Log with structured fields (without context)
log.Info("User logged in", nil, map[string]interface{}{
"user_id": "12345",
"ip": "192.168.1.1",
})
// Log with trace context (automatically includes trace_id and span_id)
log.InfoWithContext(ctx, "Processing request", nil, map[string]interface{}{
"request_id": "abc-123",
})
FX Module Integration ¶
For production applications using Uber's fx, use the FXModule which provides both the concrete type and interface:
import (
"github.com/Aleph-Alpha/std/v1/logger"
"go.uber.org/fx"
)
app := fx.New(
logger.FXModule, // Provides *LoggerClient and logger.Logger interface
fx.Provide(func() logger.Config {
return logger.Config{
Level: "info",
EnableTracing: true,
ServiceName: "my-service",
}
}),
fx.Invoke(func(log *logger.LoggerClient) {
// Use concrete type directly
log.Info("Service started", nil, nil)
}),
// ... other modules
)
app.Run()
Type Aliases in Consumer Code ¶
To simplify your code and avoid tight coupling, use type aliases:
package myapp
import stdLogger "github.com/Aleph-Alpha/std/v1/logger"
// Use type alias to reference std's interface
type Logger = stdLogger.Logger
// Now use Logger throughout your codebase
func MyFunction(log Logger) {
log.Info("Processing", nil, nil)
}
This eliminates the need for adapters and allows you to switch implementations by only changing the alias definition.
Logging Levels ¶
// Log different levels
log.Debug("Debug message", nil, nil) // Only appears if level is Debug
log.Info("Info message", nil, nil)
log.Warn("Warning message", nil, nil)
log.Error("Error message", err, nil)
Context-Aware Logging ¶
// Context-aware logging methods log.DebugWithContext(ctx, "Debug with trace", nil, nil) log.WarnWithContext(ctx, "Warning with trace", nil, nil) log.ErrorWithContext(ctx, "Error with trace", err, nil)
Configuration ¶
The logger can be configured via environment variables:
ZAP_LOGGER_LEVEL=debug # Log level (debug, info, warning, error) LOGGER_ENABLE_TRACING=true # Enable distributed tracing integration
Tracing Integration ¶
When tracing is enabled (EnableTracing: true), the logger will automatically extract trace and span IDs from the context and include them in log entries. This provides correlation between logs and distributed traces in your observability system.
The following fields are automatically added to log entries when tracing is enabled:
- trace_id: The OpenTelemetry trace ID
- span_id: The OpenTelemetry span ID
To use tracing, ensure your application has OpenTelemetry configured and pass context with active spans to the *WithContext logging methods.
Performance Considerations ¶
The logger is designed to be performant with minimal allocations. However, be mindful of excessive debug logging in production environments.
Thread Safety ¶
All methods on the Logger interface are safe for concurrent use by multiple goroutines.
Index ¶
- Constants
- Variables
- func RegisterLoggerLifecycle(lc fx.Lifecycle, client *LoggerClient)
- type Config
- type Logger
- type LoggerClient
- func (l *LoggerClient) Debug(msg string, err error, fields ...map[string]interface{})
- func (l *LoggerClient) DebugWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
- func (l *LoggerClient) Error(msg string, err error, fields ...map[string]interface{})
- func (l *LoggerClient) ErrorWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
- func (l *LoggerClient) Fatal(msg string, err error, fields ...map[string]interface{})
- func (l *LoggerClient) FatalWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
- func (l *LoggerClient) Info(msg string, err error, fields ...map[string]interface{})
- func (l *LoggerClient) InfoWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
- func (l *LoggerClient) Warn(msg string, err error, fields ...map[string]interface{})
- func (l *LoggerClient) WarnWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
Constants ¶
const ( // Debug represents the most verbose logging level, intended for development and troubleshooting. // When the logger is set to Debug level, all log messages (Debug, Info, Warning, Error) will be output. Debug = "debug" // Info represents the standard logging level for general operational information. // When the logger is set to Info level, Info, Warning, and Error messages will be output, but Debug messages will be suppressed. Info = "info" // Warning represents the logging level for potential issues that aren't errors. // When the logger is set to Warning level, only Warning and Error messages will be output. Warning = "warning" // Error represents the logging level for error conditions. // When the logger is set to Error level, only Error messages will be output. Error = "error" )
Log level constants that define the available logging levels. These string constants are used in configuration to set the desired log level.
Variables ¶
var FXModule = fx.Module("logger", fx.Provide( NewLoggerClient, fx.Annotate( func(l *LoggerClient) Logger { return l }, fx.As(new(Logger)), ), ), fx.Invoke(RegisterLoggerLifecycle), )
FXModule defines the Fx module for the logger package. This module integrates the logger into an Fx-based application by providing the logger factory and registering its lifecycle hooks.
The module provides: 1. *LoggerClient (concrete type) for direct use 2. Logger interface for dependency injection 3. Lifecycle management for proper cleanup
Usage:
app := fx.New(
logger.FXModule,
// other modules...
)
Dependencies required by this module: - A logger.Config instance must be available in the dependency injection container
Functions ¶
func RegisterLoggerLifecycle ¶
func RegisterLoggerLifecycle(lc fx.Lifecycle, client *LoggerClient)
RegisterLoggerLifecycle handles cleanup (sync) of the Zap logger. This function registers a shutdown hook with the Fx lifecycle system that ensures any buffered log entries are flushed when the application terminates.
Parameters:
- lc: The Fx lifecycle controller
- client: The logger instance to be managed
The lifecycle hook:
- OnStop: Calls Sync() on the underlying Zap logger to flush any buffered logs to their output destinations before the application terminates
This ensures that no log entries are lost if the application shuts down while logs are still buffered in memory.
Note: This function is automatically invoked by the FXModule and does not need to be called directly in application code.
Types ¶
type Config ¶
type Config struct {
// Level determines the minimum log level that will be output.
// Valid values are:
// - "debug": Most verbose, shows all log messages
// - "info": Shows info, warning, and error messages
// - "warning": Shows only warning and error messages
// - "error": Shows only error messages
//
// The default behavior is:
// - In production environments: defaults to "info"
// - In development environments: defaults to "debug"
// - In other/unspecified environments: defaults to "info"
//
// This setting can be configured via:
// - YAML configuration with the "level" key
// - Environment variable ZAP_LOGGER_LEVEL
Level string `yaml:"level" envconfig:"ZAP_LOGGER_LEVEL"`
// EnableTracing controls whether tracing integration is enabled for logging operations.
// When set to true, the logger will automatically extract trace and span information
// from context and include it in log entries. This provides correlation between
// logs and distributed traces.
//
// When tracing is enabled, the following fields are automatically added to log entries:
// - "trace_id": The trace ID from the current span context
// - "span_id": The span ID from the current span context
//
// This setting can be configured via:
// - YAML configuration with the "enable_tracing" key
// - Environment variable LOGGER_ENABLE_TRACING
EnableTracing bool `yaml:"enable_tracing" envconfig:"LOGGER_ENABLE_TRACING"`
// ServiceName is the name of the service that is logging messages.
// This value is used to populate the "service" field in log entries.
ServiceName string
// CallerSkip controls the number of stack frames to skip when reporting the caller.
// This is useful when you have wrapper layers around the logger.
//
// Guidelines for setting CallerSkip:
// - 1 (default): Use when calling std logger directly from your code
// - 2: Use when you have one additional wrapper layer (e.g., service-specific logger wrapper)
// - 3+: Use when you have multiple wrapper layers
//
// Example call stack with CallerSkip=2:
// Your business logic (this will be reported as caller) ✓
// └─> Your service wrapper calls std logger
// └─> std logger calls zap (skipped)
// └─> zap logs (skipped)
//
// If not set or set to 0, defaults to 1.
CallerSkip int `yaml:"caller_skip" envconfig:"LOGGER_CALLER_SKIP"`
}
Config defines the configuration structure for the logger. It contains settings that control the behavior of the logging system.
type Logger ¶
type Logger interface {
// Debug logs a debug-level message, useful for development and troubleshooting.
Debug(msg string, err error, fields ...map[string]interface{})
// Info logs an informational message about general application progress.
Info(msg string, err error, fields ...map[string]interface{})
// Warn logs a warning message, indicating potential issues.
Warn(msg string, err error, fields ...map[string]interface{})
// Error logs an error message with details of the error.
Error(msg string, err error, fields ...map[string]interface{})
// Fatal logs a critical error message and terminates the application.
Fatal(msg string, err error, fields ...map[string]interface{})
// DebugWithContext logs a debug-level message with trace context.
DebugWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
// InfoWithContext logs an informational message with trace context.
InfoWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
// WarnWithContext logs a warning message with trace context.
WarnWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
// ErrorWithContext logs an error message with trace context.
ErrorWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
// FatalWithContext logs a critical error message with trace context and terminates the application.
FatalWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
}
Logger provides a high-level interface for structured logging. It wraps Uber's Zap logger with a simplified API and optional tracing integration.
This interface is implemented by the concrete *LoggerClient type.
type LoggerClient ¶ added in v0.12.0
type LoggerClient struct {
// Zap is the underlying zap.Logger instance
// This is exposed to allow direct access to Zap-specific functionality
// when needed, but most logging should go through the wrapper methods.
Zap *zap.Logger
// contains filtered or unexported fields
}
LoggerClient is a wrapper around Uber's Zap logger. It provides a simplified interface to the underlying Zap logger, with additional functionality specific to the application's needs.
LoggerClient implements the Logger interface.
func NewLoggerClient ¶
func NewLoggerClient(cfg Config) *LoggerClient
NewLoggerClient initializes and returns a new instance of the logger based on configuration. This function creates a configured Zap logger with appropriate encoding, log levels, and output destinations.
Parameters:
- cfg: Configuration for the logger, including log level, caller skip, and tracing options
Returns:
- *LoggerClient: A configured logger instance ready for use
The logger is configured with:
- JSON encoding for structured logging
- ISO8601 timestamp format
- Capital letter level encoding (e.g., "INFO", "ERROR") without color codes
- Process ID and service name as default fields
- Caller information (file and line) included in log entries
- Configurable caller skip depth for wrapper scenarios
- Output directed to stderr
If initialization fails, the function will call log.Fatal to terminate the application.
Example (direct usage):
loggerConfig := logger.Config{
Level: logger.Info,
ServiceName: "my-service",
CallerSkip: 1, // default, can be omitted
}
log := logger.NewLoggerClient(loggerConfig)
log.Info("Application started", nil, nil)
Example (with service wrapper):
loggerConfig := logger.Config{
Level: logger.Info,
ServiceName: "my-service",
CallerSkip: 2, // skip service wrapper + std wrapper
}
log := logger.NewLoggerClient(loggerConfig)
func (*LoggerClient) Debug ¶ added in v0.12.0
func (l *LoggerClient) Debug(msg string, err error, fields ...map[string]interface{})
Debug logs a debug-level message, useful for development and troubleshooting. Debug logs are typically more verbose and include information primarily useful during development or when diagnosing issues.
Parameters:
- msg: The log message
- err: An error to include in the log entry, or nil if no error
- fields: Variable number of map[string]interface{} containing additional structured data
Example:
logger.Debug("Processing request", nil, map[string]interface{}{
"request_id": "abc-123",
"payload_size": 1024,
"processing_time_ms": 15,
})
func (*LoggerClient) DebugWithContext ¶ added in v0.12.0
func (l *LoggerClient) DebugWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
DebugWithContext logs a debug-level message with trace context, useful for development and troubleshooting. This method automatically extracts trace and span IDs from the provided context when tracing is enabled.
Parameters:
- ctx: The context containing trace information
- msg: The log message
- err: An error to include in the log entry, or nil if no error
- fields: Variable number of map[string]interface{} containing additional structured data
Example:
logger.DebugWithContext(ctx, "Processing request", nil, map[string]interface{}{
"request_id": "abc-123",
"payload_size": 1024,
"processing_time_ms": 15,
})
func (*LoggerClient) Error ¶ added in v0.12.0
func (l *LoggerClient) Error(msg string, err error, fields ...map[string]interface{})
Error logs an error message, including details of the error and additional context fields. Use Error when something has gone wrong that affects the current operation but doesn't require immediate termination of the application.
Parameters:
- msg: The log message
- err: An error to include in the log entry, or nil if no error
- fields: Variable number of map[string]interface{} containing additional structured data
Example:
err := database.Connect()
if err != nil {
logger.Error("Failed to connect to database", err, map[string]interface{}{
"retry_count": 3,
"database": "users",
})
}
func (*LoggerClient) ErrorWithContext ¶ added in v0.12.0
func (l *LoggerClient) ErrorWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
ErrorWithContext logs an error message with trace context, including details of the error and additional context fields. This method automatically extracts trace and span IDs from the provided context when tracing is enabled.
Parameters:
- ctx: The context containing trace information
- msg: The log message
- err: An error to include in the log entry, or nil if no error
- fields: Variable number of map[string]interface{} containing additional structured data
Example:
err := database.Connect()
if err != nil {
logger.ErrorWithContext(ctx, "Failed to connect to database", err, map[string]interface{}{
"retry_count": 3,
"database": "users",
})
}
func (*LoggerClient) Fatal ¶ added in v0.12.0
func (l *LoggerClient) Fatal(msg string, err error, fields ...map[string]interface{})
Fatal logs a critical error message and terminates the application. Use Fatal only for errors that make it impossible for the application to continue running. This method will call os.Exit(1) after logging the message.
Parameters:
- msg: The log message
- err: An error to include in the log entry, or nil if no error
- fields: Variable number of map[string]interface{} containing additional structured data
Example:
configErr := LoadConfiguration()
if configErr != nil {
logger.Fatal("Cannot start application without configuration", configErr, map[string]interface{}{
"config_path": "/etc/myapp/config.json",
})
}
Note: This function does not return as it terminates the application.
func (*LoggerClient) FatalWithContext ¶ added in v0.12.0
func (l *LoggerClient) FatalWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
FatalWithContext logs a critical error message with trace context and terminates the application. This method automatically extracts trace and span IDs from the provided context when tracing is enabled. Use Fatal only for errors that make it impossible for the application to continue running. This method will call os.Exit(1) after logging the message.
Parameters:
- ctx: The context containing trace information
- msg: The log message
- err: An error to include in the log entry, or nil if no error
- fields: Variable number of map[string]interface{} containing additional structured data
Example:
configErr := LoadConfiguration()
if configErr != nil {
logger.FatalWithContext(ctx, "Cannot start application without configuration", configErr, map[string]interface{}{
"config_path": "/etc/myapp/config.json",
})
}
Note: This function does not return as it terminates the application.
func (*LoggerClient) Info ¶ added in v0.12.0
func (l *LoggerClient) Info(msg string, err error, fields ...map[string]interface{})
Info logs an informational message, along with an optional error and structured fields. Use Info for general application progress and successful operations.
Parameters:
- msg: The log message
- err: An error to include in the log entry, or nil if no error
- fields: Variable number of map[string]interface{} containing additional structured data
Example:
logger.Info("User logged in successfully", nil, map[string]interface{}{
"user_id": 12345,
"login_method": "oauth",
})
func (*LoggerClient) InfoWithContext ¶ added in v0.12.0
func (l *LoggerClient) InfoWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
InfoWithContext logs an informational message with trace context, along with an optional error and structured fields. This method automatically extracts trace and span IDs from the provided context when tracing is enabled.
Parameters:
- ctx: The context containing trace information
- msg: The log message
- err: An error to include in the log entry, or nil if no error
- fields: Variable number of map[string]interface{} containing additional structured data
Example:
logger.InfoWithContext(ctx, "User logged in successfully", nil, map[string]interface{}{
"user_id": 12345,
"login_method": "oauth",
})
func (*LoggerClient) Warn ¶ added in v0.12.0
func (l *LoggerClient) Warn(msg string, err error, fields ...map[string]interface{})
Warn logs a warning message, indicating potential issues that aren't necessarily errors. Warnings indicate situations that aren't failures but might need attention or could lead to problems if not addressed.
Parameters:
- msg: The log message
- err: An error to include in the log entry, or nil if no error
- fields: Variable number of map[string]interface{} containing additional structured data
Example:
logger.Warn("High resource usage detected", nil, map[string]interface{}{
"cpu_usage": 85.5,
"memory_usage_mb": 1024,
})
func (*LoggerClient) WarnWithContext ¶ added in v0.12.0
func (l *LoggerClient) WarnWithContext(ctx context.Context, msg string, err error, fields ...map[string]interface{})
WarnWithContext logs a warning message with trace context, indicating potential issues that aren't necessarily errors. This method automatically extracts trace and span IDs from the provided context when tracing is enabled.
Parameters:
- ctx: The context containing trace information
- msg: The log message
- err: An error to include in the log entry, or nil if no error
- fields: Variable number of map[string]interface{} containing additional structured data
Example:
logger.WarnWithContext(ctx, "High resource usage detected", nil, map[string]interface{}{
"cpu_usage": 85.5,
"memory_usage_mb": 1024,
})