Skip to content

Commit fa1d5ed

Browse files
committed
prioritize code-based config over env vars in OTLP exporter
1 parent 2070e6c commit fa1d5ed

File tree

4 files changed

+219
-106
lines changed

4 files changed

+219
-106
lines changed

opentelemetry-otlp/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
`Error` which contained many variants unrelated to building an exporter, the
1818
new one returns specific variants applicable to building an exporter. Some
1919
variants might be applicable only on select features.
20+
- *Breaking* `ExportConfig`'s `timeout` field is now optional, and the order of
21+
priority changed in favor of compile time values. Additionally the `endpoint`
22+
field prioritizes compile time values over environment variables.
2023

2124
## 0.28.0
2225

opentelemetry-otlp/src/exporter/http/mod.rs

Lines changed: 50 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
use super::{
2-
default_headers, default_protocol, parse_header_string, ExporterBuildError,
2+
default_headers, default_protocol, parse_header_string, resolve_timeout, ExporterBuildError,
33
OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT,
44
};
5-
use crate::{
6-
ExportConfig, Protocol, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS,
7-
OTEL_EXPORTER_OTLP_TIMEOUT,
8-
};
5+
use crate::{ExportConfig, Protocol, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS};
96
use http::{HeaderName, HeaderValue, Uri};
107
use opentelemetry_http::HttpClient;
118
use opentelemetry_proto::transform::common::tonic::ResourceAttributesWithSchema;
@@ -112,19 +109,10 @@ impl HttpExporterBuilder {
112109
let endpoint = resolve_http_endpoint(
113110
signal_endpoint_var,
114111
signal_endpoint_path,
115-
self.exporter_config.endpoint.clone(),
112+
self.exporter_config.endpoint.as_deref(),
116113
)?;
117114

118-
let timeout = match env::var(signal_timeout_var)
119-
.ok()
120-
.or(env::var(OTEL_EXPORTER_OTLP_TIMEOUT).ok())
121-
{
122-
Some(val) => match val.parse() {
123-
Ok(milli_seconds) => Duration::from_millis(milli_seconds),
124-
Err(_) => self.exporter_config.timeout,
125-
},
126-
None => self.exporter_config.timeout,
127-
};
115+
let timeout = resolve_timeout(signal_timeout_var, self.exporter_config.timeout.as_ref());
128116

129117
#[allow(unused_mut)] // TODO - clippy thinks mut is not needed, but it is
130118
let mut http_client = self.http_config.client.take();
@@ -371,30 +359,27 @@ fn build_endpoint_uri(endpoint: &str, path: &str) -> Result<Uri, ExporterBuildEr
371359
fn resolve_http_endpoint(
372360
signal_endpoint_var: &str,
373361
signal_endpoint_path: &str,
374-
provided_endpoint: Option<String>,
362+
provided_endpoint: Option<&str>,
375363
) -> Result<Uri, ExporterBuildError> {
376-
// per signal env var is not modified
377-
if let Some(endpoint) = env::var(signal_endpoint_var)
364+
// programmatic configuration overrides any value set via environment variables
365+
if let Some(provider_endpoint) = provided_endpoint {
366+
provider_endpoint
367+
.parse()
368+
.map_err(|er: http::uri::InvalidUri| {
369+
ExporterBuildError::InvalidUri(provider_endpoint.to_string(), er.to_string())
370+
})
371+
} else if let Some(endpoint) = env::var(signal_endpoint_var)
378372
.ok()
379373
.and_then(|s| s.parse().ok())
380374
{
381-
return Ok(endpoint);
382-
}
383-
384-
// if signal env var is not set, then we check if the OTEL_EXPORTER_OTLP_ENDPOINT is set
385-
if let Some(endpoint) = env::var(OTEL_EXPORTER_OTLP_ENDPOINT)
375+
// per signal env var is not modified
376+
Ok(endpoint)
377+
} else if let Some(endpoint) = env::var(OTEL_EXPORTER_OTLP_ENDPOINT)
386378
.ok()
387379
.and_then(|s| build_endpoint_uri(&s, signal_endpoint_path).ok())
388380
{
389-
return Ok(endpoint);
390-
}
391-
392-
if let Some(provider_endpoint) = provided_endpoint {
393-
provider_endpoint
394-
.parse()
395-
.map_err(|er: http::uri::InvalidUri| {
396-
ExporterBuildError::InvalidUri(provider_endpoint, er.to_string())
397-
})
381+
// if signal env var is not set, then we check if the OTEL_EXPORTER_OTLP_ENDPOINT env var is set
382+
Ok(endpoint)
398383
} else {
399384
build_endpoint_uri(
400385
OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT,
@@ -481,12 +466,9 @@ mod tests {
481466
run_env_test(
482467
vec![(OTEL_EXPORTER_OTLP_ENDPOINT, "http://example.com")],
483468
|| {
484-
let endpoint = resolve_http_endpoint(
485-
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
486-
"/v1/traces",
487-
Some("http://localhost:4317".to_string()),
488-
)
489-
.unwrap();
469+
let endpoint =
470+
resolve_http_endpoint(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "/v1/traces", None)
471+
.unwrap();
490472
assert_eq!(endpoint, "http://example.com/v1/traces");
491473
},
492474
)
@@ -496,20 +478,36 @@ mod tests {
496478
fn test_not_append_signal_path_to_signal_env() {
497479
run_env_test(
498480
vec![(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "http://example.com")],
481+
|| {
482+
let endpoint =
483+
resolve_http_endpoint(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "/v1/traces", None)
484+
.unwrap();
485+
assert_eq!(endpoint, "http://example.com");
486+
},
487+
)
488+
}
489+
490+
#[test]
491+
fn test_priority_of_signal_env_over_generic_env() {
492+
run_env_test(
493+
vec![
494+
(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "http://example.com"),
495+
(OTEL_EXPORTER_OTLP_ENDPOINT, "http://wrong.com"),
496+
],
499497
|| {
500498
let endpoint = super::resolve_http_endpoint(
501499
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
502500
"/v1/traces",
503-
Some("http://localhost:4317".to_string()),
501+
None,
504502
)
505503
.unwrap();
506504
assert_eq!(endpoint, "http://example.com");
507505
},
508-
)
506+
);
509507
}
510508

511509
#[test]
512-
fn test_priority_of_signal_env_over_generic_env() {
510+
fn test_priority_of_code_based_config_over_envs() {
513511
run_env_test(
514512
vec![
515513
(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "http://example.com"),
@@ -519,24 +517,20 @@ mod tests {
519517
let endpoint = super::resolve_http_endpoint(
520518
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
521519
"/v1/traces",
522-
Some("http://localhost:4317".to_string()),
520+
Some("http://localhost:4317"),
523521
)
524522
.unwrap();
525-
assert_eq!(endpoint, "http://example.com");
523+
assert_eq!(endpoint, "http://localhost:4317");
526524
},
527525
);
528526
}
529527

530528
#[test]
531-
fn test_use_provided_or_default_when_others_missing() {
529+
fn test_use_default_when_others_missing() {
532530
run_env_test(vec![], || {
533-
let endpoint = super::resolve_http_endpoint(
534-
"NON_EXISTENT_VAR",
535-
"/v1/traces",
536-
Some("http://localhost:4317".to_string()),
537-
)
538-
.unwrap();
539-
assert_eq!(endpoint, "http://localhost:4317/");
531+
let endpoint =
532+
super::resolve_http_endpoint("NON_EXISTENT_VAR", "/v1/traces", None).unwrap();
533+
assert_eq!(endpoint, "http://localhost:4318/v1/traces");
540534
});
541535
}
542536

@@ -568,7 +562,7 @@ mod tests {
568562
let endpoint = super::resolve_http_endpoint(
569563
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
570564
"/v1/traces",
571-
Some("http://localhost:4317".to_string()),
565+
None,
572566
)
573567
.unwrap();
574568
assert_eq!(endpoint, "http://example.com/v1/traces");
@@ -582,7 +576,7 @@ mod tests {
582576
let result = super::resolve_http_endpoint(
583577
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
584578
"/v1/traces",
585-
Some("-*/*-/*-//-/-/yet-another-invalid-uri".to_string()),
579+
Some("-*/*-/*-//-/-/yet-another-invalid-uri"),
586580
);
587581
assert!(result.is_err());
588582
// You may also want to assert on the specific error type if applicable
@@ -722,7 +716,7 @@ mod tests {
722716
let url = resolve_http_endpoint(
723717
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
724718
"/v1/traces",
725-
exporter.exporter_config.endpoint,
719+
exporter.exporter_config.endpoint.as_deref(),
726720
)
727721
.unwrap();
728722

@@ -737,7 +731,7 @@ mod tests {
737731
let url = resolve_http_endpoint(
738732
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
739733
"/v1/traces",
740-
exporter.exporter_config.endpoint,
734+
exporter.exporter_config.endpoint.as_deref(),
741735
)
742736
.unwrap();
743737

opentelemetry-otlp/src/exporter/mod.rs

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::exporter::tonic::TonicExporterBuilder;
99
use crate::Protocol;
1010
#[cfg(feature = "serialize")]
1111
use serde::{Deserialize, Serialize};
12+
use std::env;
1213
use std::fmt::{Display, Formatter};
1314
use std::str::FromStr;
1415
use std::time::Duration;
@@ -76,19 +77,19 @@ pub struct ExportConfig {
7677
pub protocol: Protocol,
7778

7879
/// The timeout to the collector.
79-
pub timeout: Duration,
80+
pub timeout: Option<Duration>,
8081
}
8182

8283
impl Default for ExportConfig {
8384
fn default() -> Self {
8485
let protocol = default_protocol();
8586

86-
ExportConfig {
87+
Self {
8788
endpoint: None,
8889
// don't use default_endpoint(protocol) here otherwise we
8990
// won't know if user provided a value
9091
protocol,
91-
timeout: OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT,
92+
timeout: None,
9293
}
9394
}
9495
}
@@ -250,7 +251,7 @@ impl<B: HasExportConfig> WithExportConfig for B {
250251
}
251252

252253
fn with_timeout(mut self, timeout: Duration) -> Self {
253-
self.export_config().timeout = timeout;
254+
self.export_config().timeout = Some(timeout);
254255
self
255256
}
256257

@@ -262,6 +263,27 @@ impl<B: HasExportConfig> WithExportConfig for B {
262263
}
263264
}
264265

266+
fn resolve_timeout(signal_timeout_var: &str, provided_timeout: Option<&Duration>) -> Duration {
267+
// programmatic configuration overrides any value set via environment variables
268+
if let Some(timeout) = provided_timeout {
269+
*timeout
270+
} else if let Some(timeout) = env::var(signal_timeout_var)
271+
.ok()
272+
.and_then(|s| s.parse().ok())
273+
{
274+
// per signal env var is not modified
275+
Duration::from_millis(timeout)
276+
} else if let Some(timeout) = env::var(OTEL_EXPORTER_OTLP_TIMEOUT)
277+
.ok()
278+
.and_then(|s| s.parse().ok())
279+
{
280+
// if signal env var is not set, then we check if the OTEL_EXPORTER_OTLP_TIMEOUT env var is set
281+
Duration::from_millis(timeout)
282+
} else {
283+
OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT
284+
}
285+
}
286+
265287
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
266288
fn parse_header_string(value: &str) -> impl Iterator<Item = (&str, String)> {
267289
value
@@ -342,7 +364,6 @@ mod tests {
342364
#[cfg(feature = "logs")]
343365
#[cfg(any(feature = "http-proto", feature = "http-json"))]
344366
#[test]
345-
#[ignore = "Unstable due to interference from env variable tests. Re-enable after https://github.com/open-telemetry/opentelemetry-rust/issues/2818 is resolved."]
346367
fn export_builder_error_invalid_http_endpoint() {
347368
use std::time::Duration;
348369

@@ -351,7 +372,7 @@ mod tests {
351372
let ex_config = ExportConfig {
352373
endpoint: Some("invalid_uri/something".to_string()),
353374
protocol: Protocol::HttpBinary,
354-
timeout: Duration::from_secs(10),
375+
timeout: Some(Duration::from_secs(10)),
355376
};
356377

357378
let exporter_result = LogExporter::builder()
@@ -371,7 +392,6 @@ mod tests {
371392

372393
#[cfg(feature = "grpc-tonic")]
373394
#[tokio::test]
374-
#[ignore = "Unstable due to interference from env variable tests. Re-enable after https://github.com/open-telemetry/opentelemetry-rust/issues/2818 is resolved."]
375395
async fn export_builder_error_invalid_grpc_endpoint() {
376396
use std::time::Duration;
377397

@@ -380,7 +400,7 @@ mod tests {
380400
let ex_config = ExportConfig {
381401
endpoint: Some("invalid_uri/something".to_string()),
382402
protocol: Protocol::Grpc,
383-
timeout: Duration::from_secs(10),
403+
timeout: Some(Duration::from_secs(10)),
384404
};
385405

386406
let exporter_result = LogExporter::builder()
@@ -500,4 +520,44 @@ mod tests {
500520
)
501521
}
502522
}
523+
524+
#[test]
525+
fn test_priority_of_signal_env_over_generic_env_for_timeout() {
526+
run_env_test(
527+
vec![
528+
(crate::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, "3000"),
529+
(super::OTEL_EXPORTER_OTLP_TIMEOUT, "2000"),
530+
],
531+
|| {
532+
let timeout =
533+
super::resolve_timeout(crate::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, None);
534+
assert_eq!(timeout.as_millis(), 3000);
535+
},
536+
);
537+
}
538+
539+
#[test]
540+
fn test_priority_of_code_based_config_over_envs_for_timeout() {
541+
run_env_test(
542+
vec![
543+
(crate::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, "3000"),
544+
(super::OTEL_EXPORTER_OTLP_TIMEOUT, "2000"),
545+
],
546+
|| {
547+
let timeout = super::resolve_timeout(
548+
crate::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
549+
Some(&std::time::Duration::from_millis(1000)),
550+
);
551+
assert_eq!(timeout.as_millis(), 1000);
552+
},
553+
);
554+
}
555+
556+
#[test]
557+
fn test_use_default_when_others_missing_for_timeout() {
558+
run_env_test(vec![], || {
559+
let timeout = super::resolve_timeout(crate::OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, None);
560+
assert_eq!(timeout.as_millis(), 10_000);
561+
});
562+
}
503563
}

0 commit comments

Comments
 (0)