@@ -52,7 +52,7 @@ pub(crate) mod http;
5252pub ( crate ) mod tonic;
5353
5454/// Configuration for the OTLP exporter.
55- #[ derive( Debug ) ]
55+ #[ derive( Debug , Default ) ]
5656pub ( crate ) struct ExportConfig {
5757 /// The address of the OTLP collector.
5858 /// Default address will be used based on the protocol.
@@ -61,7 +61,9 @@ pub(crate) struct ExportConfig {
6161 pub endpoint : Option < String > ,
6262
6363 /// The protocol to use when communicating with the collector.
64- pub protocol : Protocol ,
64+ /// `None` means the protocol will be resolved from environment variables
65+ /// or feature defaults at build time.
66+ pub protocol : Option < Protocol > ,
6567
6668 /// The timeout to the collector.
6769 /// The default value is 10 seconds.
@@ -70,19 +72,26 @@ pub(crate) struct ExportConfig {
7072 pub timeout : Option < Duration > ,
7173}
7274
75+ /// Resolve protocol with priority:
76+ /// 1. Programmatic configuration (provided value)
77+ /// 2. Signal-specific environment variable
78+ /// 3. Generic OTEL_EXPORTER_OTLP_PROTOCOL environment variable
79+ /// 4. Feature-based default
7380#[ cfg( any( feature = "grpc-tonic" , feature = "http-proto" , feature = "http-json" ) ) ]
74- impl Default for ExportConfig {
75- fn default ( ) -> Self {
76- let protocol = Protocol :: default ( ) ;
77-
78- Self {
79- endpoint : None ,
80- // don't use default_endpoint(protocol) here otherwise we
81- // won't know if user provided a value
82- protocol,
83- timeout : None ,
84- }
81+ pub ( crate ) fn resolve_protocol (
82+ signal_protocol_var : & str ,
83+ provided_protocol : Option < Protocol > ,
84+ ) -> Protocol {
85+ if let Some ( protocol) = provided_protocol {
86+ return protocol;
87+ }
88+ if let Some ( protocol) = Protocol :: parse_from_env_var ( signal_protocol_var) {
89+ return protocol;
90+ }
91+ if let Some ( protocol) = Protocol :: from_env ( ) {
92+ return protocol;
8593 }
94+ Protocol :: feature_default ( )
8695}
8796
8897#[ derive( Error , Debug ) ]
@@ -186,33 +195,19 @@ fn resolve_compression_from_env(
186195 }
187196}
188197
189- /// Returns the default protocol based on environment variable or enabled features.
198+ /// Returns the default protocol based on enabled features.
199+ ///
200+ /// Note: This does not consult environment variables. Protocol resolution
201+ /// from environment variables is handled internally by the exporter builders.
190202///
191203/// Priority order (first available wins):
192- /// 1. OTEL_EXPORTER_OTLP_PROTOCOL environment variable (if set and feature is enabled)
193- /// 2. http-json (if enabled)
194- /// 3. http-proto (if enabled)
195- /// 4. grpc-tonic (if enabled)
204+ /// 1. http-json (if enabled)
205+ /// 2. http-proto (if enabled)
206+ /// 3. grpc-tonic (if enabled)
196207#[ cfg( any( feature = "grpc-tonic" , feature = "http-proto" , feature = "http-json" ) ) ]
197208impl Default for Protocol {
198209 fn default ( ) -> Self {
199- // Check environment variable first
200- if let Some ( protocol) = Protocol :: from_env ( ) {
201- return protocol;
202- }
203-
204- // Fall back to feature-based defaults
205- #[ cfg( feature = "http-json" ) ]
206- return Protocol :: HttpJson ;
207-
208- #[ cfg( all( feature = "http-proto" , not( feature = "http-json" ) ) ) ]
209- return Protocol :: HttpBinary ;
210-
211- #[ cfg( all(
212- feature = "grpc-tonic" ,
213- not( any( feature = "http-proto" , feature = "http-json" ) )
214- ) ) ]
215- return Protocol :: Grpc ;
210+ Protocol :: feature_default ( )
216211 }
217212}
218213
@@ -287,7 +282,7 @@ impl<B: HasExportConfig> WithExportConfig for B {
287282 }
288283
289284 fn with_protocol ( mut self , protocol : Protocol ) -> Self {
290- self . export_config ( ) . protocol = protocol;
285+ self . export_config ( ) . protocol = Some ( protocol) ;
291286 self
292287 }
293288
@@ -503,21 +498,26 @@ mod tests {
503498 }
504499
505500 #[ test]
506- fn test_default_protocol_respects_env ( ) {
507- // Test that env var takes precedence over feature-based defaults
508- #[ cfg( all( feature = "http-json" , feature = "http-proto" ) ) ]
509- run_env_test (
510- vec ! [ ( crate :: OTEL_EXPORTER_OTLP_PROTOCOL , "http/protobuf" ) ] ,
511- || {
512- // Even though http-json would be the default, env var should override
513- assert_eq ! ( crate :: Protocol :: default ( ) , crate :: Protocol :: HttpBinary ) ;
514- } ,
515- ) ;
501+ fn test_default_protocol_ignores_env ( ) {
502+ // Protocol::default() should always return the feature-based default,
503+ // NOT consult environment variables. Env var resolution is handled
504+ // by resolve_protocol().
516505
506+ // Without any env vars, default() equals feature_default()
507+ run_env_test ( vec ! [ ] , || {
508+ assert_eq ! (
509+ crate :: Protocol :: default ( ) ,
510+ crate :: Protocol :: feature_default( )
511+ ) ;
512+ } ) ;
513+
514+ // Even with a valid env var set, default() still equals feature_default()
517515 #[ cfg( all( feature = "grpc-tonic" , feature = "http-json" ) ) ]
518516 run_env_test ( vec ! [ ( crate :: OTEL_EXPORTER_OTLP_PROTOCOL , "grpc" ) ] , || {
519- // Even though http-json would be the default, env var should override
520- assert_eq ! ( crate :: Protocol :: default ( ) , crate :: Protocol :: Grpc ) ;
517+ assert_eq ! (
518+ crate :: Protocol :: default ( ) ,
519+ crate :: Protocol :: feature_default( )
520+ ) ;
521521 } ) ;
522522 }
523523
@@ -626,4 +626,94 @@ mod tests {
626626 assert_eq ! ( timeout. as_millis( ) , 10_000 ) ;
627627 } ) ;
628628 }
629+
630+ #[ test]
631+ fn test_protocol_parse_from_env_var ( ) {
632+ use crate :: Protocol ;
633+
634+ // Test with custom env var name
635+ temp_env:: with_var_unset ( "MY_CUSTOM_PROTOCOL_VAR" , || {
636+ assert_eq ! ( Protocol :: parse_from_env_var( "MY_CUSTOM_PROTOCOL_VAR" ) , None ) ;
637+ } ) ;
638+
639+ #[ cfg( feature = "http-proto" ) ]
640+ run_env_test ( vec ! [ ( "MY_CUSTOM_PROTOCOL_VAR" , "http/protobuf" ) ] , || {
641+ assert_eq ! (
642+ Protocol :: parse_from_env_var( "MY_CUSTOM_PROTOCOL_VAR" ) ,
643+ Some ( Protocol :: HttpBinary )
644+ ) ;
645+ } ) ;
646+
647+ #[ cfg( feature = "grpc-tonic" ) ]
648+ run_env_test ( vec ! [ ( "MY_CUSTOM_PROTOCOL_VAR" , "grpc" ) ] , || {
649+ assert_eq ! (
650+ Protocol :: parse_from_env_var( "MY_CUSTOM_PROTOCOL_VAR" ) ,
651+ Some ( Protocol :: Grpc )
652+ ) ;
653+ } ) ;
654+
655+ // Invalid value returns None
656+ run_env_test ( vec ! [ ( "MY_CUSTOM_PROTOCOL_VAR" , "invalid" ) ] , || {
657+ assert_eq ! ( Protocol :: parse_from_env_var( "MY_CUSTOM_PROTOCOL_VAR" ) , None ) ;
658+ } ) ;
659+ }
660+
661+ #[ cfg( feature = "http-proto" ) ]
662+ #[ test]
663+ fn test_resolve_protocol_signal_env_overrides_generic ( ) {
664+ use crate :: Protocol ;
665+
666+ run_env_test (
667+ vec ! [
668+ ( crate :: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL , "http/protobuf" ) ,
669+ ( crate :: OTEL_EXPORTER_OTLP_PROTOCOL , "grpc" ) ,
670+ ] ,
671+ || {
672+ let protocol =
673+ super :: resolve_protocol ( crate :: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL , None ) ;
674+ assert_eq ! ( protocol, Protocol :: HttpBinary ) ;
675+ } ,
676+ ) ;
677+ }
678+
679+ #[ cfg( feature = "http-proto" ) ]
680+ #[ test]
681+ fn test_resolve_protocol_code_overrides_all_envs ( ) {
682+ use crate :: Protocol ;
683+
684+ run_env_test (
685+ vec ! [
686+ ( crate :: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL , "grpc" ) ,
687+ ( crate :: OTEL_EXPORTER_OTLP_PROTOCOL , "grpc" ) ,
688+ ] ,
689+ || {
690+ let protocol = super :: resolve_protocol (
691+ crate :: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL ,
692+ Some ( Protocol :: HttpBinary ) ,
693+ ) ;
694+ assert_eq ! ( protocol, Protocol :: HttpBinary ) ;
695+ } ,
696+ ) ;
697+ }
698+
699+ #[ cfg( all( feature = "grpc-tonic" , feature = "http-proto" ) ) ]
700+ #[ test]
701+ fn test_resolve_protocol_falls_back_to_generic_env ( ) {
702+ use crate :: Protocol ;
703+
704+ run_env_test ( vec ! [ ( crate :: OTEL_EXPORTER_OTLP_PROTOCOL , "grpc" ) ] , || {
705+ let protocol = super :: resolve_protocol ( crate :: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL , None ) ;
706+ assert_eq ! ( protocol, Protocol :: Grpc ) ;
707+ } ) ;
708+ }
709+
710+ #[ test]
711+ fn test_resolve_protocol_falls_back_to_feature_default ( ) {
712+ use crate :: Protocol ;
713+
714+ run_env_test ( vec ! [ ] , || {
715+ let protocol = super :: resolve_protocol ( crate :: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL , None ) ;
716+ assert_eq ! ( protocol, Protocol :: feature_default( ) ) ;
717+ } ) ;
718+ }
629719}
0 commit comments