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}