gemini_genai_rs/telemetry/
mod.rs1pub mod logging;
10pub mod metrics;
11pub mod spans;
12
13#[derive(Debug, Clone)]
15pub struct TelemetryConfig {
16 pub logging_enabled: bool,
18 pub log_filter: String,
20 pub json_logs: bool,
22 pub metrics_enabled: bool,
24 pub metrics_addr: Option<String>,
26 pub otel_traces: bool,
28 pub otel_metrics: bool,
30 pub otel_service_name: String,
32 pub otel_gcp_project: Option<String>,
35}
36
37impl Default for TelemetryConfig {
38 fn default() -> Self {
39 Self {
40 logging_enabled: true,
41 log_filter: "info".to_string(),
42 json_logs: false,
43 metrics_enabled: false,
44 metrics_addr: None,
45 otel_traces: false,
46 otel_metrics: false,
47 otel_service_name: "gemini-live".to_string(),
48 otel_gcp_project: None,
49 }
50 }
51}
52
53#[derive(Default)]
56pub struct TelemetryGuard {
57 #[cfg(feature = "otel-base")]
58 _tracer_provider: Option<opentelemetry_sdk::trace::SdkTracerProvider>,
59 #[cfg(feature = "otel-base")]
60 _meter_provider: Option<opentelemetry_sdk::metrics::SdkMeterProvider>,
61 #[cfg(not(feature = "otel-base"))]
62 _private: (),
63}
64
65impl TelemetryConfig {
66 pub fn init(&self) -> Result<TelemetryGuard, Box<dyn std::error::Error>> {
79 #[allow(unused_mut)]
80 let mut guard = TelemetryGuard::default();
81
82 #[cfg(feature = "otel-otlp")]
84 let otel_tracer = if self.otel_traces {
85 let exporter = opentelemetry_otlp::SpanExporter::builder()
86 .with_tonic()
87 .build()?;
88 let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
89 .with_batch_exporter(exporter)
90 .with_resource(self.otel_resource())
91 .build();
92 let tracer = opentelemetry::trace::TracerProvider::tracer(
93 &provider,
94 self.otel_service_name.clone(),
95 );
96 guard._tracer_provider = Some(provider);
97 Some(tracer)
98 } else {
99 None
100 };
101
102 #[cfg(feature = "otel-otlp")]
103 if self.otel_metrics {
104 let exporter = opentelemetry_otlp::MetricExporter::builder()
105 .with_tonic()
106 .build()?;
107 let provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()
108 .with_periodic_exporter(exporter)
109 .with_resource(self.otel_resource())
110 .build();
111 opentelemetry::global::set_meter_provider(provider.clone());
112 guard._meter_provider = Some(provider);
113 }
114
115 #[cfg(feature = "tracing-support")]
117 if self.logging_enabled {
118 #[cfg(feature = "otel-otlp")]
119 {
120 self.init_tracing_subscriber_with_tracer(otel_tracer)
121 .map_err(|e| -> Box<dyn std::error::Error> { e })?;
122 }
123 #[cfg(not(feature = "otel-otlp"))]
124 {
125 self.init_tracing_subscriber()
126 .map_err(|e| -> Box<dyn std::error::Error> { e })?;
127 }
128 }
129
130 Ok(guard)
131 }
132
133 #[cfg(feature = "otel-gcp")]
144 pub async fn init_gcp(
145 &self,
146 ) -> Result<TelemetryGuard, Box<dyn std::error::Error + Send + Sync>> {
147 use opentelemetry_gcloud_trace::GcpCloudTraceExporterBuilder;
148
149 let mut guard = TelemetryGuard::default();
150
151 let otel_tracer = if self.otel_traces {
153 let gcp_trace_builder = if let Some(ref project_id) = self.otel_gcp_project {
154 GcpCloudTraceExporterBuilder::new(project_id.clone())
155 .with_resource(self.otel_resource())
156 } else {
157 GcpCloudTraceExporterBuilder::for_default_project_id()
158 .await?
159 .with_resource(self.otel_resource())
160 };
161
162 let tracer_provider = gcp_trace_builder.create_provider().await?;
163 let tracer = gcp_trace_builder.install(&tracer_provider).await?;
164 opentelemetry::global::set_tracer_provider(tracer_provider.clone());
165 guard._tracer_provider = Some(tracer_provider);
166 Some(tracer)
167 } else {
168 None
169 };
170
171 if self.otel_metrics {
173 use opentelemetry_gcloud_monitoring_exporter::{
174 GCPMetricsExporter, GCPMetricsExporterConfig,
175 };
176
177 let mut metrics_cfg = GCPMetricsExporterConfig::default();
178 metrics_cfg.prefix = format!("custom.googleapis.com/{}", self.otel_service_name);
179 if let Some(ref project_id) = self.otel_gcp_project {
180 metrics_cfg.project_id = Some(project_id.clone());
181 }
182 let metrics_exporter = GCPMetricsExporter::init(metrics_cfg).await?;
183
184 use opentelemetry_sdk::metrics::periodic_reader_with_async_runtime::PeriodicReader;
185 let reader =
186 PeriodicReader::builder(metrics_exporter, opentelemetry_sdk::runtime::Tokio)
187 .build();
188
189 let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()
190 .with_resource(self.otel_resource())
191 .with_reader(reader)
192 .build();
193 opentelemetry::global::set_meter_provider(meter_provider.clone());
194 guard._meter_provider = Some(meter_provider);
195 }
196
197 #[cfg(feature = "tracing-support")]
199 if self.logging_enabled {
200 self.init_tracing_subscriber_with_tracer(otel_tracer)?;
201 }
202
203 Ok(guard)
204 }
205
206 #[cfg(feature = "tracing-support")]
211 #[allow(dead_code)]
212 fn init_tracing_subscriber(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
213 self.init_tracing_subscriber_with_tracer(None)
214 }
215
216 #[cfg(feature = "tracing-support")]
220 fn init_tracing_subscriber_with_tracer(
221 &self,
222 #[cfg(feature = "otel-base")] otel_tracer: Option<opentelemetry_sdk::trace::Tracer>,
223 #[cfg(not(feature = "otel-base"))] _otel_tracer: Option<()>,
224 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
225 use tracing_subscriber::prelude::*;
226 use tracing_subscriber::EnvFilter;
227
228 let filter =
229 EnvFilter::try_new(&self.log_filter).unwrap_or_else(|_| EnvFilter::new("info"));
230
231 let fmt_layer = if self.json_logs {
232 tracing_subscriber::fmt::layer().json().boxed()
233 } else {
234 tracing_subscriber::fmt::layer().boxed()
235 };
236
237 let registry = tracing_subscriber::registry().with(filter).with(fmt_layer);
238
239 #[cfg(feature = "otel-base")]
240 {
241 if let Some(tracer) = otel_tracer {
242 let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
243 let subscriber = registry.with(otel_layer);
244 tracing::subscriber::set_global_default(subscriber)
245 .map_err(|e| format!("Failed to set tracing subscriber: {e}"))?;
246 } else {
247 tracing::subscriber::set_global_default(registry)
248 .map_err(|e| format!("Failed to set tracing subscriber: {e}"))?;
249 }
250 }
251
252 #[cfg(not(feature = "otel-base"))]
253 {
254 tracing::subscriber::set_global_default(registry)
255 .map_err(|e| format!("Failed to set tracing subscriber: {e}"))?;
256 }
257
258 Ok(())
259 }
260
261 #[cfg(feature = "otel-base")]
263 pub(crate) fn otel_resource(&self) -> opentelemetry_sdk::Resource {
264 use opentelemetry::KeyValue;
265 opentelemetry_sdk::Resource::builder_empty()
266 .with_attributes([KeyValue::new(
267 "service.name",
268 self.otel_service_name.clone(),
269 )])
270 .build()
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn default_config_values() {
280 let config = TelemetryConfig::default();
281 assert!(config.logging_enabled);
282 assert_eq!(config.log_filter, "info");
283 assert!(!config.json_logs);
284 assert!(!config.metrics_enabled);
285 assert!(config.metrics_addr.is_none());
286 assert!(!config.otel_traces);
287 assert!(!config.otel_metrics);
288 assert_eq!(config.otel_service_name, "gemini-live");
289 assert!(config.otel_gcp_project.is_none());
290 }
291
292 #[test]
293 fn config_builder_pattern() {
294 let config = TelemetryConfig {
295 logging_enabled: false,
296 log_filter: "debug".to_string(),
297 json_logs: true,
298 metrics_enabled: true,
299 metrics_addr: Some("0.0.0.0:9090".to_string()),
300 otel_traces: true,
301 otel_metrics: true,
302 otel_service_name: "my-service".to_string(),
303 otel_gcp_project: Some("my-project".to_string()),
304 };
305 assert!(!config.logging_enabled);
306 assert_eq!(config.log_filter, "debug");
307 assert!(config.json_logs);
308 assert!(config.metrics_enabled);
309 assert_eq!(config.metrics_addr.as_deref(), Some("0.0.0.0:9090"));
310 assert!(config.otel_traces);
311 assert!(config.otel_metrics);
312 assert_eq!(config.otel_service_name, "my-service");
313 assert_eq!(config.otel_gcp_project.as_deref(), Some("my-project"));
314 }
315
316 #[test]
317 fn telemetry_guard_default() {
318 let _guard = TelemetryGuard::default();
319 }
321}