1use barrzen_axum_core::{Config, LogBackend, LogFormat};
6use tracing_subscriber::{
7 fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter,
8};
9
10#[cfg(feature = "otel")]
11use tracing_subscriber::Layer;
12#[cfg(feature = "otel")]
13use std::sync::OnceLock;
14
15#[cfg(feature = "otel")]
16static OTEL_PROVIDER: OnceLock<opentelemetry_sdk::trace::SdkTracerProvider> = OnceLock::new();
17
18pub fn init_tracing(config: &Config) -> anyhow::Result<()> {
23 match config.logging.log_backend {
24 LogBackend::Tracing => {
25 let env_filter = EnvFilter::try_from_default_env()
26 .unwrap_or_else(|_| EnvFilter::new(&config.logging.log_level));
27 init_tracing_subscriber(config, env_filter)
28 }
29 LogBackend::FastLog => init_fast_log(config),
30 }
31}
32
33pub fn shutdown() {
37 #[cfg(feature = "otel")]
38 {
39 if let Some(provider) = OTEL_PROVIDER.get() {
40 let _ = provider.shutdown();
41 }
42 }
43}
44
45fn init_tracing_subscriber(config: &Config, env_filter: EnvFilter) -> anyhow::Result<()> {
46 let fmt_layer = tracing_subscriber::fmt::layer()
48 .with_target(config.logging.log_include_target)
49 .with_span_events(FmtSpan::NONE);
50
51 let registry = tracing_subscriber::registry().with(env_filter);
53
54 match config.logging.log_format {
55 LogFormat::Pretty => {
56 let registry = registry.with(
57 fmt_layer
58 .pretty()
59 .with_file(config.logging.log_include_fileline)
60 .with_line_number(config.logging.log_include_fileline),
61 );
62
63 #[cfg(feature = "otel")]
64 if config.features.feature_otel {
65 let otel_layer = init_otel_layer(config)?;
66 registry.with(otel_layer).try_init()?;
67 return Ok(());
68 }
69
70 registry.try_init()?;
71 }
72 LogFormat::Compact => {
73 let registry = registry.with(
74 fmt_layer
75 .compact()
76 .with_ansi(false)
77 .with_file(config.logging.log_include_fileline)
78 .with_line_number(config.logging.log_include_fileline),
79 );
80
81 #[cfg(feature = "otel")]
82 if config.features.feature_otel {
83 let otel_layer = init_otel_layer(config)?;
84 registry.with(otel_layer).try_init()?;
85 return Ok(());
86 }
87
88 registry.try_init()?;
89 }
90 LogFormat::Json => {
91 let registry = registry.with(
92 fmt_layer
93 .json()
94 .with_file(config.logging.log_include_fileline)
95 .with_line_number(config.logging.log_include_fileline),
96 );
97
98 #[cfg(feature = "otel")]
99 if config.features.feature_otel {
100 let otel_layer = init_otel_layer(config)?;
101 registry.with(otel_layer).try_init()?;
102 return Ok(());
103 }
104
105 registry.try_init()?;
106 }
107 }
108
109 Ok(())
110}
111
112fn init_fast_log(config: &Config) -> anyhow::Result<()> {
113 #[cfg(feature = "fast-log")]
114 {
115 use fast_log::config::Config as FastLogConfig;
116
117 if config.features.feature_otel {
118 anyhow::bail!("LOG_BACKEND=fast_log is not compatible with FEATURE_OTEL=true");
119 }
120
121 if let Err(err) = fast_log::init(FastLogConfig::new().console()) {
122 let message = err.to_string();
123 if message.contains("logging system was already initialized") {
124 anyhow::bail!("fast_log init failed because another logger is already set. Ensure init_tracing runs before any other logger initialization.");
125 }
126 return Err(err.into());
127 }
128 log::set_max_level(resolve_log_level(config));
129 return Ok(());
130 }
131
132 #[cfg(not(feature = "fast-log"))]
133 {
134 let _ = config;
135 anyhow::bail!("LOG_BACKEND=fast_log requires the \"fast-log\" feature on barrzen-axum-obs")
136 }
137}
138
139#[cfg(feature = "fast-log")]
140fn resolve_log_level(config: &Config) -> log::LevelFilter {
141 let mut level = parse_log_level(&config.logging.log_level).unwrap_or(log::LevelFilter::Info);
142
143 if let Ok(rust_log) = std::env::var("RUST_LOG") {
144 for directive in rust_log.split(',') {
145 let directive = directive.trim();
146 if directive.is_empty() {
147 continue;
148 }
149 let level_str = directive
150 .split_once('=')
151 .map_or(directive, |(_, level)| level);
152 if let Some(parsed) = parse_log_level(level_str) {
153 level = level.max(parsed);
154 }
155 }
156 }
157
158 level
159}
160
161#[cfg(feature = "fast-log")]
162fn parse_log_level(value: &str) -> Option<log::LevelFilter> {
163 match value.trim().to_lowercase().as_str() {
164 "off" => Some(log::LevelFilter::Off),
165 "error" => Some(log::LevelFilter::Error),
166 "warn" | "warning" => Some(log::LevelFilter::Warn),
167 "info" => Some(log::LevelFilter::Info),
168 "debug" => Some(log::LevelFilter::Debug),
169 "trace" => Some(log::LevelFilter::Trace),
170 _ => None,
171 }
172}
173
174#[cfg(feature = "otel")]
177fn init_otel_layer<S>(config: &Config) -> anyhow::Result<impl Layer<S>>
178where
179 S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
180{
181 use opentelemetry::{global, trace::TracerProvider as _};
182 use opentelemetry_otlp::WithExportConfig;
183 use opentelemetry_sdk::{propagation::TraceContextPropagator, trace as sdktrace, Resource};
184 use tracing_opentelemetry::OpenTelemetryLayer;
185
186 global::set_text_map_propagator(TraceContextPropagator::new());
188
189 let app_name = &config.app.app_name;
190 let otel_endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT")
191 .unwrap_or_else(|_| "http://localhost:4317".to_string());
192
193 let exporter = opentelemetry_otlp::SpanExporter::builder()
195 .with_tonic()
196 .with_endpoint(otel_endpoint)
197 .build()?;
198
199 let resource = Resource::builder()
201 .with_service_name(app_name.clone())
202 .build();
203
204 let provider = sdktrace::SdkTracerProvider::builder()
205 .with_batch_exporter(exporter)
206 .with_resource(resource)
207 .build();
208
209 global::set_tracer_provider(provider.clone());
211 let _ = OTEL_PROVIDER.set(provider.clone());
212
213 let tracer = provider.tracer("barrzen-axum");
214
215 Ok(OpenTelemetryLayer::new(tracer))
216}