Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion opentelemetry-otlp/src/exporter/http/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::Protocol;
use opentelemetry::{otel_debug, otel_warn};
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
use opentelemetry_sdk::logs::{LogBatch, LogExporter};
#[cfg(feature = "http-proto")]
use prost::Message;
use std::time;

Expand Down Expand Up @@ -51,13 +52,18 @@ fn handle_partial_success(response_body: &[u8], protocol: Protocol) {
return;
}
},
_ => match Message::decode(response_body) {
#[cfg(feature = "http-proto")]
Protocol::HttpBinary => match Message::decode(response_body) {
Ok(r) => r,
Err(e) => {
otel_debug!(name: "HttpLogsClient.ResponseParseError", error = e.to_string());
return;
}
},
#[cfg(feature = "grpc-tonic")]
Protocol::Grpc => {
unreachable!("HTTP client should not receive Grpc protocol")
}
};

if let Some(partial_success) = response.partial_success {
Expand Down
8 changes: 7 additions & 1 deletion opentelemetry-otlp/src/exporter/http/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::Protocol;
use opentelemetry::{otel_debug, otel_warn};
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
use opentelemetry_sdk::metrics::data::ResourceMetrics;
#[cfg(feature = "http-proto")]
use prost::Message;

use super::OtlpHttpClient;
Expand Down Expand Up @@ -47,13 +48,18 @@ fn handle_partial_success(response_body: &[u8], protocol: Protocol) {
return;
}
},
_ => match Message::decode(response_body) {
#[cfg(feature = "http-proto")]
Protocol::HttpBinary => match Message::decode(response_body) {
Ok(r) => r,
Err(e) => {
otel_debug!(name: "HttpMetricsClient.ResponseParseError", error = e.to_string());
return;
}
},
#[cfg(feature = "grpc-tonic")]
Protocol::Grpc => {
unreachable!("HTTP client should not receive Grpc protocol")
}
};

if let Some(partial_success) = response.partial_success {
Expand Down
26 changes: 21 additions & 5 deletions opentelemetry-otlp/src/exporter/http/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{
default_headers, default_protocol, parse_header_string, resolve_timeout, ExporterBuildError,
default_headers, parse_header_string, resolve_timeout, ExporterBuildError,
OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT,
};
use crate::{ExportConfig, Protocol, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS};
Expand All @@ -15,6 +15,7 @@ use opentelemetry_proto::transform::trace::tonic::group_spans_by_resource_and_sc
use opentelemetry_sdk::logs::LogBatch;
#[cfg(feature = "trace")]
use opentelemetry_sdk::trace::SpanData;
#[cfg(feature = "http-proto")]
use prost::Message;
use std::collections::HashMap;
use std::env;
Expand Down Expand Up @@ -154,7 +155,7 @@ impl Default for HttpExporterBuilder {
fn default() -> Self {
HttpExporterBuilder {
exporter_config: ExportConfig {
protocol: default_protocol(),
protocol: Protocol::default(),
..ExportConfig::default()
},
http_config: HttpConfig {
Expand Down Expand Up @@ -595,7 +596,12 @@ impl OtlpHttpClient {
Ok(json) => (json.into_bytes(), "application/json"),
Err(e) => return Err(e.to_string()),
},
_ => (req.encode_to_vec(), "application/x-protobuf"),
#[cfg(feature = "http-proto")]
Protocol::HttpBinary => (req.encode_to_vec(), "application/x-protobuf"),
#[cfg(feature = "grpc-tonic")]
Protocol::Grpc => {
unreachable!("HTTP client should not receive Grpc protocol")
}
};

let (processed_body, content_encoding) = self.process_body(body)?;
Expand All @@ -617,7 +623,12 @@ impl OtlpHttpClient {
Ok(json) => (json.into_bytes(), "application/json"),
Err(e) => return Err(e.to_string()),
},
_ => (req.encode_to_vec(), "application/x-protobuf"),
#[cfg(feature = "http-proto")]
Protocol::HttpBinary => (req.encode_to_vec(), "application/x-protobuf"),
#[cfg(feature = "grpc-tonic")]
Protocol::Grpc => {
unreachable!("HTTP client should not receive Grpc protocol")
}
};

let (processed_body, content_encoding) = self.process_body(body)?;
Expand All @@ -642,7 +653,12 @@ impl OtlpHttpClient {
return None;
}
},
_ => (req.encode_to_vec(), "application/x-protobuf"),
#[cfg(feature = "http-proto")]
Protocol::HttpBinary => (req.encode_to_vec(), "application/x-protobuf"),
#[cfg(feature = "grpc-tonic")]
Protocol::Grpc => {
unreachable!("HTTP client should not receive Grpc protocol")
}
};

match self.process_body(body) {
Expand Down
8 changes: 7 additions & 1 deletion opentelemetry-otlp/src/exporter/http/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use opentelemetry_sdk::{
error::{OTelSdkError, OTelSdkResult},
trace::{SpanData, SpanExporter},
};
#[cfg(feature = "http-proto")]
use prost::Message;

impl SpanExporter for OtlpHttpClient {
Expand Down Expand Up @@ -52,13 +53,18 @@ fn handle_partial_success(response_body: &[u8], protocol: Protocol) {
return;
}
},
_ => match Message::decode(response_body) {
#[cfg(feature = "http-proto")]
Protocol::HttpBinary => match Message::decode(response_body) {
Ok(r) => r,
Err(e) => {
otel_debug!(name: "HttpTraceClient.ResponseParseError", error = e.to_string());
return;
}
},
#[cfg(feature = "grpc-tonic")]
Protocol::Grpc => {
unreachable!("HTTP client should not receive Grpc protocol")
}
};

if let Some(partial_success) = response.partial_success {
Expand Down
125 changes: 89 additions & 36 deletions opentelemetry-otlp/src/exporter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,12 @@ pub const OTEL_EXPORTER_OTLP_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL";
/// Compression algorithm to use, defaults to none.
pub const OTEL_EXPORTER_OTLP_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_COMPRESSION";

#[cfg(feature = "http-json")]
/// Default protocol, using http-json.
pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON;
#[cfg(all(feature = "http-proto", not(feature = "http-json")))]
/// Default protocol, using http-proto.
pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF;
#[cfg(all(
feature = "grpc-tonic",
not(any(feature = "http-proto", feature = "http-json"))
))]
/// Default protocol, using grpc
pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_GRPC;

#[cfg(not(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json")))]
/// Default protocol if no features are enabled.
pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = "";

const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF: &str = "http/protobuf";
const OTEL_EXPORTER_OTLP_PROTOCOL_GRPC: &str = "grpc";
const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON: &str = "http/json";
/// Protocol value for HTTP with protobuf encoding
pub const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF: &str = "http/protobuf";
/// Protocol value for gRPC
pub const OTEL_EXPORTER_OTLP_PROTOCOL_GRPC: &str = "grpc";
/// Protocol value for HTTP with JSON encoding
pub const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON: &str = "http/json";

/// Max waiting time for the backend to process each signal batch, defaults to 10 seconds.
pub const OTEL_EXPORTER_OTLP_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_TIMEOUT";
Expand Down Expand Up @@ -84,9 +70,10 @@ pub struct ExportConfig {
pub timeout: Option<Duration>,
}

#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
impl Default for ExportConfig {
fn default() -> Self {
let protocol = default_protocol();
let protocol = Protocol::default();

Self {
endpoint: None,
Expand Down Expand Up @@ -190,13 +177,33 @@ fn resolve_compression_from_env(
}
}

/// default protocol based on enabled features
fn default_protocol() -> Protocol {
match OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT {
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF => Protocol::HttpBinary,
OTEL_EXPORTER_OTLP_PROTOCOL_GRPC => Protocol::Grpc,
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON => Protocol::HttpJson,
_ => Protocol::HttpBinary,
/// Returns the default protocol based on environment variable or enabled features.
///
/// Priority order (first available wins):
/// 1. OTEL_EXPORTER_OTLP_PROTOCOL environment variable (if set and feature is enabled)
/// 2. http-json (if enabled)
/// 3. http-proto (if enabled)
/// 4. grpc-tonic (if enabled)
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
impl Default for Protocol {
fn default() -> Self {
// Check environment variable first
if let Some(protocol) = Protocol::from_env() {
return protocol;
}

// Fall back to feature-based defaults
#[cfg(feature = "http-json")]
return Protocol::HttpJson;

#[cfg(all(feature = "http-proto", not(feature = "http-json")))]
return Protocol::HttpBinary;

#[cfg(all(
feature = "grpc-tonic",
not(any(feature = "http-proto", feature = "http-json"))
))]
return Protocol::Grpc;
}
}

Expand Down Expand Up @@ -460,21 +467,15 @@ mod tests {
not(any(feature = "grpc-tonic", feature = "http-proto"))
))]
{
assert_eq!(
crate::exporter::default_protocol(),
crate::Protocol::HttpJson
);
assert_eq!(crate::Protocol::default(), crate::Protocol::HttpJson);
}

#[cfg(all(
feature = "http-proto",
not(any(feature = "grpc-tonic", feature = "http-json"))
))]
{
assert_eq!(
crate::exporter::default_protocol(),
crate::Protocol::HttpBinary
);
assert_eq!(crate::Protocol::default(), crate::Protocol::HttpBinary);
}

#[cfg(all(
Expand All @@ -486,6 +487,58 @@ mod tests {
}
}

#[test]
fn test_protocol_from_env() {
use crate::{Protocol, OTEL_EXPORTER_OTLP_PROTOCOL};

// Test with no env var set - should return None
temp_env::with_var_unset(OTEL_EXPORTER_OTLP_PROTOCOL, || {
assert_eq!(Protocol::from_env(), None);
});

// Test with grpc protocol
#[cfg(feature = "grpc-tonic")]
run_env_test(vec![(OTEL_EXPORTER_OTLP_PROTOCOL, "grpc")], || {
assert_eq!(Protocol::from_env(), Some(Protocol::Grpc));
});

// Test with http/protobuf protocol
#[cfg(feature = "http-proto")]
run_env_test(vec![(OTEL_EXPORTER_OTLP_PROTOCOL, "http/protobuf")], || {
assert_eq!(Protocol::from_env(), Some(Protocol::HttpBinary));
});

// Test with http/json protocol
#[cfg(feature = "http-json")]
run_env_test(vec![(OTEL_EXPORTER_OTLP_PROTOCOL, "http/json")], || {
assert_eq!(Protocol::from_env(), Some(Protocol::HttpJson));
});

// Test with invalid protocol - should return None
run_env_test(vec![(OTEL_EXPORTER_OTLP_PROTOCOL, "invalid")], || {
assert_eq!(Protocol::from_env(), None);
});
}

#[test]
fn test_default_protocol_respects_env() {
// Test that env var takes precedence over feature-based defaults
#[cfg(all(feature = "http-json", feature = "http-proto"))]
run_env_test(
vec![(crate::OTEL_EXPORTER_OTLP_PROTOCOL, "http/protobuf")],
|| {
// Even though http-json would be the default, env var should override
assert_eq!(crate::Protocol::default(), crate::Protocol::HttpBinary);
},
);

#[cfg(all(feature = "grpc-tonic", feature = "http-json"))]
run_env_test(vec![(crate::OTEL_EXPORTER_OTLP_PROTOCOL, "grpc")], || {
// Even though http-json would be the default, env var should override
assert_eq!(crate::Protocol::default(), crate::Protocol::Grpc);
});
}

#[test]
fn test_url_decode() {
let test_cases = vec![
Expand Down
Loading