Skip to content

Commit 506399f

Browse files
committed
chore(otlp): add environment variable parsing for default exporter feature selection
1 parent 19b21c2 commit 506399f

File tree

3 files changed

+122
-28
lines changed

3 files changed

+122
-28
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{
2-
default_headers, default_protocol, parse_header_string, resolve_timeout, ExporterBuildError,
2+
default_headers, parse_header_string, resolve_timeout, ExporterBuildError,
33
OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT,
44
};
55
use crate::{ExportConfig, Protocol, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS};
@@ -155,7 +155,7 @@ impl Default for HttpExporterBuilder {
155155
fn default() -> Self {
156156
HttpExporterBuilder {
157157
exporter_config: ExportConfig {
158-
protocol: default_protocol(),
158+
protocol: Protocol::default(),
159159
..ExportConfig::default()
160160
},
161161
http_config: HttpConfig {

opentelemetry-otlp/src/exporter/mod.rs

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ pub const OTEL_EXPORTER_OTLP_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL";
2929
/// Compression algorithm to use, defaults to none.
3030
pub const OTEL_EXPORTER_OTLP_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_COMPRESSION";
3131

32+
/// Protocol value for HTTP with protobuf encoding
33+
pub const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF: &str = "http/protobuf";
34+
/// Protocol value for gRPC
35+
pub const OTEL_EXPORTER_OTLP_PROTOCOL_GRPC: &str = "grpc";
36+
/// Protocol value for HTTP with JSON encoding
37+
pub const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON: &str = "http/json";
38+
3239
/// Max waiting time for the backend to process each signal batch, defaults to 10 seconds.
3340
pub const OTEL_EXPORTER_OTLP_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_TIMEOUT";
3441
/// Default max waiting time for the backend to process each signal batch.
@@ -66,7 +73,7 @@ pub struct ExportConfig {
6673
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
6774
impl Default for ExportConfig {
6875
fn default() -> Self {
69-
let protocol = default_protocol();
76+
let protocol = Protocol::default();
7077

7178
Self {
7279
endpoint: None,
@@ -170,25 +177,34 @@ fn resolve_compression_from_env(
170177
}
171178
}
172179

173-
/// Returns the default protocol based on enabled features.
180+
/// Returns the default protocol based on environment variable or enabled features.
174181
///
175182
/// Priority order (first available wins):
176-
/// 1. http-json (if enabled)
177-
/// 2. http-proto (if enabled)
178-
/// 3. grpc-tonic (if enabled)
183+
/// 1. OTEL_EXPORTER_OTLP_PROTOCOL environment variable (if set and feature is enabled)
184+
/// 2. http-json (if enabled)
185+
/// 3. http-proto (if enabled)
186+
/// 4. grpc-tonic (if enabled)
179187
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
180-
fn default_protocol() -> Protocol {
181-
#[cfg(feature = "http-json")]
182-
return Protocol::HttpJson;
183-
184-
#[cfg(all(feature = "http-proto", not(feature = "http-json")))]
185-
return Protocol::HttpBinary;
186-
187-
#[cfg(all(
188-
feature = "grpc-tonic",
189-
not(any(feature = "http-proto", feature = "http-json"))
190-
))]
191-
return Protocol::Grpc;
188+
impl Default for Protocol {
189+
fn default() -> Self {
190+
// Check environment variable first
191+
if let Some(protocol) = Protocol::from_env() {
192+
return protocol;
193+
}
194+
195+
// Fall back to feature-based defaults
196+
#[cfg(feature = "http-json")]
197+
return Protocol::HttpJson;
198+
199+
#[cfg(all(feature = "http-proto", not(feature = "http-json")))]
200+
return Protocol::HttpBinary;
201+
202+
#[cfg(all(
203+
feature = "grpc-tonic",
204+
not(any(feature = "http-proto", feature = "http-json"))
205+
))]
206+
return Protocol::Grpc;
207+
}
192208
}
193209

194210
/// default user-agent headers
@@ -451,21 +467,15 @@ mod tests {
451467
not(any(feature = "grpc-tonic", feature = "http-proto"))
452468
))]
453469
{
454-
assert_eq!(
455-
crate::exporter::default_protocol(),
456-
crate::Protocol::HttpJson
457-
);
470+
assert_eq!(crate::Protocol::default(), crate::Protocol::HttpJson);
458471
}
459472

460473
#[cfg(all(
461474
feature = "http-proto",
462475
not(any(feature = "grpc-tonic", feature = "http-json"))
463476
))]
464477
{
465-
assert_eq!(
466-
crate::exporter::default_protocol(),
467-
crate::Protocol::HttpBinary
468-
);
478+
assert_eq!(crate::Protocol::default(), crate::Protocol::HttpBinary);
469479
}
470480

471481
#[cfg(all(
@@ -477,6 +487,58 @@ mod tests {
477487
}
478488
}
479489

490+
#[test]
491+
fn test_protocol_from_env() {
492+
use crate::{Protocol, OTEL_EXPORTER_OTLP_PROTOCOL};
493+
494+
// Test with no env var set - should return None
495+
temp_env::with_var_unset(OTEL_EXPORTER_OTLP_PROTOCOL, || {
496+
assert_eq!(Protocol::from_env(), None);
497+
});
498+
499+
// Test with grpc protocol
500+
#[cfg(feature = "grpc-tonic")]
501+
run_env_test(vec![(OTEL_EXPORTER_OTLP_PROTOCOL, "grpc")], || {
502+
assert_eq!(Protocol::from_env(), Some(Protocol::Grpc));
503+
});
504+
505+
// Test with http/protobuf protocol
506+
#[cfg(feature = "http-proto")]
507+
run_env_test(vec![(OTEL_EXPORTER_OTLP_PROTOCOL, "http/protobuf")], || {
508+
assert_eq!(Protocol::from_env(), Some(Protocol::HttpBinary));
509+
});
510+
511+
// Test with http/json protocol
512+
#[cfg(feature = "http-json")]
513+
run_env_test(vec![(OTEL_EXPORTER_OTLP_PROTOCOL, "http/json")], || {
514+
assert_eq!(Protocol::from_env(), Some(Protocol::HttpJson));
515+
});
516+
517+
// Test with invalid protocol - should return None
518+
run_env_test(vec![(OTEL_EXPORTER_OTLP_PROTOCOL, "invalid")], || {
519+
assert_eq!(Protocol::from_env(), None);
520+
});
521+
}
522+
523+
#[test]
524+
fn test_default_protocol_respects_env() {
525+
// Test that env var takes precedence over feature-based defaults
526+
#[cfg(all(feature = "http-json", feature = "http-proto"))]
527+
run_env_test(
528+
vec![(crate::OTEL_EXPORTER_OTLP_PROTOCOL, "http/protobuf")],
529+
|| {
530+
// Even though http-json would be the default, env var should override
531+
assert_eq!(crate::Protocol::default(), crate::Protocol::HttpBinary);
532+
},
533+
);
534+
535+
#[cfg(all(feature = "grpc-tonic", feature = "http-json"))]
536+
run_env_test(vec![(crate::OTEL_EXPORTER_OTLP_PROTOCOL, "grpc")], || {
537+
// Even though http-json would be the default, env var should override
538+
assert_eq!(crate::Protocol::default(), crate::Protocol::Grpc);
539+
});
540+
}
541+
480542
#[test]
481543
fn test_url_decode() {
482544
let test_cases = vec![

opentelemetry-otlp/src/lib.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,9 @@ pub use crate::exporter::tonic::{HasTonicConfig, WithTonicConfig};
409409
pub use crate::exporter::{
410410
HasExportConfig, WithExportConfig, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT,
411411
OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_PROTOCOL,
412-
OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT,
412+
OTEL_EXPORTER_OTLP_PROTOCOL_GRPC, OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON,
413+
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF, OTEL_EXPORTER_OTLP_TIMEOUT,
414+
OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT,
413415
};
414416

415417
#[cfg(feature = "experimental-http-retry")]
@@ -459,6 +461,36 @@ pub enum Protocol {
459461
HttpJson,
460462
}
461463

464+
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
465+
impl Protocol {
466+
/// Attempts to parse a protocol from the `OTEL_EXPORTER_OTLP_PROTOCOL` environment variable.
467+
///
468+
/// Returns `None` if:
469+
/// - The environment variable is not set
470+
/// - The value doesn't match a known protocol
471+
/// - The specified protocol's feature is not enabled
472+
pub fn from_env() -> Option<Self> {
473+
#[cfg(feature = "grpc-tonic")]
474+
use crate::exporter::OTEL_EXPORTER_OTLP_PROTOCOL_GRPC;
475+
#[cfg(feature = "http-json")]
476+
use crate::exporter::OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON;
477+
#[cfg(feature = "http-proto")]
478+
use crate::exporter::OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF;
479+
480+
let protocol = std::env::var(OTEL_EXPORTER_OTLP_PROTOCOL).ok()?;
481+
482+
match protocol.as_str() {
483+
#[cfg(feature = "grpc-tonic")]
484+
OTEL_EXPORTER_OTLP_PROTOCOL_GRPC => Some(Protocol::Grpc),
485+
#[cfg(feature = "http-proto")]
486+
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF => Some(Protocol::HttpBinary),
487+
#[cfg(feature = "http-json")]
488+
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON => Some(Protocol::HttpJson),
489+
_ => None,
490+
}
491+
}
492+
}
493+
462494
#[derive(Debug, Default)]
463495
#[doc(hidden)]
464496
/// Placeholder type when no exporter pipeline has been configured in telemetry pipeline.

0 commit comments

Comments
 (0)