gemini_adk_rs/telemetry/setup.rs
1//! OTLP exporter setup helpers for agent-level telemetry.
2//!
3//! Provides a convenience [`TelemetrySetup`] builder that configures tracing-subscriber
4//! with optional OpenTelemetry exporters. This is a higher-level wrapper around the
5//! L0 [`gemini_genai_rs::telemetry::TelemetryConfig`] tailored for agent applications.
6//!
7//! # Feature flags
8//!
9//! Console logging (tracing-subscriber with env-filter and fmt layer) is always
10//! available. Exporters are opt-in:
11//!
12//! - `otel-otlp`: Adds OTLP trace export via `opentelemetry-otlp`.
13//! - `otel-gcp`: Adds Google Cloud Trace export via `opentelemetry-gcloud-trace`.
14
15/// Configuration for telemetry export.
16///
17/// Use the builder methods to configure the desired exporters, then call [`init`](TelemetrySetup::init)
18/// to set up the global tracing subscriber.
19///
20/// # Examples
21///
22/// ```rust,no_run
23/// use gemini_adk_rs::telemetry::setup::TelemetrySetup;
24///
25/// // Basic setup with console logging only
26/// TelemetrySetup::new("my-agent-service").init().unwrap();
27///
28/// // With OTLP export (requires `otel-otlp` feature)
29/// TelemetrySetup::new("my-agent-service")
30/// .with_otlp("http://localhost:4317")
31/// .with_content_capture(true)
32/// .init()
33/// .unwrap();
34///
35/// // With Google Cloud Trace (requires `otel-gcp` feature)
36/// TelemetrySetup::new("my-agent-service")
37/// .with_cloud_trace()
38/// .init()
39/// .unwrap();
40/// ```
41#[derive(Debug, Clone)]
42pub struct TelemetrySetup {
43 /// Service name for OTel resource identification.
44 pub service_name: String,
45 /// OTLP gRPC endpoint (e.g., `http://localhost:4317`).
46 /// When set, enables OTLP trace export (requires `otel-otlp` feature).
47 pub otlp_endpoint: Option<String>,
48 /// Enable Google Cloud Trace export (requires `otel-gcp` feature).
49 pub cloud_trace: bool,
50 /// Whether to capture prompt/completion content in spans.
51 /// Defaults to `false` to avoid logging sensitive data.
52 pub capture_content: bool,
53}
54
55impl TelemetrySetup {
56 /// Create a new telemetry setup with the given service name.
57 ///
58 /// Defaults to console logging only, no OTLP or Cloud Trace export,
59 /// and content capture disabled.
60 pub fn new(service_name: impl Into<String>) -> Self {
61 Self {
62 service_name: service_name.into(),
63 otlp_endpoint: None,
64 cloud_trace: false,
65 capture_content: false,
66 }
67 }
68
69 /// Set the OTLP gRPC endpoint for trace export.
70 ///
71 /// Requires the `otel-otlp` feature. If the feature is not enabled,
72 /// this value is ignored during [`init`](TelemetrySetup::init).
73 pub fn with_otlp(mut self, endpoint: impl Into<String>) -> Self {
74 self.otlp_endpoint = Some(endpoint.into());
75 self
76 }
77
78 /// Enable Google Cloud Trace export.
79 ///
80 /// Requires the `otel-gcp` feature. If the feature is not enabled,
81 /// this value is ignored during [`init`](TelemetrySetup::init).
82 pub fn with_cloud_trace(mut self) -> Self {
83 self.cloud_trace = true;
84 self
85 }
86
87 /// Set whether to capture prompt/completion content in trace spans.
88 ///
89 /// Defaults to `false`. When enabled, LLM request and response content
90 /// may be recorded in span attributes, which is useful for debugging
91 /// but should be disabled in production to avoid logging sensitive data.
92 pub fn with_content_capture(mut self, capture: bool) -> Self {
93 self.capture_content = capture;
94 self
95 }
96
97 /// Initialize the tracing subscriber with the configured exporters.
98 ///
99 /// This is a convenience function that sets up:
100 /// - `tracing-subscriber` with `EnvFilter` (reads `RUST_LOG` env var, defaults to `info`)
101 /// - OpenTelemetry tracer (if `otlp_endpoint` is set and `otel-otlp` feature is enabled)
102 /// - Google Cloud Trace (if `cloud_trace` is set and `otel-gcp` feature is enabled)
103 /// - Pretty log format for development
104 ///
105 /// # Feature behavior
106 ///
107 /// | Features enabled | Behavior |
108 /// |-----------------|----------|
109 /// | (none) | Console logging with env-filter |
110 /// | `otel-otlp` | Console + OTLP trace export |
111 /// | `otel-gcp` | Console + Cloud Trace export |
112 ///
113 /// # Errors
114 ///
115 /// Returns an error if the tracing subscriber cannot be set (e.g., if one
116 /// is already registered globally), or if OTel exporter initialization fails.
117 pub fn init(self) -> Result<(), Box<dyn std::error::Error>> {
118 let config = gemini_genai_rs::telemetry::TelemetryConfig {
119 logging_enabled: true,
120 log_filter: std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
121 json_logs: false,
122 metrics_enabled: false,
123 metrics_addr: None,
124 otel_traces: self.otlp_endpoint.is_some() || self.cloud_trace,
125 otel_metrics: false,
126 otel_service_name: self.service_name,
127 otel_endpoint: self.otlp_endpoint.clone(),
128 otel_gcp_project: None,
129 };
130
131 let guard = config.init()?;
132 // The guard is intentionally leaked so the providers stay alive for the
133 // process lifetime. For finer control, use TelemetryConfig::init()
134 // directly and hold the guard.
135 Box::leak(Box::new(guard));
136
137 Ok(())
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn telemetry_setup_defaults() {
147 let setup = TelemetrySetup::new("test-service");
148 assert_eq!(setup.service_name, "test-service");
149 assert!(setup.otlp_endpoint.is_none());
150 assert!(!setup.cloud_trace);
151 assert!(!setup.capture_content);
152 }
153
154 #[test]
155 fn telemetry_setup_builder_chain() {
156 let setup = TelemetrySetup::new("my-service")
157 .with_otlp("http://localhost:4317")
158 .with_cloud_trace()
159 .with_content_capture(true);
160
161 assert_eq!(setup.service_name, "my-service");
162 assert_eq!(
163 setup.otlp_endpoint.as_deref(),
164 Some("http://localhost:4317")
165 );
166 assert!(setup.cloud_trace);
167 assert!(setup.capture_content);
168 }
169
170 #[test]
171 fn telemetry_setup_clone() {
172 let setup = TelemetrySetup::new("svc").with_otlp("http://otel:4317");
173 let cloned = setup.clone();
174 assert_eq!(cloned.service_name, "svc");
175 assert_eq!(cloned.otlp_endpoint.as_deref(), Some("http://otel:4317"));
176 }
177}