use std::sync::{Arc, Once};
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
use crate::span_exporter::{AdkSpanExporter, AdkSpanLayer};
static INIT: Once = Once::new();
#[derive(Debug, thiserror::Error)]
pub enum TelemetryError {
#[error("telemetry init failed: {0}")]
Init(String),
}
pub fn init_telemetry(service_name: &str) -> Result<(), TelemetryError> {
INIT.call_once(|| {
let filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::registry()
.with(filter)
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_thread_ids(true)
.with_line_number(true),
)
.init();
tracing::info!(service.name = service_name, "telemetry initialized");
});
Ok(())
}
pub fn init_with_otlp(service_name: &str, endpoint: &str) -> Result<(), TelemetryError> {
use opentelemetry::trace::TracerProvider;
use opentelemetry_otlp::WithExportConfig;
use tracing_opentelemetry::OpenTelemetryLayer;
let endpoint = endpoint.to_string();
let service_name = service_name.to_string();
let init_error: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
INIT.call_once(|| {
let resource = opentelemetry_sdk::Resource::builder_empty()
.with_attributes([opentelemetry::KeyValue::new("service.name", service_name.clone())])
.build();
let span_exporter = match opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(&endpoint)
.build()
{
Ok(e) => e,
Err(e) => {
*init_error.lock().unwrap_or_else(|p| p.into_inner()) =
Some(format!("failed to build OTLP span exporter: {e}"));
return;
}
};
let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
.with_batch_exporter(span_exporter)
.with_resource(resource.clone())
.build();
let tracer = tracer_provider.tracer("adk-telemetry");
opentelemetry::global::set_tracer_provider(tracer_provider);
let metric_exporter = match opentelemetry_otlp::MetricExporter::builder()
.with_tonic()
.with_endpoint(&endpoint)
.build()
{
Ok(e) => e,
Err(e) => {
*init_error.lock().unwrap_or_else(|p| p.into_inner()) =
Some(format!("failed to build OTLP metric exporter: {e}"));
return;
}
};
let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()
.with_periodic_exporter(metric_exporter)
.with_resource(resource)
.build();
opentelemetry::global::set_meter_provider(meter_provider);
let telemetry_layer = OpenTelemetryLayer::new(tracer);
let filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::registry()
.with(filter)
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_thread_ids(true)
.with_line_number(true),
)
.with(telemetry_layer)
.init();
tracing::info!(
service.name = service_name,
otlp.endpoint = %endpoint,
"telemetry initialized with OpenTelemetry"
);
});
if let Some(err) = init_error.lock().unwrap_or_else(|p| p.into_inner()).take() {
return Err(TelemetryError::Init(err));
}
Ok(())
}
pub fn build_otlp_layer<S>(
service_name: &str,
endpoint: &str,
) -> Result<Box<dyn tracing_subscriber::Layer<S> + Send + Sync>, TelemetryError>
where
S: tracing::Subscriber
+ for<'span> tracing_subscriber::registry::LookupSpan<'span>
+ Send
+ Sync,
{
use opentelemetry::trace::TracerProvider;
use opentelemetry_otlp::WithExportConfig;
use tracing_opentelemetry::OpenTelemetryLayer;
let resource = opentelemetry_sdk::Resource::builder_empty()
.with_attributes([opentelemetry::KeyValue::new("service.name", service_name.to_string())])
.build();
let span_exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.map_err(|e| TelemetryError::Init(format!("failed to build OTLP span exporter: {e}")))?;
let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
.with_batch_exporter(span_exporter)
.with_resource(resource.clone())
.build();
let tracer = tracer_provider.tracer("adk-telemetry");
opentelemetry::global::set_tracer_provider(tracer_provider);
let metric_exporter = opentelemetry_otlp::MetricExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.map_err(|e| TelemetryError::Init(format!("failed to build OTLP metric exporter: {e}")))?;
let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()
.with_periodic_exporter(metric_exporter)
.with_resource(resource)
.build();
opentelemetry::global::set_meter_provider(meter_provider);
Ok(Box::new(OpenTelemetryLayer::new(tracer)))
}
pub fn shutdown_telemetry() {
opentelemetry::global::set_tracer_provider(
opentelemetry::trace::noop::NoopTracerProvider::new(),
);
}
pub fn init_with_adk_exporter(service_name: &str) -> Result<Arc<AdkSpanExporter>, TelemetryError> {
let exporter = Arc::new(AdkSpanExporter::new());
let exporter_clone = exporter.clone();
INIT.call_once(|| {
let filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap_or_else(|_| EnvFilter::new("info"));
let adk_layer = AdkSpanLayer::new(exporter_clone);
tracing_subscriber::registry()
.with(filter)
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_thread_ids(true)
.with_line_number(true),
)
.with(adk_layer)
.init();
tracing::info!(service.name = service_name, "telemetry initialized with ADK span exporter");
});
Ok(exporter)
}