1use serde::{Deserialize, Serialize};
21
22const DEFAULT_SERVICE_NAME: &str = "quiver";
24const DEFAULT_TIMEOUT_SECS: u64 = 10;
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(default)]
31pub struct OtlpConfig {
32 pub endpoint: String,
35 pub service_name: String,
37 pub timeout_secs: u64,
39}
40
41impl Default for OtlpConfig {
42 fn default() -> Self {
43 Self {
44 endpoint: String::new(),
45 service_name: DEFAULT_SERVICE_NAME.to_owned(),
46 timeout_secs: DEFAULT_TIMEOUT_SECS,
47 }
48 }
49}
50
51impl OtlpConfig {
52 #[must_use]
55 pub fn is_enabled(&self) -> bool {
56 !self.endpoint.trim().is_empty()
57 }
58
59 pub fn apply_env_overrides(&mut self) -> Result<(), String> {
66 if let Ok(v) = std::env::var("QUIVER_OTLP_ENDPOINT") {
67 self.endpoint = v;
68 }
69 if let Ok(v) = std::env::var("QUIVER_OTLP_SERVICE_NAME") {
70 self.service_name = v;
71 }
72 if let Ok(v) = std::env::var("QUIVER_OTLP_TIMEOUT_SECS") {
73 self.timeout_secs = v
74 .parse()
75 .map_err(|_| format!("QUIVER_OTLP_TIMEOUT_SECS must be an integer, got {v:?}"))?;
76 }
77 Ok(())
78 }
79}
80
81#[cfg(feature = "otlp")]
86mod live {
87 use std::sync::OnceLock;
88 use std::time::Duration;
89
90 use super::OtlpConfig;
91
92 static PROVIDER: OnceLock<opentelemetry_sdk::trace::SdkTracerProvider> = OnceLock::new();
94
95 pub fn build_provider(
99 cfg: &OtlpConfig,
100 ) -> Result<opentelemetry_sdk::trace::SdkTracerProvider, String> {
101 use opentelemetry_otlp::WithExportConfig;
102 let exporter = opentelemetry_otlp::SpanExporter::builder()
103 .with_tonic()
104 .with_endpoint(&cfg.endpoint)
105 .with_timeout(Duration::from_secs(cfg.timeout_secs))
106 .build()
107 .map_err(|e| format!("building OTLP span exporter: {e}"))?;
108 let resource = opentelemetry_sdk::Resource::builder()
109 .with_service_name(cfg.service_name.clone())
110 .build();
111 let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
112 .with_batch_exporter(exporter)
113 .with_resource(resource)
114 .build();
115 Ok(provider)
116 }
117
118 pub fn store_provider(provider: opentelemetry_sdk::trace::SdkTracerProvider) {
120 let _ = PROVIDER.set(provider);
121 }
122
123 pub fn shutdown() {
125 if let Some(provider) = PROVIDER.get() {
126 let _ = provider.shutdown();
127 }
128 }
129}
130
131#[cfg(feature = "otlp")]
132pub use live::{build_provider, shutdown, store_provider};
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn disabled_by_default() {
140 let c = OtlpConfig::default();
141 assert!(!c.is_enabled());
142 assert_eq!(c.service_name, "quiver");
143 assert_eq!(c.timeout_secs, 10);
144 }
145
146 #[test]
147 fn enabled_when_endpoint_set_and_defaults_apply() {
148 let c: OtlpConfig =
150 serde_json::from_value(serde_json::json!({"endpoint":"http://localhost:4317"}))
151 .unwrap();
152 assert!(c.is_enabled());
153 assert_eq!(c.service_name, "quiver");
154 assert_eq!(c.timeout_secs, 10);
155 }
156
157 #[test]
158 fn whitespace_endpoint_is_not_enabled() {
159 let c: OtlpConfig = serde_json::from_value(serde_json::json!({"endpoint":" "})).unwrap();
160 assert!(!c.is_enabled());
161 }
162
163 #[test]
164 fn fields_deserialize() {
165 let c: OtlpConfig = serde_json::from_value(serde_json::json!({
166 "endpoint":"http://collector:4317","service_name":"q-prod","timeout_secs":3
167 }))
168 .unwrap();
169 assert_eq!(c.service_name, "q-prod");
170 assert_eq!(c.timeout_secs, 3);
171 assert!(c.is_enabled());
172 }
173
174 #[test]
175 fn env_overrides_apply() {
176 unsafe {
178 std::env::set_var("QUIVER_OTLP_ENDPOINT", "http://envhost:4317");
179 std::env::set_var("QUIVER_OTLP_SERVICE_NAME", "from-env");
180 std::env::set_var("QUIVER_OTLP_TIMEOUT_SECS", "7");
181 }
182 let mut c = OtlpConfig::default();
183 c.apply_env_overrides().unwrap();
184 assert_eq!(c.endpoint, "http://envhost:4317");
185 assert_eq!(c.service_name, "from-env");
186 assert_eq!(c.timeout_secs, 7);
187
188 unsafe { std::env::set_var("QUIVER_OTLP_TIMEOUT_SECS", "soon") }
190 assert!(OtlpConfig::default().apply_env_overrides().is_err());
191
192 unsafe {
193 std::env::remove_var("QUIVER_OTLP_ENDPOINT");
194 std::env::remove_var("QUIVER_OTLP_SERVICE_NAME");
195 std::env::remove_var("QUIVER_OTLP_TIMEOUT_SECS");
196 }
197 }
198}