use crate::config::OtelConfig;
use crate::error::Result;
#[cfg(feature = "otel")]
use crate::error::ForgeError;
pub fn init_otel(config: &OtelConfig) -> Result<()> {
init_otel_inner(config)
}
#[cfg(feature = "otel")]
fn init_otel_inner(config: &OtelConfig) -> Result<()> {
use opentelemetry::global;
use opentelemetry::KeyValue;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::propagation::TraceContextPropagator;
use opentelemetry_sdk::trace::Config as SdkConfig;
use opentelemetry_sdk::Resource;
use std::sync::OnceLock;
static INIT: OnceLock<()> = OnceLock::new();
if INIT.get().is_some() {
return Ok(());
}
let mut resource_kvs: Vec<KeyValue> = Vec::new();
if let Some(name) = &config.service_name {
resource_kvs.push(KeyValue::new("service.name", name.clone()));
}
for (k, v) in &config.resource_attributes {
resource_kvs.push(KeyValue::new(k.clone(), v.clone()));
}
let resource = Resource::new(resource_kvs);
let mut exporter_builder = opentelemetry_otlp::new_exporter()
.http()
.with_protocol(opentelemetry_otlp::Protocol::HttpBinary);
if let Some(endpoint) = &config.endpoint {
exporter_builder = exporter_builder.with_endpoint(endpoint.clone());
}
if !config.headers.is_empty() {
exporter_builder = exporter_builder.with_headers(config.headers.clone());
}
let tracer_provider = opentelemetry_otlp::new_pipeline()
.tracing()
.with_exporter(exporter_builder)
.with_trace_config(SdkConfig::default().with_resource(resource))
.install_batch(opentelemetry_sdk::runtime::Tokio)
.map_err(|e| ForgeError::config(format!("failed to install OTLP pipeline: {}", e)))?;
global::set_text_map_propagator(TraceContextPropagator::new());
global::set_tracer_provider(tracer_provider);
let _ = INIT.set(());
Ok(())
}
#[cfg(not(feature = "otel"))]
#[inline]
fn init_otel_inner(_config: &OtelConfig) -> Result<()> {
Ok(())
}
pub const fn otel_feature_enabled() -> bool {
cfg!(feature = "otel")
}