1#[cfg(any(feature = "http-proto", feature = "http-json"))]
6use crate::exporter::http::HttpExporterBuilder;
7#[cfg(feature = "grpc-tonic")]
8use crate::exporter::tonic::TonicExporterBuilder;
9use crate::{Error, Protocol};
10#[cfg(feature = "serialize")]
11use serde::{Deserialize, Serialize};
12use std::fmt::{Display, Formatter};
13use std::str::FromStr;
14use std::time::Duration;
15
16pub const OTEL_EXPORTER_OTLP_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_ENDPOINT";
20pub const OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT: &str = OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT;
22pub const OTEL_EXPORTER_OTLP_HEADERS: &str = "OTEL_EXPORTER_OTLP_HEADERS";
26pub const OTEL_EXPORTER_OTLP_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL";
28pub const OTEL_EXPORTER_OTLP_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_COMPRESSION";
30
31#[cfg(feature = "http-json")]
32pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON;
34#[cfg(all(feature = "http-proto", not(feature = "http-json")))]
35pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF;
37#[cfg(all(
38 feature = "grpc-tonic",
39 not(any(feature = "http-proto", feature = "http-json"))
40))]
41pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_GRPC;
43
44#[cfg(not(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json")))]
45pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = "";
47
48const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF: &str = "http/protobuf";
49const OTEL_EXPORTER_OTLP_PROTOCOL_GRPC: &str = "grpc";
50const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON: &str = "http/json";
51
52pub const OTEL_EXPORTER_OTLP_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_TIMEOUT";
54pub const OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT: u64 = 10;
56
57#[cfg(feature = "grpc-tonic")]
59const OTEL_EXPORTER_OTLP_GRPC_ENDPOINT_DEFAULT: &str = "http://localhost:4317";
60const OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT: &str = "http://localhost:4318";
61
62#[cfg(any(feature = "http-proto", feature = "http-json"))]
63pub(crate) mod http;
64#[cfg(feature = "grpc-tonic")]
65pub(crate) mod tonic;
66
67#[derive(Debug)]
69pub struct ExportConfig {
70 pub endpoint: String,
73
74 pub protocol: Protocol,
76
77 pub timeout: Duration,
79}
80
81impl Default for ExportConfig {
82 fn default() -> Self {
83 let protocol = default_protocol();
84
85 ExportConfig {
86 endpoint: "".to_string(),
87 protocol,
90 timeout: Duration::from_secs(OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT),
91 }
92 }
93}
94
95#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
97#[derive(Clone, Copy, Debug, Eq, PartialEq)]
98pub enum Compression {
99 Gzip,
101}
102
103impl Display for Compression {
104 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105 match self {
106 Compression::Gzip => write!(f, "gzip"),
107 }
108 }
109}
110
111impl FromStr for Compression {
112 type Err = Error;
113
114 fn from_str(s: &str) -> Result<Self, Self::Err> {
115 match s {
116 "gzip" => Ok(Compression::Gzip),
117 _ => Err(Error::UnsupportedCompressionAlgorithm(s.to_string())),
118 }
119 }
120}
121
122fn default_protocol() -> Protocol {
124 match OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT {
125 OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF => Protocol::HttpBinary,
126 OTEL_EXPORTER_OTLP_PROTOCOL_GRPC => Protocol::Grpc,
127 OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON => Protocol::HttpJson,
128 _ => Protocol::HttpBinary,
129 }
130}
131
132#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
134fn default_headers() -> std::collections::HashMap<String, String> {
135 let mut headers = std::collections::HashMap::new();
136 headers.insert(
137 "User-Agent".to_string(),
138 format!("OTel OTLP Exporter Rust/{}", env!("CARGO_PKG_VERSION")),
139 );
140 headers
141}
142
143pub trait HasExportConfig {
145 fn export_config(&mut self) -> &mut ExportConfig;
147}
148
149#[cfg(feature = "grpc-tonic")]
150impl HasExportConfig for TonicExporterBuilder {
151 fn export_config(&mut self) -> &mut ExportConfig {
152 &mut self.exporter_config
153 }
154}
155
156#[cfg(any(feature = "http-proto", feature = "http-json"))]
157impl HasExportConfig for HttpExporterBuilder {
158 fn export_config(&mut self) -> &mut ExportConfig {
159 &mut self.exporter_config
160 }
161}
162
163pub trait WithExportConfig {
178 fn with_endpoint<T: Into<String>>(self, endpoint: T) -> Self;
180 fn with_protocol(self, protocol: Protocol) -> Self;
188 fn with_timeout(self, timeout: Duration) -> Self;
190 fn with_export_config(self, export_config: ExportConfig) -> Self;
192}
193
194impl<B: HasExportConfig> WithExportConfig for B {
195 fn with_endpoint<T: Into<String>>(mut self, endpoint: T) -> Self {
196 self.export_config().endpoint = endpoint.into();
197 self
198 }
199
200 fn with_protocol(mut self, protocol: Protocol) -> Self {
201 self.export_config().protocol = protocol;
202 self
203 }
204
205 fn with_timeout(mut self, timeout: Duration) -> Self {
206 self.export_config().timeout = timeout;
207 self
208 }
209
210 fn with_export_config(mut self, exporter_config: ExportConfig) -> Self {
211 self.export_config().endpoint = exporter_config.endpoint;
212 self.export_config().protocol = exporter_config.protocol;
213 self.export_config().timeout = exporter_config.timeout;
214 self
215 }
216}
217
218#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
219fn parse_header_string(value: &str) -> impl Iterator<Item = (&str, String)> {
220 value
221 .split_terminator(',')
222 .map(str::trim)
223 .filter_map(parse_header_key_value_string)
224}
225
226#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
227fn url_decode(value: &str) -> Option<String> {
228 let mut result = String::with_capacity(value.len());
229 let mut chars_to_decode = Vec::<u8>::new();
230 let mut all_chars = value.chars();
231
232 loop {
233 let ch = all_chars.next();
234
235 if ch.is_some() && ch.unwrap() == '%' {
236 chars_to_decode.push(
237 u8::from_str_radix(&format!("{}{}", all_chars.next()?, all_chars.next()?), 16)
238 .ok()?,
239 );
240 continue;
241 }
242
243 if !chars_to_decode.is_empty() {
244 result.push_str(std::str::from_utf8(&chars_to_decode).ok()?);
245 chars_to_decode.clear();
246 }
247
248 if let Some(c) = ch {
249 result.push(c);
250 } else {
251 return Some(result);
252 }
253 }
254}
255
256#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
257fn parse_header_key_value_string(key_value_string: &str) -> Option<(&str, String)> {
258 key_value_string
259 .split_once('=')
260 .map(|(key, value)| {
261 (
262 key.trim(),
263 url_decode(value.trim()).unwrap_or(value.to_string()),
264 )
265 })
266 .filter(|(key, value)| !key.is_empty() && !value.is_empty())
267}
268
269#[cfg(test)]
270#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
271mod tests {
272 pub(crate) fn run_env_test<T, F>(env_vars: T, f: F)
273 where
274 F: FnOnce(),
275 T: Into<Vec<(&'static str, &'static str)>>,
276 {
277 temp_env::with_vars(
278 env_vars
279 .into()
280 .iter()
281 .map(|&(k, v)| (k, Some(v)))
282 .collect::<Vec<(&'static str, Option<&'static str>)>>(),
283 f,
284 )
285 }
286
287 #[cfg(any(feature = "http-proto", feature = "http-json"))]
288 #[test]
289 fn test_default_http_endpoint() {
290 let exporter_builder = crate::new_exporter().http();
291
292 assert_eq!(exporter_builder.exporter_config.endpoint, "");
293 }
294
295 #[cfg(feature = "grpc-tonic")]
296 #[test]
297 fn test_default_tonic_endpoint() {
298 let exporter_builder = crate::new_exporter().tonic();
299
300 assert_eq!(exporter_builder.exporter_config.endpoint, "");
301 }
302
303 #[test]
304 fn test_default_protocol() {
305 #[cfg(all(
306 feature = "http-json",
307 not(any(feature = "grpc-tonic", feature = "http-proto"))
308 ))]
309 {
310 assert_eq!(
311 crate::exporter::default_protocol(),
312 crate::Protocol::HttpJson
313 );
314 }
315
316 #[cfg(all(
317 feature = "http-proto",
318 not(any(feature = "grpc-tonic", feature = "http-json"))
319 ))]
320 {
321 assert_eq!(
322 crate::exporter::default_protocol(),
323 crate::Protocol::HttpBinary
324 );
325 }
326
327 #[cfg(all(
328 feature = "grpc-tonic",
329 not(any(feature = "http-proto", feature = "http-json"))
330 ))]
331 {
332 assert_eq!(crate::exporter::default_protocol(), crate::Protocol::Grpc);
333 }
334 }
335
336 #[test]
337 fn test_url_decode() {
338 let test_cases = vec![
339 ("v%201", Some("v 1")),
341 ("v 1", Some("v 1")),
342 ("%C3%B6%C3%A0%C2%A7%C3%96abcd%C3%84", Some("öà§ÖabcdÄ")),
343 ("v%XX1", None),
344 ];
345
346 for (encoded, expected_decoded) in test_cases {
347 assert_eq!(
348 super::url_decode(encoded),
349 expected_decoded.map(|v| v.to_string()),
350 )
351 }
352 }
353
354 #[test]
355 fn test_parse_header_string() {
356 let test_cases = vec![
357 ("k1=v1", vec![("k1", "v1")]),
359 ("k1=v1,k2=v2", vec![("k1", "v1"), ("k2", "v2")]),
360 ("k1=v1=10,k2,k3", vec![("k1", "v1=10")]),
361 ("k1=v1,,,k2,k3=10", vec![("k1", "v1"), ("k3", "10")]),
362 ];
363
364 for (input_str, expected_headers) in test_cases {
365 assert_eq!(
366 super::parse_header_string(input_str).collect::<Vec<_>>(),
367 expected_headers
368 .into_iter()
369 .map(|(k, v)| (k, v.to_string()))
370 .collect::<Vec<_>>(),
371 )
372 }
373 }
374
375 #[test]
376 fn test_parse_header_key_value_string() {
377 let test_cases = vec![
378 ("k1=v1", Some(("k1", "v1"))),
380 (
381 "Authentication=Basic AAA",
382 Some(("Authentication", "Basic AAA")),
383 ),
384 (
385 "Authentication=Basic%20AAA",
386 Some(("Authentication", "Basic AAA")),
387 ),
388 ("k1=%XX", Some(("k1", "%XX"))),
389 ("", None),
390 ("=v1", None),
391 ("k1=", None),
392 ];
393
394 for (input_str, expected_headers) in test_cases {
395 assert_eq!(
396 super::parse_header_key_value_string(input_str),
397 expected_headers.map(|(k, v)| (k, v.to_string())),
398 )
399 }
400 }
401}