use tracing_subscriber::{layer::SubscriberExt, Registry, EnvFilter};
use thiserror::Error;
use crate::config::ObservabilityConfig;
#[derive(Debug, Error)]
pub enum TracingError {
#[error("Trace setup error: {0}")]
SetupError(String),
}
pub fn init_tracing(config: &ObservabilityConfig) -> Result<(), TracingError> {
if !config.tracing_enabled {
return Ok(());
}
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(&config.log_level));
let subscriber = Registry::default()
.with(filter)
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_thread_ids(false)
.with_line_number(true)
);
tracing::subscriber::set_global_default(subscriber)
.map_err(|e| TracingError::SetupError(e.to_string()))?;
tracing::info!("Tracing initialized for service: {}", config.service_name);
#[cfg(feature = "otel-tracing")]
{
tracing::info!("OpenTelemetry tracing is available via init_tracing_with_otel()");
}
Ok(())
}
#[cfg(feature = "otel-tracing")]
pub fn init_tracing_with_otel(config: &ObservabilityConfig) -> Result<OtelGuard, TracingError> {
use opentelemetry::trace::TracerProvider;
use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
use opentelemetry_sdk::Resource;
use opentelemetry::KeyValue;
if !config.tracing_enabled {
return Ok(OtelGuard::None);
}
let resource = Resource::new(vec![
KeyValue::new("service.name", config.service_name.clone()),
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
]);
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(&config.log_level));
let tracer_provider = SdkTracerProvider::builder()
.with_resource(resource)
.build();
let otel_layer = tracing_opentelemetry::layer()
.with_tracer(tracer_provider.tracer(config.service_name.clone()));
let subscriber = Registry::default()
.with(filter)
.with(otel_layer)
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_thread_ids(false)
.with_line_number(true)
);
tracing::subscriber::set_global_default(subscriber)
.map_err(|e| TracingError::SetupError(e.to_string()))?;
tracing::info!("OpenTelemetry tracing initialized for service: {}", config.service_name);
if let Some(endpoint) = &config.otlp_endpoint {
tracing::info!("OTLP endpoint configured: {}", endpoint);
tracing::info!("Note: OTLP exporter setup requires additional configuration");
} else {
tracing::info!("No OTLP endpoint configured - tracing to console only");
}
Ok(OtelGuard::Some(tracer_provider))
}
#[cfg(feature = "otel-tracing")]
pub enum OtelGuard {
Some(opentelemetry_sdk::trace::TracerProvider),
None,
}
#[cfg(feature = "otel-tracing")]
impl Drop for OtelGuard {
fn drop(&mut self) {
if let Self::Some(_) = self {
tracing::debug!("OtelGuard dropped - tracer provider shutdown");
}
}
}
pub fn http_request_span(method: &str, path: &str) -> tracing::Span {
tracing::info_span!(
"http_request",
method = %method,
path = %path,
status = tracing::field::Empty,
duration_ms = tracing::field::Empty,
)
}
#[cfg(feature = "otel-tracing")]
pub fn build_otel_tracer(
config: &ObservabilityConfig,
) -> Result<(opentelemetry_sdk::trace::Tracer, opentelemetry_sdk::trace::TracerProvider), TracingError>
{
use opentelemetry::trace::TracerProvider;
use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
use opentelemetry_sdk::Resource;
use opentelemetry::KeyValue;
use opentelemetry_otlp::WithExportConfig;
opentelemetry::global::set_text_map_propagator(
opentelemetry_sdk::propagation::TraceContextPropagator::new()
);
let resource = Resource::new(vec![
KeyValue::new("service.name", config.service_name.clone()),
KeyValue::new("service.version", env!("CARGO_PKG_VERSION")),
]);
let mut provider_builder = SdkTracerProvider::builder().with_resource(resource);
if let Some(endpoint) = &config.otlp_endpoint {
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.map_err(|e| TracingError::SetupError(format!("OTLP exporter: {e}")))?;
provider_builder = provider_builder.with_simple_exporter(exporter);
}
let provider = provider_builder.build();
let tracer = provider.tracer(config.service_name.clone());
Ok((tracer, provider))
}
pub mod span_attributes {
pub const HTTP_METHOD: &str = "http.method";
pub const HTTP_PATH: &str = "http.path";
pub const HTTP_STATUS: &str = "http.status_code";
pub const USER_ID: &str = "user.id";
pub const REQUEST_ID: &str = "request.id";
pub const MODULE: &str = "code.module";
pub const FUNCTION: &str = "code.function";
}