backbone_observability/
tracing.rs1use tracing_subscriber::{layer::SubscriberExt, Registry, EnvFilter};
6use thiserror::Error;
7use crate::config::ObservabilityConfig;
8
9#[derive(Debug, Error)]
11pub enum TracingError {
12 #[error("Trace setup error: {0}")]
14 SetupError(String),
15}
16
17pub fn init_tracing(config: &ObservabilityConfig) -> Result<(), TracingError> {
22 if !config.tracing_enabled {
23 return Ok(());
24 }
25
26 let filter = EnvFilter::try_from_default_env()
28 .unwrap_or_else(|_| EnvFilter::new(&config.log_level));
29
30 let subscriber = Registry::default()
31 .with(filter)
32 .with(
33 tracing_subscriber::fmt::layer()
34 .with_target(true)
35 .with_thread_ids(false)
36 .with_line_number(true)
37 );
38
39 tracing::subscriber::set_global_default(subscriber)
41 .map_err(|e| TracingError::SetupError(e.to_string()))?;
42
43 tracing::info!("Tracing initialized for service: {}", config.service_name);
44
45 #[cfg(feature = "otel-tracing")]
46 {
47 tracing::info!("OpenTelemetry tracing is available via init_tracing_with_otel()");
48 }
49
50 Ok(())
51}
52
53#[cfg(feature = "otel-tracing")]
83pub fn init_tracing_with_otel(config: &ObservabilityConfig) -> Result<OtelGuard, TracingError> {
84 use opentelemetry::trace::TracerProvider;
85 use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
86 use opentelemetry_sdk::Resource;
87 use opentelemetry::KeyValue;
88
89 if !config.tracing_enabled {
90 return Ok(OtelGuard::None);
91 }
92
93 let resource = Resource::new(vec![
95 KeyValue::new("service.name", config.service_name.clone()),
96 KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
97 ]);
98
99 let filter = EnvFilter::try_from_default_env()
101 .unwrap_or_else(|_| EnvFilter::new(&config.log_level));
102
103 let tracer_provider = SdkTracerProvider::builder()
105 .with_resource(resource)
106 .build();
107
108 let otel_layer = tracing_opentelemetry::layer()
110 .with_tracer(tracer_provider.tracer(config.service_name.clone()));
111
112 let subscriber = Registry::default()
113 .with(filter)
114 .with(otel_layer)
115 .with(
116 tracing_subscriber::fmt::layer()
117 .with_target(true)
118 .with_thread_ids(false)
119 .with_line_number(true)
120 );
121
122 tracing::subscriber::set_global_default(subscriber)
123 .map_err(|e| TracingError::SetupError(e.to_string()))?;
124
125 tracing::info!("OpenTelemetry tracing initialized for service: {}", config.service_name);
126
127 if let Some(endpoint) = &config.otlp_endpoint {
128 tracing::info!("OTLP endpoint configured: {}", endpoint);
129 tracing::info!("Note: OTLP exporter setup requires additional configuration");
130 } else {
131 tracing::info!("No OTLP endpoint configured - tracing to console only");
132 }
133
134 Ok(OtelGuard::Some(tracer_provider))
135}
136
137#[cfg(feature = "otel-tracing")]
142pub enum OtelGuard {
143 Some(opentelemetry_sdk::trace::TracerProvider),
145 None,
147}
148
149#[cfg(feature = "otel-tracing")]
150impl Drop for OtelGuard {
151 fn drop(&mut self) {
152 if let Self::Some(_) = self {
153 tracing::debug!("OtelGuard dropped - tracer provider shutdown");
154 }
155 }
156}
157
158pub fn http_request_span(method: &str, path: &str) -> tracing::Span {
169 tracing::info_span!(
170 "http_request",
171 method = %method,
172 path = %path,
173 status = tracing::field::Empty,
174 duration_ms = tracing::field::Empty,
175 )
176}
177
178#[cfg(feature = "otel-tracing")]
188pub fn build_otel_tracer(
189 config: &ObservabilityConfig,
190) -> Result<(opentelemetry_sdk::trace::Tracer, opentelemetry_sdk::trace::TracerProvider), TracingError>
191{
192 use opentelemetry::trace::TracerProvider;
193 use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
194 use opentelemetry_sdk::Resource;
195 use opentelemetry::KeyValue;
196 use opentelemetry_otlp::WithExportConfig;
197
198 opentelemetry::global::set_text_map_propagator(
200 opentelemetry_sdk::propagation::TraceContextPropagator::new()
201 );
202
203 let resource = Resource::new(vec![
204 KeyValue::new("service.name", config.service_name.clone()),
205 KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
206 ]);
207
208 let mut provider_builder = SdkTracerProvider::builder().with_resource(resource);
209
210 if let Some(endpoint) = &config.otlp_endpoint {
212 let exporter = opentelemetry_otlp::SpanExporter::builder()
213 .with_tonic()
214 .with_endpoint(endpoint)
215 .build()
216 .map_err(|e| TracingError::SetupError(format!("OTLP exporter: {e}")))?;
217
218 provider_builder = provider_builder.with_simple_exporter(exporter);
219 }
220
221 let provider = provider_builder.build();
222 let tracer = provider.tracer(config.service_name.clone());
223
224 Ok((tracer, provider))
225}
226
227pub mod span_attributes {
229 pub const HTTP_METHOD: &str = "http.method";
231
232 pub const HTTP_PATH: &str = "http.path";
234
235 pub const HTTP_STATUS: &str = "http.status_code";
237
238 pub const USER_ID: &str = "user.id";
240
241 pub const REQUEST_ID: &str = "request.id";
243
244 pub const MODULE: &str = "code.module";
246
247 pub const FUNCTION: &str = "code.function";
249}