Skip to main content

ferrox_logger/
lib.rs

1use opentelemetry::KeyValue;
2use opentelemetry_sdk::trace::{self, Sampler};
3use opentelemetry_sdk::Resource;
4use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
5use ferrox_errors::AppError;
6
7pub struct LoggerConfig {
8    pub service_name: String,
9    pub otlp_endpoint: Option<String>,
10    pub sentry_dsn: Option<String>,
11}
12
13pub fn setup_logger(config: LoggerConfig) -> Result<Option<sentry::ClientInitGuard>, AppError> {
14    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
15    
16    // Default JSON formatting for standard output
17    let formatting_layer = tracing_subscriber::fmt::layer()
18        .json()
19        .with_file(true)
20        .with_line_number(true)
21        .with_target(false); // We often don't need the module target in JSON if we have file/line
22
23    let subscriber = Registry::default().with(env_filter).with(formatting_layer);
24
25    // If Sentry DSN is provided, setup Sentry
26    let mut sentry_guard = None;
27    if let Some(dsn) = config.sentry_dsn {
28        sentry_guard = Some(sentry::init((
29            dsn,
30            sentry::ClientOptions {
31                release: sentry::release_name!(),
32                traces_sample_rate: 1.0,
33                ..Default::default()
34            },
35        )));
36        
37        let sentry_layer = sentry_tracing::layer();
38        
39        if let Some(endpoint) = config.otlp_endpoint {
40            let tracer = opentelemetry_otlp::new_pipeline()
41                .tracing()
42                .with_exporter(
43                    opentelemetry_otlp::new_exporter()
44                        .tonic()
45                        .with_endpoint(endpoint),
46                )
47                .with_trace_config(
48                    trace::config()
49                        .with_sampler(Sampler::AlwaysOn)
50                        .with_resource(Resource::new(vec![KeyValue::new("service.name", config.service_name)])),
51                )
52                .install_batch(opentelemetry_sdk::runtime::Tokio)
53                .map_err(|e| AppError::InternalServerError(Box::new(e)))?;
54
55            let telemetry_layer = tracing_opentelemetry::layer().with_tracer(tracer);
56            
57            subscriber.with(telemetry_layer).with(sentry_layer).init();
58        } else {
59            subscriber.with(sentry_layer).init();
60        }
61    } else {
62        if let Some(endpoint) = config.otlp_endpoint {
63            let tracer = opentelemetry_otlp::new_pipeline()
64                .tracing()
65                .with_exporter(
66                    opentelemetry_otlp::new_exporter()
67                        .tonic()
68                        .with_endpoint(endpoint),
69                )
70                .with_trace_config(
71                    trace::config()
72                        .with_sampler(Sampler::AlwaysOn)
73                        .with_resource(Resource::new(vec![KeyValue::new("service.name", config.service_name)])),
74                )
75                .install_batch(opentelemetry_sdk::runtime::Tokio)
76                .map_err(|e| AppError::InternalServerError(Box::new(e)))?;
77
78            let telemetry_layer = tracing_opentelemetry::layer().with_tracer(tracer);
79            
80            subscriber.with(telemetry_layer).init();
81        } else {
82            subscriber.init();
83        }
84    }
85
86    tracing::info!("ferrox-logger initialized: Structured JSON logging enabled.");
87    Ok(sentry_guard)
88}