Skip to content

Commit 5eaaf53

Browse files
authored
Allow specifying OTLP HTTP headers from env variable (#1290)
1 parent 2e6a43d commit 5eaaf53

File tree

8 files changed

+161
-10
lines changed

8 files changed

+161
-10
lines changed

opentelemetry-otlp/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Add `build_{signal}_exporter` methods to client builders (#1187)
88
- Add `grpcio` metrics exporter (#1202)
9+
- Allow specifying OTLP HTTP headers from env variable (#1290)
910

1011
### Changed
1112

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

+133-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
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+
};
25
use http::{HeaderName, HeaderValue, Uri};
36
use opentelemetry_http::HttpClient;
47
use std::collections::HashMap;
@@ -143,6 +146,7 @@ impl HttpExporterBuilder {
143146
signal_endpoint_var: &str,
144147
signal_endpoint_path: &str,
145148
signal_timeout_var: &str,
149+
signal_http_headers_var: &str,
146150
) -> Result<OtlpHttpClient, crate::Error> {
147151
let endpoint = resolve_endpoint(
148152
signal_endpoint_var,
@@ -168,7 +172,7 @@ impl HttpExporterBuilder {
168172
.ok_or(crate::Error::NoHttpClient)?;
169173

170174
#[allow(clippy::mutable_key_type)] // http headers are not mutated
171-
let headers = self
175+
let mut headers: HashMap<HeaderName, HeaderValue> = self
172176
.http_config
173177
.headers
174178
.take()
@@ -182,6 +186,13 @@ impl HttpExporterBuilder {
182186
})
183187
.collect();
184188

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+
185196
Ok(OtlpHttpClient::new(http_client, endpoint, headers, timeout))
186197
}
187198

@@ -190,12 +201,16 @@ impl HttpExporterBuilder {
190201
pub fn build_span_exporter(
191202
mut self,
192203
) -> 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+
};
194208

195209
let client = self.build_client(
196210
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
197211
"/v1/traces",
198212
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
213+
OTEL_EXPORTER_OTLP_TRACES_HEADERS,
199214
)?;
200215

201216
Ok(crate::SpanExporter::new(client))
@@ -204,12 +219,16 @@ impl HttpExporterBuilder {
204219
/// Create a log exporter with the current configuration
205220
#[cfg(feature = "logs")]
206221
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+
};
208226

209227
let client = self.build_client(
210228
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
211229
"/v1/logs",
212230
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
231+
OTEL_EXPORTER_OTLP_LOGS_HEADERS,
213232
)?;
214233

215234
Ok(crate::LogExporter::new(client))
@@ -222,12 +241,16 @@ impl HttpExporterBuilder {
222241
aggregation_selector: Box<dyn opentelemetry_sdk::metrics::reader::AggregationSelector>,
223242
temporality_selector: Box<dyn opentelemetry_sdk::metrics::reader::TemporalitySelector>,
224243
) -> 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+
};
226248

227249
let client = self.build_client(
228250
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
229251
"/v1/metrics",
230252
OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
253+
OTEL_EXPORTER_OTLP_METRICS_HEADERS,
231254
)?;
232255

233256
Ok(crate::MetricsExporter::new(
@@ -291,6 +314,25 @@ fn resolve_endpoint(
291314
.map_err(From::from)
292315
}
293316

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+
294336
#[cfg(test)]
295337
mod tests {
296338
use crate::{OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT};
@@ -410,4 +452,90 @@ mod tests {
410452
// You may also want to assert on the specific error type if applicable
411453
});
412454
}
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+
}
413541
}

opentelemetry-otlp/src/exporter/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ use std::time::Duration;
2121
pub const OTEL_EXPORTER_OTLP_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_ENDPOINT";
2222
/// Default target to which the exporter is going to send signals.
2323
pub const OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT: &str = OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT;
24+
/// Key-value pairs to be used as headers associated with gRPC or HTTP requests
25+
/// Example: `k1=v1,k2=v2`
26+
/// Note: as of now, this is only supported for HTTP requests.
27+
pub const OTEL_EXPORTER_OTLP_HEADERS: &str = "OTEL_EXPORTER_OTLP_HEADERS";
2428
/// Protocol the exporter will use. Either `http/protobuf` or `grpc`.
2529
pub const OTEL_EXPORTER_OTLP_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL";
2630
/// Compression algorithm to use, defaults to none.

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ impl TonicExporterBuilder {
215215
signal_compression_var: &str,
216216
) -> Result<(Channel, BoxInterceptor, Option<CompressionEncoding>), crate::Error> {
217217
let config = &mut self.exporter_config;
218-
let tonic_config = &mut self.tonic_config;
218+
let tonic_config: &mut TonicConfig = &mut self.tonic_config;
219219

220220
let endpoint = match env::var(signal_endpoint_var)
221221
.ok()

opentelemetry-otlp/src/lib.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -211,25 +211,27 @@ pub use crate::exporter::ExportConfig;
211211
#[cfg(feature = "trace")]
212212
pub use crate::span::{
213213
OtlpTracePipeline, SpanExporter, SpanExporterBuilder, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
214-
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
214+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_HEADERS,
215+
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
215216
};
216217

217218
#[cfg(feature = "metrics")]
218219
pub use crate::metric::{
219220
MetricsExporter, MetricsExporterBuilder, OtlpMetricPipeline,
220221
OTEL_EXPORTER_OTLP_METRICS_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
221-
OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
222+
OTEL_EXPORTER_OTLP_METRICS_HEADERS, OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
222223
};
223224

224225
#[cfg(feature = "logs")]
225226
pub use crate::logs::{
226227
LogExporter, LogExporterBuilder, OtlpLogPipeline, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
227-
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
228+
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_HEADERS,
229+
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
228230
};
229231

230232
pub use crate::exporter::{
231233
HasExportConfig, WithExportConfig, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT,
232-
OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT, OTEL_EXPORTER_OTLP_PROTOCOL,
234+
OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_PROTOCOL,
233235
OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT, OTEL_EXPORTER_OTLP_TIMEOUT,
234236
OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT,
235237
};

opentelemetry-otlp/src/logs.rs

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ pub const OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_LOGS_ENDP
3030
/// Maximum time the OTLP exporter will wait for each batch logs export.
3131
pub const OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT";
3232

33+
/// Key-value pairs to be used as headers associated with gRPC or HTTP requests
34+
/// for sending logs.
35+
/// Example: `k1=v1,k2=v2`
36+
/// Note: this is only supported for HTTP.
37+
pub const OTEL_EXPORTER_OTLP_LOGS_HEADERS: &str = "OTEL_EXPORTER_OTLP_LOGS_HEADERS";
38+
3339
impl OtlpPipeline {
3440
/// Create a OTLP logging pipeline.
3541
pub fn logging(self) -> OtlpLogPipeline<NoExporterConfig> {

opentelemetry-otlp/src/metric.rs

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ pub const OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_METRIC
4040
pub const OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT";
4141
/// Compression algorithm to use, defaults to none.
4242
pub const OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_METRICS_COMPRESSION";
43+
/// Key-value pairs to be used as headers associated with gRPC or HTTP requests
44+
/// for sending metrics.
45+
/// Example: `k1=v1,k2=v2`
46+
/// Note: this is only supported for HTTP.
47+
pub const OTEL_EXPORTER_OTLP_METRICS_HEADERS: &str = "OTEL_EXPORTER_OTLP_METRICS_HEADERS";
4348
impl OtlpPipeline {
4449
/// Create a OTLP metrics pipeline.
4550
pub fn metrics<RT>(self, rt: RT) -> OtlpMetricPipeline<RT, NoExporterConfig>

opentelemetry-otlp/src/span.rs

+5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ pub const OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_TRACES_
3636
pub const OTEL_EXPORTER_OTLP_TRACES_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_TRACES_TIMEOUT";
3737
/// Compression algorithm to use, defaults to none.
3838
pub const OTEL_EXPORTER_OTLP_TRACES_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_TRACES_COMPRESSION";
39+
/// Key-value pairs to be used as headers associated with gRPC or HTTP requests
40+
/// for sending spans.
41+
/// Example: `k1=v1,k2=v2`
42+
/// Note: this is only supported for HTTP.
43+
pub const OTEL_EXPORTER_OTLP_TRACES_HEADERS: &str = "OTEL_EXPORTER_OTLP_TRACES_HEADERS";
3944

4045
impl OtlpPipeline {
4146
/// Create a OTLP tracing pipeline.

0 commit comments

Comments
 (0)