opentelemetry_configuration/
config.rs1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::time::Duration;
10
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum ComputeEnvironment {
15 #[default]
18 Auto,
19 Lambda,
21 Kubernetes,
23 None,
25}
26
27#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30pub enum Protocol {
31 Grpc,
33 #[default]
35 #[serde(alias = "http_binary", alias = "http-binary")]
36 HttpBinary,
37 #[serde(alias = "http_json", alias = "http-json")]
39 HttpJson,
40}
41
42impl Protocol {
43 #[must_use]
45 pub fn default_endpoint(&self) -> &'static str {
46 match self {
47 Protocol::Grpc => "http://localhost:4317",
48 Protocol::HttpBinary | Protocol::HttpJson => "http://localhost:4318",
49 }
50 }
51
52 #[must_use]
54 pub fn default_port(&self) -> u16 {
55 match self {
56 Protocol::Grpc => 4317,
57 Protocol::HttpBinary | Protocol::HttpJson => 4318,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(default)]
65pub struct OtelSdkConfig {
66 pub endpoint: EndpointConfig,
68
69 pub resource: ResourceConfig,
71
72 pub traces: SignalConfig,
74
75 pub metrics: SignalConfig,
77
78 pub logs: SignalConfig,
80
81 pub init_tracing_subscriber: bool,
83
84 pub instrumentation_scope_name: Option<String>,
87}
88
89impl Default for OtelSdkConfig {
90 fn default() -> Self {
91 Self {
92 endpoint: EndpointConfig::default(),
93 resource: ResourceConfig::default(),
94 traces: SignalConfig::default_enabled(),
95 metrics: SignalConfig::default_enabled(),
96 logs: SignalConfig::default_enabled(),
97 init_tracing_subscriber: true,
98 instrumentation_scope_name: None,
99 }
100 }
101}
102
103impl OtelSdkConfig {
104 #[must_use]
106 pub fn effective_endpoint(&self) -> String {
107 self.endpoint
108 .url
109 .clone()
110 .unwrap_or_else(|| self.endpoint.protocol.default_endpoint().to_string())
111 }
112
113 #[must_use]
115 pub fn signal_endpoint(&self, signal_path: &str) -> String {
116 let base = self.effective_endpoint();
117 let base = base.trim_end_matches('/');
118
119 match self.endpoint.protocol {
120 Protocol::Grpc => base.to_string(),
121 Protocol::HttpBinary | Protocol::HttpJson => {
122 format!("{base}{signal_path}")
123 }
124 }
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(default)]
131pub struct EndpointConfig {
132 pub url: Option<String>,
138
139 pub protocol: Protocol,
141
142 #[serde(with = "humantime_serde")]
144 pub timeout: Duration,
145
146 #[serde(default)]
148 pub headers: HashMap<String, String>,
149}
150
151impl Default for EndpointConfig {
152 fn default() -> Self {
153 Self {
154 url: None,
155 protocol: Protocol::default(),
156 timeout: Duration::from_secs(10),
157 headers: HashMap::new(),
158 }
159 }
160}
161
162#[derive(Debug, Clone, Default, Serialize, Deserialize)]
164#[serde(default)]
165pub struct ResourceConfig {
166 pub service_name: Option<String>,
168
169 pub service_version: Option<String>,
171
172 pub deployment_environment: Option<String>,
174
175 #[serde(default)]
177 pub attributes: HashMap<String, String>,
178
179 #[serde(default)]
181 pub compute_environment: ComputeEnvironment,
182}
183
184impl ResourceConfig {
185 pub fn with_service_name(name: impl Into<String>) -> Self {
187 Self {
188 service_name: Some(name.into()),
189 ..Default::default()
190 }
191 }
192}
193
194#[derive(Debug, Clone, Default, Serialize, Deserialize)]
196#[serde(default)]
197pub struct SignalConfig {
198 pub enabled: bool,
200
201 pub batch: BatchConfig,
203}
204
205impl SignalConfig {
206 #[must_use]
208 pub fn default_enabled() -> Self {
209 Self {
210 enabled: true,
211 batch: BatchConfig::default(),
212 }
213 }
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218#[serde(default)]
219pub struct BatchConfig {
220 pub max_queue_size: usize,
222
223 pub max_export_batch_size: usize,
225
226 #[serde(with = "humantime_serde")]
228 pub scheduled_delay: Duration,
229
230 #[serde(with = "humantime_serde")]
232 pub export_timeout: Duration,
233}
234
235impl Default for BatchConfig {
236 fn default() -> Self {
237 Self {
238 max_queue_size: 2048,
239 max_export_batch_size: 512,
240 scheduled_delay: Duration::from_secs(5),
241 export_timeout: Duration::from_secs(30),
242 }
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn test_protocol_default_endpoint() {
252 assert_eq!(Protocol::Grpc.default_endpoint(), "http://localhost:4317");
253 assert_eq!(
254 Protocol::HttpBinary.default_endpoint(),
255 "http://localhost:4318"
256 );
257 assert_eq!(
258 Protocol::HttpJson.default_endpoint(),
259 "http://localhost:4318"
260 );
261 }
262
263 #[test]
264 fn test_otel_sdk_config_effective_endpoint() {
265 let config = OtelSdkConfig::default();
266 assert_eq!(config.effective_endpoint(), "http://localhost:4318");
267
268 let mut config = OtelSdkConfig::default();
269 config.endpoint.protocol = Protocol::Grpc;
270 assert_eq!(config.effective_endpoint(), "http://localhost:4317");
271
272 let mut config = OtelSdkConfig::default();
273 config.endpoint.url = Some("http://collector:4318".to_string());
274 assert_eq!(config.effective_endpoint(), "http://collector:4318");
275 }
276
277 #[test]
278 fn signal_endpoint_appends_path_for_http_protocols() {
279 let config = OtelSdkConfig::default();
280 assert_eq!(
281 config.signal_endpoint("/v1/traces"),
282 "http://localhost:4318/v1/traces"
283 );
284 }
285
286 #[test]
287 fn signal_endpoint_strips_trailing_slash_before_appending() {
288 let mut config = OtelSdkConfig::default();
289 config.endpoint.url = Some("http://collector:4318/".to_string());
290 assert_eq!(
291 config.signal_endpoint("/v1/traces"),
292 "http://collector:4318/v1/traces"
293 );
294 }
295
296 #[test]
297 fn signal_endpoint_returns_base_only_for_grpc() {
298 let mut config = OtelSdkConfig::default();
299 config.endpoint.protocol = Protocol::Grpc;
300 assert_eq!(
301 config.signal_endpoint("/v1/traces"),
302 "http://localhost:4317"
303 );
304 }
305
306 #[test]
307 fn test_resource_config_with_service_name() {
308 let config = ResourceConfig::with_service_name("my-service");
309 assert_eq!(config.service_name, Some("my-service".to_string()));
310 }
311
312 #[test]
313 fn test_batch_config_defaults() {
314 let config = BatchConfig::default();
315 assert_eq!(config.max_queue_size, 2048);
316 assert_eq!(config.max_export_batch_size, 512);
317 assert_eq!(config.scheduled_delay, Duration::from_secs(5));
318 assert_eq!(config.export_timeout, Duration::from_secs(30));
319 }
320}