title: "Logging Service Example" description: "" category: "Documentation" tags: [] last_updated: "March 28, 2025" version: "1.0"
Logging Service Generalization
This document explains how to use the generic logging interface in the Navius application. The logging service has been redesigned to use a provider-based approach with pluggable implementations.
Core Concepts
LoggingOperations
The LoggingOperations
trait defines the core interface for all loggers:
pub trait LoggingOperations: Send + Sync + 'static {
// Log a message at a specific level
fn log(&self, level: LogLevel, info: LogInfo) -> Result<(), LoggingError>;
// Convenience methods for different log levels
fn trace(&self, info: LogInfo) -> Result<(), LoggingError>;
fn debug(&self, info: LogInfo) -> Result<(), LoggingError>;
fn info(&self, info: LogInfo) -> Result<(), LoggingError>;
fn warn(&self, info: LogInfo) -> Result<(), LoggingError>;
fn error(&self, info: LogInfo) -> Result<(), LoggingError>;
// Log a structured record directly
fn log_structured(&self, record: StructuredLog) -> Result<(), LoggingError>;
// Set global context for all logs
fn with_global_context(&self, key: &str, value: &str) -> Result<(), LoggingError>;
// Control log levels
fn set_level(&self, level: LogLevel) -> Result<(), LoggingError>;
fn get_level(&self) -> LogLevel;
// Flush any buffered logs
async fn flush(&self) -> Result<(), LoggingError>;
// Create a child logger with additional context
fn child(&self, context: &str) -> Arc<dyn LoggingOperations>;
}
LoggingProvider
The LoggingProvider
trait allows different logging implementations to be created:
pub trait LoggingProvider: Send + Sync + 'static {
// Create a new logger instance
async fn create_logger(&self, config: &LoggingConfig) -> Result<Arc<dyn LoggingOperations>, LoggingError>;
// Get provider name
fn name(&self) -> &'static str;
// Check if this provider supports the given configuration
fn supports(&self, config: &LoggingConfig) -> bool;
}
Using the Logging Service
Initialization
Initialize the logging service using the factory method:
use navius::core::logger::{init, LoggingConfig};
async fn setup_logging() -> Result<Arc<dyn LoggingOperations>, LoggingError> {
// Create a default configuration
let config = LoggingConfig::default();
// Initialize the logging system
let logger = init(&config).await?;
// Return the logger instance
Ok(logger)
}
Basic Logging
Log messages at different levels:
use navius::core::logger::{LogInfo, LogLevel};
// Log an info message
logger.info(LogInfo::new("Application started")).unwrap();
// Log a warning with context
logger.warn(
LogInfo::new("Resource limit approaching")
.with_context("memory-service")
.with_field("current_usage", "85%")
).unwrap();
// Log an error with request tracking
logger.error(
LogInfo::new("Authentication failed")
.with_request_id("req-123456")
.with_user_id("[email protected]")
).unwrap();
Structured Logging
Create structured logs for consistent formatting:
use navius::core::logger::{LogInfo, LogLevel, StructuredLog};
use std::collections::HashMap;
// Create structured log fields
let mut fields = HashMap::new();
fields.insert("operation".to_string(), "user-create".to_string());
fields.insert("duration_ms".to_string(), "42".to_string());
// Create log info
let log_info = LogInfo {
message: "Operation completed".to_string(),
context: Some("user-service".to_string()),
module: Some("api".to_string()),
request_id: Some("req-123".to_string()),
user_id: None,
timestamp: Some(chrono::Utc::now()),
additional_fields: fields,
};
// Convert to structured log
let structured_log = StructuredLog::from((LogLevel::Info, log_info));
// Log the structured record
logger.log_structured(structured_log).unwrap();
Advanced: Creating Child Loggers
Child loggers inherit settings but add additional context:
// Create a logger for a specific subsystem
let auth_logger = logger.child("auth-service");
// All logs from this logger will include the auth-service context
auth_logger.info(LogInfo::new("User login successful")).unwrap();
// Create nested child loggers
let oauth_logger = auth_logger.child("oauth");
oauth_logger.debug(LogInfo::new("Token validation")).unwrap();
Implementing a Custom Logger
To create a custom logger implementation:
- Create a struct that implements
LoggingOperations
- Create a provider that implements
LoggingProvider
- Register your provider with the registry
use navius::core::logger::{
LoggingOperations, LoggingProvider, LoggingProviderRegistry,
LogInfo, LogLevel, StructuredLog, LoggingConfig, LoggingError
};
use std::sync::Arc;
use async_trait::async_trait;
// Example custom logger implementation
struct CustomLogger;
impl LoggingOperations for CustomLogger {
fn log(&self, level: LogLevel, info: LogInfo) -> Result<(), LoggingError> {
// Implement your custom logging logic here
println!("[{}] {}", level, info.message);
Ok(())
}
// Implement other required methods...
}
// Custom provider implementation
struct CustomLoggerProvider;
#[async_trait]
impl LoggingProvider for CustomLoggerProvider {
async fn create_logger(&self, _config: &LoggingConfig) -> Result<Arc<dyn LoggingOperations>, LoggingError> {
Ok(Arc::new(CustomLogger))
}
fn name(&self) -> &'static str {
"custom"
}
fn supports(&self, config: &LoggingConfig) -> bool {
config.logger_type == "custom"
}
}
// Register your provider
async fn register_custom_provider() -> Result<Arc<dyn LoggingOperations>, LoggingError> {
let registry = Arc::new(LoggingProviderRegistry::new());
registry.register_provider(Arc::new(CustomLoggerProvider))?;
let config = LoggingConfig {
logger_type: "custom".to_string(),
..Default::default()
};
registry.create_logger_from_config(&config).await
}
Built-in Logger Implementations
Tracing Logger
The default implementation is based on the tracing
crate:
// Create a tracing-based logger
let config = LoggingConfig {
logger_type: "tracing".to_string(),
level: "debug".to_string(),
format: "json".to_string(),
..Default::default()
};
let logger = init(&config).await.unwrap();
Console Logger
A colorized console logger is also provided:
// Create a console logger with colors
let config = LoggingConfig {
logger_type: "console".to_string(),
level: "info".to_string(),
colorize: true,
..Default::default()
};
let logger = init(&config).await.unwrap();
Configuring Logging
The LoggingConfig
struct controls logger behavior:
let config = LoggingConfig {
// Logger implementation to use
logger_type: "tracing".to_string(),
// Minimum log level
level: "info".to_string(),
// Output format
format: "json".to_string(),
// Enable colorized output (for console loggers)
colorize: true,
// Include file path and line number
include_file_info: true,
// Global fields to add to all logs
global_fields: {
let mut fields = HashMap::new();
fields.insert("app_name".to_string(), "navius".to_string());
fields.insert("environment".to_string(), "development".to_string());
fields
},
..Default::default()
};
Best Practices
- Use structured logging: Prefer structured logs over raw strings for better searchability
- Include context: Always add relevant context to logs
- Use child loggers: Create child loggers for subsystems to maintain context
- Set appropriate log levels: Use debug/trace for development and info/warn for production
- Add request IDs: Include request IDs in logs for distributed request tracing