Skip to main content

ferrox_logger/
lib.rs

1//! # Ferrox Logger (`ferrox-logger`)
2//!
3//! `ferrox-logger` sets up production-ready, structured JSON logging and Sentry telemetry for Ferrox applications.
4//! Built on top of `tracing-subscriber` and `sentry`, it provides unified log formatting, environmental level filtering,
5//! and automated crash reports.
6//!
7//! ## Architectural Context
8//! Containerized environments (Kubernetes, AWS ECS, Docker) require logs to be emitted in structured JSON format via stdout
9//! for centralized aggregation (Elasticsearch, Datadog, Loki). `ferrox-logger` ensures all log events retain trace IDs,
10//! timestamps, and module metadata.
11//!
12//! ## Key Features
13//! - 📝 **Structured JSON Output**: Standardized log format with timestamp, severity level, target module, and dynamic fields.
14//! - 🚨 **Sentry Integration**: Automatic reporting of critical error events to Sentry APM.
15//! - 🎛️ **Environment-Driven Filtering**: Configurable log level thresholds via `RUST_LOG` environment variables.
16//!
17//! ## Example Usage
18//! ```rust,no_run
19//! use ferrox_logger::{setup_logger, LoggerConfig};
20//!
21//! fn main() -> Result<(), Box<dyn std::error::Error>> {
22//!     let _sentry_guard = setup_logger(LoggerConfig::default())?;
23//!     tracing::info!("Application booted successfully");
24//!     Ok(())
25//! }
26//! ```
27
28use opentelemetry::KeyValue;
29use opentelemetry_otlp::WithExportConfig;
30use opentelemetry_sdk::trace::{self, Sampler};
31use opentelemetry_sdk::Resource;
32use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
33use ferrox_errors::AppError;
34
35pub mod sanitizer;
36pub mod dp;
37pub mod weekly_report;
38pub mod merkle;
39pub mod merkle_audit_chain;
40pub use merkle_audit_chain::{AuditLogRecord, MerkleAuditChain};
41
42#[derive(Clone, serde::Deserialize)]
43pub struct LoggerConfig {
44    pub service_name: String,
45    pub environment: String,
46    pub otlp_endpoint: Option<String>,
47    pub sentry_dsn: Option<String>,
48    pub log_level: String,
49}
50
51impl Default for LoggerConfig {
52    fn default() -> Self {
53        Self {
54            service_name: "ferrox-app".to_string(),
55            environment: "development".to_string(),
56            otlp_endpoint: None,
57            sentry_dsn: None,
58            log_level: "info".to_string(),
59        }
60    }
61}
62
63pub fn setup_logger(config: LoggerConfig) -> Result<Option<sentry::ClientInitGuard>, AppError> {
64    let env_filter = EnvFilter::try_from_default_env()
65        .unwrap_or_else(|_| EnvFilter::new(&config.log_level));
66    
67    // Default JSON formatting for standard output
68    let formatting_layer = tracing_subscriber::fmt::layer()
69        .json()
70        .with_file(true)
71        .with_line_number(true)
72        .with_target(false);
73
74    let subscriber = Registry::default().with(env_filter).with(formatting_layer);
75
76    // If Sentry DSN is provided, setup Sentry
77    let mut sentry_guard = None;
78    if let Some(dsn) = config.sentry_dsn {
79        sentry_guard = Some(sentry::init((
80            dsn,
81            sentry::ClientOptions {
82                release: sentry::release_name!(),
83                traces_sample_rate: 1.0,
84                environment: Some(config.environment.into()),
85                ..Default::default()
86            },
87        )));
88    }
89
90    let _ = subscriber.try_init();
91
92    tracing::info!("ferrox-logger initialized: Structured JSON logging enabled for service '{}'.", config.service_name);
93    Ok(sentry_guard)
94}