Skip to main content

adk_telemetry/
init.rs

1//! Telemetry initialization and configuration
2
3use std::sync::{Arc, Once};
4use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
5
6use crate::span_exporter::{AdkSpanExporter, AdkSpanLayer};
7
8static INIT: Once = Once::new();
9
10/// Error returned by telemetry initialization functions.
11#[derive(Debug, thiserror::Error)]
12pub enum TelemetryError {
13    /// Failed to build the tracing/OTLP pipeline.
14    #[error("telemetry init failed: {0}")]
15    Init(String),
16}
17
18/// Initialize basic telemetry with console logging.
19///
20/// # Arguments
21/// * `service_name` - Name of the service for trace identification
22///
23/// # Example
24/// ```
25/// use adk_telemetry::init_telemetry;
26/// init_telemetry("my-agent-service").expect("Failed to initialize telemetry");
27/// ```
28pub fn init_telemetry(service_name: &str) -> Result<(), TelemetryError> {
29    INIT.call_once(|| {
30        let filter = EnvFilter::try_from_default_env()
31            .or_else(|_| EnvFilter::try_new("info"))
32            .unwrap_or_else(|_| EnvFilter::new("info"));
33
34        tracing_subscriber::registry()
35            .with(filter)
36            .with(
37                tracing_subscriber::fmt::layer()
38                    .with_target(true)
39                    .with_thread_ids(true)
40                    .with_line_number(true),
41            )
42            .init();
43
44        tracing::info!(service.name = service_name, "telemetry initialized");
45    });
46
47    Ok(())
48}
49
50/// Initialize telemetry with OpenTelemetry OTLP export.
51///
52/// Enables distributed tracing by exporting spans to an OTLP collector.
53///
54/// # Arguments
55/// * `service_name` - Name of the service for trace identification
56/// * `endpoint` - OTLP collector endpoint (e.g., "http://localhost:4317")
57///
58/// # Example
59/// ```no_run
60/// use adk_telemetry::init_with_otlp;
61/// init_with_otlp("my-agent", "http://localhost:4317")
62///     .expect("Failed to initialize telemetry");
63/// ```
64pub fn init_with_otlp(service_name: &str, endpoint: &str) -> Result<(), TelemetryError> {
65    use opentelemetry::trace::TracerProvider;
66    use opentelemetry_otlp::WithExportConfig;
67    use tracing_opentelemetry::OpenTelemetryLayer;
68
69    let endpoint = endpoint.to_string();
70    let service_name = service_name.to_string();
71
72    let init_error: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
73
74    INIT.call_once(|| {
75        let resource = opentelemetry_sdk::Resource::builder_empty()
76            .with_attributes([opentelemetry::KeyValue::new("service.name", service_name.clone())])
77            .build();
78
79        // Build OTLP span exporter
80        let span_exporter = match opentelemetry_otlp::SpanExporter::builder()
81            .with_tonic()
82            .with_endpoint(&endpoint)
83            .build()
84        {
85            Ok(e) => e,
86            Err(e) => {
87                *init_error.lock().unwrap_or_else(|p| p.into_inner()) =
88                    Some(format!("failed to build OTLP span exporter: {e}"));
89                return;
90            }
91        };
92
93        // Build tracer provider with batch exporter
94        let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
95            .with_batch_exporter(span_exporter)
96            .with_resource(resource.clone())
97            .build();
98
99        let tracer = tracer_provider.tracer("adk-telemetry");
100        opentelemetry::global::set_tracer_provider(tracer_provider);
101
102        // Initialize metrics
103        let metric_exporter = match opentelemetry_otlp::MetricExporter::builder()
104            .with_tonic()
105            .with_endpoint(&endpoint)
106            .build()
107        {
108            Ok(e) => e,
109            Err(e) => {
110                *init_error.lock().unwrap_or_else(|p| p.into_inner()) =
111                    Some(format!("failed to build OTLP metric exporter: {e}"));
112                return;
113            }
114        };
115
116        let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()
117            .with_periodic_exporter(metric_exporter)
118            .with_resource(resource)
119            .build();
120
121        opentelemetry::global::set_meter_provider(meter_provider);
122
123        let telemetry_layer = OpenTelemetryLayer::new(tracer);
124
125        let filter = EnvFilter::try_from_default_env()
126            .or_else(|_| EnvFilter::try_new("info"))
127            .unwrap_or_else(|_| EnvFilter::new("info"));
128
129        tracing_subscriber::registry()
130            .with(filter)
131            .with(
132                tracing_subscriber::fmt::layer()
133                    .with_target(true)
134                    .with_thread_ids(true)
135                    .with_line_number(true),
136            )
137            .with(telemetry_layer)
138            .init();
139
140        tracing::info!(
141            service.name = service_name,
142            otlp.endpoint = %endpoint,
143            "telemetry initialized with OpenTelemetry"
144        );
145    });
146
147    if let Some(err) = init_error.lock().unwrap_or_else(|p| p.into_inner()).take() {
148        return Err(TelemetryError::Init(err));
149    }
150
151    Ok(())
152}
153
154/// Shutdown telemetry and flush any pending spans.
155///
156/// Should be called before application exit to ensure all telemetry data is sent.
157/// In OTel 0.28+, the tracer provider is shut down when the last reference is dropped.
158/// This function is kept for backward compatibility and explicitly drops the global provider.
159pub fn shutdown_telemetry() {
160    // In OTel 0.28, shutdown_tracer_provider() was removed.
161    // The SdkTracerProvider shuts down automatically when the last reference is dropped.
162    // We trigger this by replacing the global provider with a no-op, which drops the old one.
163    opentelemetry::global::set_tracer_provider(
164        opentelemetry::trace::noop::NoopTracerProvider::new(),
165    );
166}
167
168/// Initialize telemetry with ADK span exporter.
169///
170/// Creates a shared span exporter that can be used by both telemetry and the debug API.
171/// Returns the exporter so it can be passed to the debug controller.
172pub fn init_with_adk_exporter(service_name: &str) -> Result<Arc<AdkSpanExporter>, TelemetryError> {
173    let exporter = Arc::new(AdkSpanExporter::new());
174    let exporter_clone = exporter.clone();
175
176    INIT.call_once(|| {
177        let filter = EnvFilter::try_from_default_env()
178            .or_else(|_| EnvFilter::try_new("info"))
179            .unwrap_or_else(|_| EnvFilter::new("info"));
180
181        let adk_layer = AdkSpanLayer::new(exporter_clone);
182
183        tracing_subscriber::registry()
184            .with(filter)
185            .with(
186                tracing_subscriber::fmt::layer()
187                    .with_target(true)
188                    .with_thread_ids(true)
189                    .with_line_number(true),
190            )
191            .with(adk_layer)
192            .init();
193
194        tracing::info!(service.name = service_name, "telemetry initialized with ADK span exporter");
195    });
196
197    Ok(exporter)
198}