Skip to content

Commit f66e962

Browse files
authored
fix: endpoint urls for otlp http exporter. (#1210)
1 parent 1688ec6 commit f66e962

File tree

3 files changed

+161
-187
lines changed

3 files changed

+161
-187
lines changed

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

Lines changed: 154 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,11 @@ impl HttpExporterBuilder {
144144
signal_endpoint_path: &str,
145145
signal_timeout_var: &str,
146146
) -> Result<OtlpHttpClient, crate::Error> {
147-
let endpoint = match env::var(signal_endpoint_var)
148-
.ok()
149-
.or(env::var(OTEL_EXPORTER_OTLP_ENDPOINT).ok())
150-
.and_then(|s| s.parse().ok())
151-
{
152-
Some(val) => val,
153-
None => format!("{}{signal_endpoint_path}", self.exporter_config.endpoint).parse()?,
154-
};
147+
let endpoint = resolve_endpoint(
148+
signal_endpoint_var,
149+
signal_endpoint_path,
150+
self.exporter_config.endpoint.as_str(),
151+
)?;
155152

156153
let timeout = match env::var(signal_timeout_var)
157154
.ok()
@@ -265,3 +262,152 @@ impl OtlpHttpClient {
265262
}
266263
}
267264
}
265+
266+
// see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#endpoint-urls-for-otlphttp
267+
fn resolve_endpoint(
268+
signal_endpoint_var: &str,
269+
signal_endpoint_path: &str,
270+
provided_or_default_endpoint: &str,
271+
) -> Result<Uri, crate::Error> {
272+
// per signal env var is not modified
273+
if let Some(endpoint) = env::var(signal_endpoint_var)
274+
.ok()
275+
.and_then(|s| s.parse().ok())
276+
{
277+
return Ok(endpoint);
278+
}
279+
280+
// if signal env var is not set, then we check if the OTEL_EXPORTER_OTLP_ENDPOINT is set
281+
if let Some(endpoint) = env::var(OTEL_EXPORTER_OTLP_ENDPOINT)
282+
.ok()
283+
.and_then(|s| format!("{s}{signal_endpoint_path}").parse().ok())
284+
{
285+
return Ok(endpoint);
286+
}
287+
288+
// if neither works, we use the one provided in pipeline. If user never provide one, we will use the default one
289+
format!("{provided_or_default_endpoint}{signal_endpoint_path}")
290+
.parse()
291+
.map_err(From::from)
292+
}
293+
294+
#[cfg(test)]
295+
mod tests {
296+
use crate::{OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT};
297+
use std::sync::Mutex;
298+
299+
// Make sure env tests are not running concurrently
300+
static ENV_LOCK: Mutex<()> = Mutex::new(());
301+
302+
fn run_env_test<T, F>(env_vars: T, f: F)
303+
where
304+
F: FnOnce(),
305+
T: Into<Vec<(&'static str, &'static str)>>,
306+
{
307+
let _env_lock = ENV_LOCK.lock().expect("env test lock poisoned");
308+
let env_vars = env_vars.into();
309+
for (k, v) in env_vars.iter() {
310+
std::env::set_var(k, v);
311+
}
312+
f();
313+
for (k, _) in env_vars {
314+
std::env::remove_var(k);
315+
}
316+
}
317+
318+
#[test]
319+
fn test_append_signal_path_to_generic_env() {
320+
run_env_test(
321+
vec![(OTEL_EXPORTER_OTLP_ENDPOINT, "http://example.com")],
322+
|| {
323+
let endpoint = super::resolve_endpoint(
324+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
325+
"/v1/traces",
326+
"http://localhost:4317",
327+
)
328+
.unwrap();
329+
assert_eq!(endpoint, "http://example.com/v1/traces");
330+
},
331+
)
332+
}
333+
334+
#[test]
335+
fn test_not_append_signal_path_to_signal_env() {
336+
run_env_test(
337+
vec![(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "http://example.com")],
338+
|| {
339+
let endpoint = super::resolve_endpoint(
340+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
341+
"/v1/traces",
342+
"http://localhost:4317",
343+
)
344+
.unwrap();
345+
assert_eq!(endpoint, "http://example.com");
346+
},
347+
)
348+
}
349+
350+
#[test]
351+
fn test_priority_of_signal_env_over_generic_env() {
352+
run_env_test(
353+
vec![
354+
(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "http://example.com"),
355+
(OTEL_EXPORTER_OTLP_ENDPOINT, "http://wrong.com"),
356+
],
357+
|| {
358+
let endpoint = super::resolve_endpoint(
359+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
360+
"/v1/traces",
361+
"http://localhost:4317",
362+
)
363+
.unwrap();
364+
assert_eq!(endpoint, "http://example.com");
365+
},
366+
);
367+
}
368+
369+
#[test]
370+
fn test_use_provided_or_default_when_others_missing() {
371+
run_env_test(vec![], || {
372+
let endpoint =
373+
super::resolve_endpoint("NON_EXISTENT_VAR", "/v1/traces", "http://localhost:4317")
374+
.unwrap();
375+
assert_eq!(endpoint, "http://localhost:4317/v1/traces");
376+
});
377+
}
378+
379+
#[test]
380+
fn test_invalid_uri_in_signal_env_falls_back_to_generic_env() {
381+
run_env_test(
382+
vec![
383+
(
384+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
385+
"-*/*-/*-//-/-/invalid-uri",
386+
),
387+
(OTEL_EXPORTER_OTLP_ENDPOINT, "http://example.com"),
388+
],
389+
|| {
390+
let endpoint = super::resolve_endpoint(
391+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
392+
"/v1/traces",
393+
"http://localhost:4317",
394+
)
395+
.unwrap();
396+
assert_eq!(endpoint, "http://example.com/v1/traces");
397+
},
398+
);
399+
}
400+
401+
#[test]
402+
fn test_all_invalid_urls_falls_back_to_error() {
403+
run_env_test(vec![], || {
404+
let result = super::resolve_endpoint(
405+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
406+
"/v1/traces",
407+
"-*/*-/*-//-/-/yet-another-invalid-uri",
408+
);
409+
assert!(result.is_err());
410+
// You may also want to assert on the specific error type if applicable
411+
});
412+
}
413+
}

opentelemetry-otlp/src/exporter/mod.rs

Lines changed: 3 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,12 @@ pub trait WithExportConfig {
186186
///
187187
/// Note that protocols that are not supported by exporters will be ignore. The exporter
188188
/// will use default protocol in this case.
189+
///
190+
/// ## Note
191+
/// All exporters in this crate are only support one protocol thus choosing the protocol is an no-op at the moment
189192
fn with_protocol(self, protocol: Protocol) -> Self;
190193
/// Set the timeout to the collector.
191194
fn with_timeout(self, timeout: Duration) -> Self;
192-
/// Set the trace provider configuration from the given environment variables.
193-
///
194-
/// If the value in environment variables is illegal, will fall back to use default value.
195-
fn with_env(self) -> Self;
196195
/// Set export config. This will override all previous configuration.
197196
fn with_export_config(self, export_config: ExportConfig) -> Self;
198197
}
@@ -213,184 +212,10 @@ impl<B: HasExportConfig> WithExportConfig for B {
213212
self
214213
}
215214

216-
fn with_env(mut self) -> Self {
217-
let protocol = match std::env::var(OTEL_EXPORTER_OTLP_PROTOCOL)
218-
.unwrap_or_else(|_| OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT.to_string())
219-
.as_str()
220-
{
221-
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF => Protocol::HttpBinary,
222-
OTEL_EXPORTER_OTLP_PROTOCOL_GRPC => Protocol::Grpc,
223-
_ => default_protocol(),
224-
};
225-
226-
self.export_config().protocol = protocol;
227-
228-
let endpoint = match std::env::var(OTEL_EXPORTER_OTLP_ENDPOINT) {
229-
Ok(val) => val,
230-
Err(_) => default_endpoint(protocol),
231-
};
232-
self.export_config().endpoint = endpoint;
233-
234-
let timeout = match std::env::var(OTEL_EXPORTER_OTLP_TIMEOUT) {
235-
Ok(val) => u64::from_str(&val).unwrap_or(OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT),
236-
Err(_) => OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT,
237-
};
238-
self.export_config().timeout = Duration::from_secs(timeout);
239-
self
240-
}
241-
242215
fn with_export_config(mut self, exporter_config: ExportConfig) -> Self {
243216
self.export_config().endpoint = exporter_config.endpoint;
244217
self.export_config().protocol = exporter_config.protocol;
245218
self.export_config().timeout = exporter_config.timeout;
246219
self
247220
}
248221
}
249-
250-
#[cfg(test)]
251-
#[cfg(feature = "grpc-tonic")]
252-
mod tests {
253-
// If an env test fails then the mutex will be poisoned and the following error will be displayed.
254-
const LOCK_POISONED_MESSAGE: &str = "one of the other pipeline builder from env tests failed";
255-
256-
use crate::exporter::{
257-
default_endpoint, default_protocol, WithExportConfig, OTEL_EXPORTER_OTLP_ENDPOINT,
258-
OTEL_EXPORTER_OTLP_GRPC_ENDPOINT_DEFAULT, OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT,
259-
OTEL_EXPORTER_OTLP_PROTOCOL_GRPC, OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF,
260-
OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT,
261-
};
262-
use crate::{new_exporter, Compression, Protocol, OTEL_EXPORTER_OTLP_PROTOCOL};
263-
use std::str::FromStr;
264-
use std::sync::Mutex;
265-
266-
// Make sure env tests are not running concurrently
267-
static ENV_LOCK: Mutex<usize> = Mutex::new(0);
268-
269-
#[test]
270-
fn test_pipeline_builder_from_env_default_vars() {
271-
let _env_lock = ENV_LOCK.lock().expect(LOCK_POISONED_MESSAGE);
272-
let exporter_builder = new_exporter().tonic().with_env();
273-
assert_eq!(
274-
exporter_builder.exporter_config.protocol,
275-
default_protocol()
276-
);
277-
assert_eq!(
278-
exporter_builder.exporter_config.endpoint,
279-
default_endpoint(default_protocol())
280-
);
281-
assert_eq!(
282-
exporter_builder.exporter_config.timeout,
283-
std::time::Duration::from_secs(OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT)
284-
);
285-
}
286-
287-
#[test]
288-
fn test_pipeline_builder_from_env_endpoint() {
289-
let _env_lock = ENV_LOCK.lock().expect(LOCK_POISONED_MESSAGE);
290-
std::env::set_var(OTEL_EXPORTER_OTLP_ENDPOINT, "http://example.com");
291-
let exporter_builder = new_exporter().tonic().with_env();
292-
assert_eq!(
293-
exporter_builder.exporter_config.endpoint,
294-
"http://example.com"
295-
);
296-
std::env::remove_var(OTEL_EXPORTER_OTLP_ENDPOINT);
297-
assert!(std::env::var(OTEL_EXPORTER_OTLP_ENDPOINT).is_err());
298-
}
299-
300-
#[test]
301-
fn test_pipeline_builder_from_env_protocol_http_protobuf() {
302-
let _env_lock = ENV_LOCK.lock().expect(LOCK_POISONED_MESSAGE);
303-
std::env::set_var(
304-
OTEL_EXPORTER_OTLP_PROTOCOL,
305-
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF,
306-
);
307-
let exporter_builder = new_exporter().tonic().with_env();
308-
assert_eq!(
309-
exporter_builder.exporter_config.protocol,
310-
Protocol::HttpBinary
311-
);
312-
assert_eq!(
313-
exporter_builder.exporter_config.endpoint,
314-
OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT
315-
);
316-
317-
std::env::remove_var(OTEL_EXPORTER_OTLP_PROTOCOL);
318-
assert!(std::env::var(OTEL_EXPORTER_OTLP_PROTOCOL).is_err());
319-
}
320-
321-
#[test]
322-
fn test_pipeline_builder_from_env_protocol_grpc() {
323-
let _env_lock = ENV_LOCK.lock().expect(LOCK_POISONED_MESSAGE);
324-
std::env::set_var(
325-
OTEL_EXPORTER_OTLP_PROTOCOL,
326-
OTEL_EXPORTER_OTLP_PROTOCOL_GRPC,
327-
);
328-
let exporter_builder = new_exporter().tonic().with_env();
329-
assert_eq!(exporter_builder.exporter_config.protocol, Protocol::Grpc);
330-
assert_eq!(
331-
exporter_builder.exporter_config.endpoint,
332-
OTEL_EXPORTER_OTLP_GRPC_ENDPOINT_DEFAULT
333-
);
334-
335-
std::env::remove_var(OTEL_EXPORTER_OTLP_PROTOCOL);
336-
assert!(std::env::var(OTEL_EXPORTER_OTLP_PROTOCOL).is_err());
337-
}
338-
339-
#[test]
340-
fn test_pipeline_builder_from_env_bad_protocol() {
341-
let _env_lock = ENV_LOCK.lock().expect(LOCK_POISONED_MESSAGE);
342-
std::env::set_var(OTEL_EXPORTER_OTLP_PROTOCOL, "bad_protocol");
343-
let exporter_builder = new_exporter().tonic().with_env();
344-
assert_eq!(
345-
exporter_builder.exporter_config.protocol,
346-
default_protocol()
347-
);
348-
assert_eq!(
349-
exporter_builder.exporter_config.endpoint,
350-
default_endpoint(default_protocol())
351-
);
352-
353-
std::env::remove_var(OTEL_EXPORTER_OTLP_PROTOCOL);
354-
assert!(std::env::var(OTEL_EXPORTER_OTLP_PROTOCOL).is_err());
355-
}
356-
357-
#[test]
358-
fn test_pipeline_builder_from_env_timeout() {
359-
let _env_lock = ENV_LOCK.lock().expect(LOCK_POISONED_MESSAGE);
360-
std::env::set_var(OTEL_EXPORTER_OTLP_TIMEOUT, "60");
361-
let exporter_builder = new_exporter().tonic().with_env();
362-
assert_eq!(
363-
exporter_builder.exporter_config.timeout,
364-
std::time::Duration::from_secs(60)
365-
);
366-
367-
std::env::remove_var(OTEL_EXPORTER_OTLP_TIMEOUT);
368-
assert!(std::env::var(OTEL_EXPORTER_OTLP_TIMEOUT).is_err());
369-
}
370-
371-
#[test]
372-
fn test_pipeline_builder_from_env_bad_timeout() {
373-
let _env_lock = ENV_LOCK.lock().expect(LOCK_POISONED_MESSAGE);
374-
std::env::set_var(OTEL_EXPORTER_OTLP_TIMEOUT, "bad_timeout");
375-
376-
let exporter_builder = new_exporter().tonic().with_env();
377-
assert_eq!(
378-
exporter_builder.exporter_config.timeout,
379-
std::time::Duration::from_secs(OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT)
380-
);
381-
382-
std::env::remove_var(OTEL_EXPORTER_OTLP_TIMEOUT);
383-
assert!(std::env::var(OTEL_EXPORTER_OTLP_TIMEOUT).is_err());
384-
}
385-
386-
#[test]
387-
fn test_compression_parse() {
388-
assert_eq!(Compression::from_str("gzip").unwrap(), Compression::Gzip);
389-
Compression::from_str("bad_compression").expect_err("bad compression");
390-
}
391-
392-
#[test]
393-
fn test_compression_to_str() {
394-
assert_eq!(Compression::Gzip.to_string(), "gzip");
395-
}
396-
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,10 @@ impl Default for TonicExporterBuilder {
145145
};
146146

147147
TonicExporterBuilder {
148-
exporter_config: ExportConfig::default(),
148+
exporter_config: ExportConfig {
149+
protocol: crate::Protocol::Grpc,
150+
..Default::default()
151+
},
149152
tonic_config,
150153
channel: Option::default(),
151154
interceptor: Option::default(),

0 commit comments

Comments
 (0)