1
- use crate :: { ExportConfig , Protocol , OTEL_EXPORTER_OTLP_ENDPOINT , OTEL_EXPORTER_OTLP_TIMEOUT } ;
1
+ use crate :: {
2
+ ExportConfig , Protocol , OTEL_EXPORTER_OTLP_ENDPOINT , OTEL_EXPORTER_OTLP_HEADERS ,
3
+ OTEL_EXPORTER_OTLP_TIMEOUT ,
4
+ } ;
2
5
use http:: { HeaderName , HeaderValue , Uri } ;
3
6
use opentelemetry_http:: HttpClient ;
4
7
use std:: collections:: HashMap ;
@@ -143,6 +146,7 @@ impl HttpExporterBuilder {
143
146
signal_endpoint_var : & str ,
144
147
signal_endpoint_path : & str ,
145
148
signal_timeout_var : & str ,
149
+ signal_http_headers_var : & str ,
146
150
) -> Result < OtlpHttpClient , crate :: Error > {
147
151
let endpoint = resolve_endpoint (
148
152
signal_endpoint_var,
@@ -168,7 +172,7 @@ impl HttpExporterBuilder {
168
172
. ok_or ( crate :: Error :: NoHttpClient ) ?;
169
173
170
174
#[ allow( clippy:: mutable_key_type) ] // http headers are not mutated
171
- let headers = self
175
+ let mut headers: HashMap < HeaderName , HeaderValue > = self
172
176
. http_config
173
177
. headers
174
178
. take ( )
@@ -182,6 +186,13 @@ impl HttpExporterBuilder {
182
186
} )
183
187
. collect ( ) ;
184
188
189
+ // read headers from env var - signal specific env var is preferred over general
190
+ if let Ok ( input) =
191
+ env:: var ( signal_http_headers_var) . or_else ( |_| env:: var ( OTEL_EXPORTER_OTLP_HEADERS ) )
192
+ {
193
+ add_header_from_string ( & input, & mut headers) ;
194
+ }
195
+
185
196
Ok ( OtlpHttpClient :: new ( http_client, endpoint, headers, timeout) )
186
197
}
187
198
@@ -190,12 +201,16 @@ impl HttpExporterBuilder {
190
201
pub fn build_span_exporter (
191
202
mut self ,
192
203
) -> Result < crate :: SpanExporter , opentelemetry:: trace:: TraceError > {
193
- use crate :: { OTEL_EXPORTER_OTLP_TRACES_ENDPOINT , OTEL_EXPORTER_OTLP_TRACES_TIMEOUT } ;
204
+ use crate :: {
205
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT , OTEL_EXPORTER_OTLP_TRACES_HEADERS ,
206
+ OTEL_EXPORTER_OTLP_TRACES_TIMEOUT ,
207
+ } ;
194
208
195
209
let client = self . build_client (
196
210
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ,
197
211
"/v1/traces" ,
198
212
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT ,
213
+ OTEL_EXPORTER_OTLP_TRACES_HEADERS ,
199
214
) ?;
200
215
201
216
Ok ( crate :: SpanExporter :: new ( client) )
@@ -204,12 +219,16 @@ impl HttpExporterBuilder {
204
219
/// Create a log exporter with the current configuration
205
220
#[ cfg( feature = "logs" ) ]
206
221
pub fn build_log_exporter ( mut self ) -> opentelemetry:: logs:: LogResult < crate :: LogExporter > {
207
- use crate :: { OTEL_EXPORTER_OTLP_LOGS_ENDPOINT , OTEL_EXPORTER_OTLP_LOGS_TIMEOUT } ;
222
+ use crate :: {
223
+ OTEL_EXPORTER_OTLP_LOGS_ENDPOINT , OTEL_EXPORTER_OTLP_LOGS_HEADERS ,
224
+ OTEL_EXPORTER_OTLP_LOGS_TIMEOUT ,
225
+ } ;
208
226
209
227
let client = self . build_client (
210
228
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT ,
211
229
"/v1/logs" ,
212
230
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT ,
231
+ OTEL_EXPORTER_OTLP_LOGS_HEADERS ,
213
232
) ?;
214
233
215
234
Ok ( crate :: LogExporter :: new ( client) )
@@ -222,12 +241,16 @@ impl HttpExporterBuilder {
222
241
aggregation_selector : Box < dyn opentelemetry_sdk:: metrics:: reader:: AggregationSelector > ,
223
242
temporality_selector : Box < dyn opentelemetry_sdk:: metrics:: reader:: TemporalitySelector > ,
224
243
) -> opentelemetry:: metrics:: Result < crate :: MetricsExporter > {
225
- use crate :: { OTEL_EXPORTER_OTLP_METRICS_ENDPOINT , OTEL_EXPORTER_OTLP_METRICS_TIMEOUT } ;
244
+ use crate :: {
245
+ OTEL_EXPORTER_OTLP_METRICS_ENDPOINT , OTEL_EXPORTER_OTLP_METRICS_HEADERS ,
246
+ OTEL_EXPORTER_OTLP_METRICS_TIMEOUT ,
247
+ } ;
226
248
227
249
let client = self . build_client (
228
250
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT ,
229
251
"/v1/metrics" ,
230
252
OTEL_EXPORTER_OTLP_METRICS_TIMEOUT ,
253
+ OTEL_EXPORTER_OTLP_METRICS_HEADERS ,
231
254
) ?;
232
255
233
256
Ok ( crate :: MetricsExporter :: new (
@@ -291,6 +314,25 @@ fn resolve_endpoint(
291
314
. map_err ( From :: from)
292
315
}
293
316
317
+ #[ allow( clippy:: mutable_key_type) ] // http headers are not mutated
318
+ fn add_header_from_string ( input : & str , headers : & mut HashMap < HeaderName , HeaderValue > ) {
319
+ for pair in input. split_terminator ( ',' ) {
320
+ if pair. trim ( ) . is_empty ( ) {
321
+ continue ;
322
+ }
323
+ if let Some ( ( k, v) ) = pair. trim ( ) . split_once ( '=' ) {
324
+ if !k. trim ( ) . is_empty ( ) && !v. trim ( ) . is_empty ( ) {
325
+ if let ( Ok ( key) , Ok ( value) ) = (
326
+ HeaderName :: from_str ( k. trim ( ) ) ,
327
+ HeaderValue :: from_str ( v. trim ( ) ) ,
328
+ ) {
329
+ headers. insert ( key, value) ;
330
+ }
331
+ }
332
+ }
333
+ }
334
+ }
335
+
294
336
#[ cfg( test) ]
295
337
mod tests {
296
338
use crate :: { OTEL_EXPORTER_OTLP_ENDPOINT , OTEL_EXPORTER_OTLP_TRACES_ENDPOINT } ;
@@ -410,4 +452,90 @@ mod tests {
410
452
// You may also want to assert on the specific error type if applicable
411
453
} ) ;
412
454
}
455
+
456
+ #[ test]
457
+ fn test_add_header_from_string ( ) {
458
+ use http:: { HeaderName , HeaderValue } ;
459
+ use std:: collections:: HashMap ;
460
+ let test_cases = vec ! [
461
+ // Format: (input_str, expected_headers)
462
+ ( "k1=v1" , vec![ ( "k1" , "v1" ) ] ) ,
463
+ ( "k1=v1,k2=v2" , vec![ ( "k1" , "v1" ) , ( "k2" , "v2" ) ] ) ,
464
+ ( "k1=v1=10,k2,k3" , vec![ ( "k1" , "v1=10" ) ] ) ,
465
+ ( "k1=v1,,,k2,k3=10" , vec![ ( "k1" , "v1" ) , ( "k3" , "10" ) ] ) ,
466
+ ] ;
467
+
468
+ for ( input_str, expected_headers) in test_cases {
469
+ #[ allow( clippy:: mutable_key_type) ] // http headers are not mutated
470
+ let mut headers: HashMap < HeaderName , HeaderValue > = HashMap :: new ( ) ;
471
+ super :: add_header_from_string ( input_str, & mut headers) ;
472
+
473
+ assert_eq ! (
474
+ headers. len( ) ,
475
+ expected_headers. len( ) ,
476
+ "Failed on input: {}" ,
477
+ input_str
478
+ ) ;
479
+
480
+ for ( expected_key, expected_value) in expected_headers {
481
+ assert_eq ! (
482
+ headers. get( & HeaderName :: from_static( expected_key) ) ,
483
+ Some ( & HeaderValue :: from_static( expected_value) ) ,
484
+ "Failed on key: {} with input: {}" ,
485
+ expected_key,
486
+ input_str
487
+ ) ;
488
+ }
489
+ }
490
+ }
491
+
492
+ #[ test]
493
+ fn test_merge_header_from_string ( ) {
494
+ use http:: { HeaderName , HeaderValue } ;
495
+ use std:: collections:: HashMap ;
496
+ #[ allow( clippy:: mutable_key_type) ] // http headers are not mutated
497
+ let mut headers: HashMap < HeaderName , HeaderValue > = std:: collections:: HashMap :: new ( ) ;
498
+ headers. insert (
499
+ HeaderName :: from_static ( "k1" ) ,
500
+ HeaderValue :: from_static ( "v1" ) ,
501
+ ) ;
502
+ headers. insert (
503
+ HeaderName :: from_static ( "k2" ) ,
504
+ HeaderValue :: from_static ( "v2" ) ,
505
+ ) ;
506
+ let test_cases = vec ! [
507
+ // Format: (input_str, expected_headers)
508
+ ( "k1=v1_new" , vec![ ( "k1" , "v1_new" ) , ( "k2" , "v2" ) ] ) ,
509
+ (
510
+ "k3=val=10,22,34,k4=,k5=10" ,
511
+ vec![
512
+ ( "k1" , "v1_new" ) ,
513
+ ( "k2" , "v2" ) ,
514
+ ( "k3" , "val=10" ) ,
515
+ ( "k5" , "10" ) ,
516
+ ] ,
517
+ ) ,
518
+ ] ;
519
+
520
+ for ( input_str, expected_headers) in test_cases {
521
+ super :: add_header_from_string ( input_str, & mut headers) ;
522
+
523
+ assert_eq ! (
524
+ headers. len( ) ,
525
+ expected_headers. len( ) ,
526
+ "Failed on input: {}" ,
527
+ input_str
528
+ ) ;
529
+
530
+ for ( expected_key, expected_value) in expected_headers {
531
+ assert_eq ! (
532
+ headers. get( & HeaderName :: from_static( expected_key) ) ,
533
+ Some ( & HeaderValue :: from_static( expected_value) ) ,
534
+ "Failed on key: {} with input: {}" ,
535
+ expected_key,
536
+ input_str
537
+ ) ;
538
+ }
539
+ }
540
+ }
413
541
}
0 commit comments