Logging Service Pattern
This document describes the Logging Service Pattern implemented in the Navius framework, which provides a clean abstraction over logging functionality through a provider-based approach.
Overview
The Logging Service Pattern follows these key principles:
- Separation of Interface and Implementation: Logging operations are defined by a core interface, with multiple implementations provided via the provider pattern.
- Pluggable Providers: Different logging implementations can be swapped in and out based on configuration.
- Structured Logging: All logging is done with structured data rather than raw strings, making logs more searchable and meaningful.
- Context Propagation: Logging context can be inherited across components via child loggers.
- Configuration-driven: Behavior is controlled through configuration rather than code changes.
Core Components
The pattern consists of the following components:
LoggingOperations Interface
The LoggingOperations
trait defines the core functionality exposed to application code:
#![allow(unused)] fn main() { pub trait LoggingOperations: Send + Sync + 'static { fn log(&self, level: LogLevel, info: LogInfo) -> Result<(), LoggingError>; fn log_structured(&self, record: StructuredLog) -> Result<(), LoggingError>; // Additional methods... } }
LoggingProvider Interface
The LoggingProvider
trait defines how logging implementations are created:
#![allow(unused)] fn main() { pub trait LoggingProvider: Send + Sync + 'static { async fn create_logger(&self, config: &LoggingConfig) -> Result<Arc<dyn LoggingOperations>, LoggingError>; fn name(&self) -> &'static str; fn supports(&self, config: &LoggingConfig) -> bool; } }
LoggingProviderRegistry
A registry that manages available providers and creates loggers based on configuration:
#![allow(unused)] fn main() { pub struct LoggingProviderRegistry { providers: Mutex<HashMap<String, Arc<dyn LoggingProvider>>>, default_provider_name: Mutex<String>, } }
Implementation Steps
To implement this pattern in your own code:
- Define the logging operations interface
- Create a provider interface for instantiating loggers
- Implement a provider registry for managing available providers
- Create concrete implementations of the logging operations
- Implement the provider for each concrete implementation
Benefits
- Testability: Logging can be easily mocked for testing purposes
- Extensibility: New logging backends can be added without changing application code
- Consistency: All logs follow the same structured format
- Configuration: Behavior can be changed through configuration
- Runtime Selection: Logging implementation can be selected at runtime
Example Usage
See the Logging Service Example for detailed usage examples.
Related Patterns
- Provider Pattern: For creating implementations of an interface
- Registry Pattern: For managing and accessing providers
- Factory Method Pattern: For creating logger instances
- Decorator Pattern: For adding functionality to loggers (e.g., child loggers)
- Strategy Pattern: For selecting logging implementation at runtime
Recommended Implementation
The Navius framework provides a complete implementation of this pattern in the core::logger
module. This implementation includes:
TracingLoggerProvider
: A provider for tracing-based loggingConsoleLoggerProvider
: A provider for colorized console outputLoggingProviderRegistry
: A registry for managing providers- Factory methods for creating loggers based on configuration
Refer to the framework implementation for a complete reference implementation of this pattern.
Considerations
- Ensure thread safety when implementing loggers
- Consider performance implications, especially for high-volume logging
- Provide extension points for advanced features like filtering and sampling
- Plan for error handling when logging fails
- Consider how to handle buffering and asynchronous logging