rs_zero/observability/
otel.rs1use thiserror::Error;
2use tracing_subscriber::{EnvFilter, fmt};
3
4use crate::observability::{OpenTelemetryConfig, TraceExporter};
5
6pub type ObservabilityResult<T> = Result<T, ObservabilityError>;
8
9#[derive(Debug, Error, PartialEq, Eq)]
11pub enum ObservabilityError {
12 #[error("tracing subscriber is already initialized")]
14 SubscriberAlreadyInitialized,
15
16 #[error("otlp exporter endpoint is required")]
18 MissingOtlpEndpoint,
19}
20
21pub fn init_opentelemetry_tracing(config: OpenTelemetryConfig) -> ObservabilityResult<()> {
29 match config.exporter {
30 TraceExporter::Disabled => Ok(()),
31 TraceExporter::Stdout => {
32 let filter =
33 EnvFilter::try_new(config.filter).unwrap_or_else(|_| EnvFilter::new("info"));
34 fmt()
35 .with_env_filter(filter)
36 .try_init()
37 .map_err(|_| ObservabilityError::SubscriberAlreadyInitialized)
38 }
39 TraceExporter::Otlp { endpoint } => {
40 if endpoint.trim().is_empty() {
41 Err(ObservabilityError::MissingOtlpEndpoint)
42 } else {
43 Ok(())
44 }
45 }
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::{ObservabilityError, init_opentelemetry_tracing};
52 use crate::observability::{OpenTelemetryConfig, TraceExporter};
53
54 #[test]
55 fn disabled_exporter_does_not_install_subscriber() {
56 init_opentelemetry_tracing(OpenTelemetryConfig::default()).expect("disabled");
57 }
58
59 #[test]
60 fn otlp_requires_endpoint() {
61 let error = init_opentelemetry_tracing(OpenTelemetryConfig {
62 exporter: TraceExporter::Otlp {
63 endpoint: String::new(),
64 },
65 ..OpenTelemetryConfig::default()
66 })
67 .expect_err("missing endpoint");
68 assert_eq!(error, ObservabilityError::MissingOtlpEndpoint);
69 }
70}