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//! - `tracing-support`: Enables tracing-subscriber with env-filter and fmt layer.
10//! - `otel-otlp`: Adds OTLP trace export via `opentelemetry-otlp`.
11//! - `otel-gcp`: Adds Google Cloud Trace export via `opentelemetry-gcloud-trace`.
12//!
13//! Without any of these features, [`TelemetrySetup::init`] is a no-op that returns `Ok(())`.
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 (requires `tracing-support` feature)
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) | No-op, returns `Ok(())` |
110    /// | `tracing-support` | Console logging with env-filter |
111    /// | `tracing-support` + `otel-otlp` | Console + OTLP trace export |
112    /// | `tracing-support` + `otel-gcp` | Console + Cloud Trace export |
113    ///
114    /// # Errors
115    ///
116    /// Returns an error if the tracing subscriber cannot be set (e.g., if one
117    /// is already registered globally), or if OTel exporter initialization fails.
118    pub fn init(self) -> Result<(), Box<dyn std::error::Error>> {
119        #[cfg(feature = "tracing-support")]
120        {
121            let config = gemini_genai_rs::telemetry::TelemetryConfig {
122                logging_enabled: true,
123                log_filter: std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
124                json_logs: false,
125                metrics_enabled: false,
126                metrics_addr: None,
127                otel_traces: self.otlp_endpoint.is_some() || self.cloud_trace,
128                otel_metrics: false,
129                otel_service_name: self.service_name,
130                otel_gcp_project: None,
131            };
132
133            // Set OTLP endpoint env var if provided (the OTLP exporter reads it).
134            #[cfg(feature = "otel-otlp")]
135            if let Some(ref endpoint) = self.otlp_endpoint {
136                std::env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", endpoint);
137            }
138
139            let _guard = config.init()?;
140            // Note: The guard is intentionally leaked here so the providers stay alive
141            // for the process lifetime. For finer control, use TelemetryConfig::init()
142            // directly and hold the guard.
143            std::mem::forget(_guard);
144        }
145
146        #[cfg(not(feature = "tracing-support"))]
147        {
148            let _ = self; // suppress unused warning
149        }
150
151        Ok(())
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn telemetry_setup_defaults() {
161        let setup = TelemetrySetup::new("test-service");
162        assert_eq!(setup.service_name, "test-service");
163        assert!(setup.otlp_endpoint.is_none());
164        assert!(!setup.cloud_trace);
165        assert!(!setup.capture_content);
166    }
167
168    #[test]
169    fn telemetry_setup_builder_chain() {
170        let setup = TelemetrySetup::new("my-service")
171            .with_otlp("http://localhost:4317")
172            .with_cloud_trace()
173            .with_content_capture(true);
174
175        assert_eq!(setup.service_name, "my-service");
176        assert_eq!(
177            setup.otlp_endpoint.as_deref(),
178            Some("http://localhost:4317")
179        );
180        assert!(setup.cloud_trace);
181        assert!(setup.capture_content);
182    }
183
184    #[test]
185    fn telemetry_setup_clone() {
186        let setup = TelemetrySetup::new("svc").with_otlp("http://otel:4317");
187        let cloned = setup.clone();
188        assert_eq!(cloned.service_name, "svc");
189        assert_eq!(cloned.otlp_endpoint.as_deref(), Some("http://otel:4317"));
190    }
191}