Skip to content

Commit cbcc038

Browse files
committed
chore(otlp): Make invalid proto combos unrepresentable
1 parent 5c8dbc8 commit cbcc038

File tree

9 files changed

+198
-167
lines changed

9 files changed

+198
-167
lines changed

opentelemetry-otlp/CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@
44

55
- Add partial success response handling for OTLP exporters (traces, metrics, logs) per OTLP spec. Exporters now log warnings when the server returns partial success responses with rejected items and error messages. [#865](https://github.com/open-telemetry/opentelemetry-rust/issues/865)
66
- Refactor `internal-logs` feature in `opentelemetry-otlp` to reduce unnecessary dependencies[3191](https://github.com/open-telemetry/opentelemetry-rust/pull/3192)
7+
- **Breaking** Replace ambiguous `Protocol` enum with type-safe HTTP encoding API [#3082](https://github.com/open-telemetry/opentelemetry-rust/issues/3082)
8+
- Removed `protocol` field from `ExportConfig` and `with_protocol()` method from `WithExportConfig` trait
9+
- Added new `HttpEncoding` enum with `Protobuf` and `Json` variants for HTTP transport
10+
- Added HTTP-specific methods: `with_http_encoding(encoding)`, `with_json_encoding()`, `with_protobuf_encoding()`
11+
- This change makes invalid protocol/transport combinations impossible to express to simplify the user experience
12+
- Migration example:
13+
```rust
14+
// Before:
15+
SpanExporter::builder()
16+
.with_http()
17+
.with_protocol(Protocol::HttpBinary)
18+
.build()
19+
20+
// After:
21+
SpanExporter::builder()
22+
.with_http()
23+
.with_protobuf_encoding()
24+
.build()
25+
```
726

827
## 0.31.0
928

opentelemetry-otlp/examples/basic-otlp-http/src/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use opentelemetry::{
44
InstrumentationScope, KeyValue,
55
};
66
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
7-
use opentelemetry_otlp::WithExportConfig;
8-
use opentelemetry_otlp::{LogExporter, MetricExporter, Protocol, SpanExporter};
7+
use opentelemetry_otlp::WithHttpConfig;
8+
use opentelemetry_otlp::{LogExporter, MetricExporter, SpanExporter};
99
use opentelemetry_sdk::Resource;
1010
use opentelemetry_sdk::{
1111
logs::SdkLoggerProvider, metrics::SdkMeterProvider, trace::SdkTracerProvider,
@@ -29,7 +29,7 @@ fn get_resource() -> Resource {
2929
fn init_logs() -> SdkLoggerProvider {
3030
let exporter = LogExporter::builder()
3131
.with_http()
32-
.with_protocol(Protocol::HttpBinary)
32+
.with_protobuf_encoding() // can be changed to .with_json_encoding() to export in JSON format
3333
.build()
3434
.expect("Failed to create log exporter");
3535

@@ -42,7 +42,7 @@ fn init_logs() -> SdkLoggerProvider {
4242
fn init_traces() -> SdkTracerProvider {
4343
let exporter = SpanExporter::builder()
4444
.with_http()
45-
.with_protocol(Protocol::HttpBinary) //can be changed to `Protocol::HttpJson` to export in JSON format
45+
.with_protobuf_encoding() // can be changed to .with_json_encoding() to export in JSON format
4646
.build()
4747
.expect("Failed to create trace exporter");
4848

@@ -55,7 +55,7 @@ fn init_traces() -> SdkTracerProvider {
5555
fn init_metrics() -> SdkMeterProvider {
5656
let exporter = MetricExporter::builder()
5757
.with_http()
58-
.with_protocol(Protocol::HttpBinary) //can be changed to `Protocol::HttpJson` to export in JSON format
58+
.with_protobuf_encoding() // can be changed to .with_json_encoding() to export in JSON format
5959
.build()
6060
.expect("Failed to create metric exporter");
6161

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

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use super::OtlpHttpClient;
2-
use crate::Protocol;
1+
use super::{HttpEncoding, OtlpHttpClient};
32
use opentelemetry::{otel_debug, otel_warn};
43
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
54
use opentelemetry_sdk::logs::{LogBatch, LogExporter};
@@ -16,7 +15,7 @@ impl LogExporter for OtlpHttpClient {
1615
)
1716
.await?;
1817

19-
handle_partial_success(&response_body, self.protocol);
18+
handle_partial_success(&response_body, self.encoding);
2019
Ok(())
2120
}
2221

@@ -39,19 +38,27 @@ impl LogExporter for OtlpHttpClient {
3938

4039
/// Handles partial success returned by OTLP endpoints. We log the rejected log records,
4140
/// as well as the error message returned.
42-
fn handle_partial_success(response_body: &[u8], protocol: Protocol) {
41+
fn handle_partial_success(response_body: &[u8], encoding: HttpEncoding) {
4342
use opentelemetry_proto::tonic::collector::logs::v1::ExportLogsServiceResponse;
4443

45-
let response: ExportLogsServiceResponse = match protocol {
44+
let response: ExportLogsServiceResponse = match encoding {
4645
#[cfg(feature = "http-json")]
47-
Protocol::HttpJson => match serde_json::from_slice(response_body) {
46+
HttpEncoding::Json => match serde_json::from_slice(response_body) {
4847
Ok(r) => r,
4948
Err(e) => {
5049
otel_debug!(name: "HttpLogsClient.ResponseParseError", error = e.to_string());
5150
return;
5251
}
5352
},
54-
_ => match Message::decode(response_body) {
53+
HttpEncoding::Protobuf => match Message::decode(response_body) {
54+
Ok(r) => r,
55+
Err(e) => {
56+
otel_debug!(name: "HttpLogsClient.ResponseParseError", error = e.to_string());
57+
return;
58+
}
59+
},
60+
#[cfg(not(feature = "http-json"))]
61+
HttpEncoding::Json => match Message::decode(response_body) {
5562
Ok(r) => r,
5663
Err(e) => {
5764
otel_debug!(name: "HttpLogsClient.ResponseParseError", error = e.to_string());
@@ -81,15 +88,15 @@ mod tests {
8188
let invalid = vec![0xFF, 0xFF, 0xFF, 0xFF];
8289

8390
// Should not panic - logs debug and returns early
84-
handle_partial_success(&invalid, Protocol::HttpBinary);
91+
handle_partial_success(&invalid, HttpEncoding::Protobuf);
8592
}
8693

8794
#[test]
8895
fn test_handle_empty_response() {
8996
let empty = vec![];
9097

9198
// Should not panic
92-
handle_partial_success(&empty, Protocol::HttpBinary);
99+
handle_partial_success(&empty, HttpEncoding::Protobuf);
93100
}
94101

95102
#[cfg(feature = "http-json")]
@@ -98,6 +105,6 @@ mod tests {
98105
let invalid_json = b"{not valid json}";
99106

100107
// Should not panic - logs debug and returns
101-
handle_partial_success(invalid_json, Protocol::HttpJson);
108+
handle_partial_success(invalid_json, HttpEncoding::Json);
102109
}
103110
}

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

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use crate::metric::MetricsClient;
2-
use crate::Protocol;
32
use opentelemetry::{otel_debug, otel_warn};
43
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
54
use opentelemetry_sdk::metrics::data::ResourceMetrics;
65
use prost::Message;
76

8-
use super::OtlpHttpClient;
7+
use super::{HttpEncoding, OtlpHttpClient};
98

109
impl MetricsClient for OtlpHttpClient {
1110
async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult {
@@ -19,7 +18,7 @@ impl MetricsClient for OtlpHttpClient {
1918
.export_http_with_retry(metrics, build_body_wrapper, "HttpMetricsClient.Export")
2019
.await?;
2120

22-
handle_partial_success(&response_body, self.protocol);
21+
handle_partial_success(&response_body, self.encoding);
2322
Ok(())
2423
}
2524

@@ -35,19 +34,27 @@ impl MetricsClient for OtlpHttpClient {
3534

3635
/// Handles partial success returned by OTLP endpoints. We log the rejected data points,
3736
/// as well as the error message returned.
38-
fn handle_partial_success(response_body: &[u8], protocol: Protocol) {
37+
fn handle_partial_success(response_body: &[u8], encoding: HttpEncoding) {
3938
use opentelemetry_proto::tonic::collector::metrics::v1::ExportMetricsServiceResponse;
4039

41-
let response: ExportMetricsServiceResponse = match protocol {
40+
let response: ExportMetricsServiceResponse = match encoding {
4241
#[cfg(feature = "http-json")]
43-
Protocol::HttpJson => match serde_json::from_slice(response_body) {
42+
HttpEncoding::Json => match serde_json::from_slice(response_body) {
4443
Ok(r) => r,
4544
Err(e) => {
4645
otel_debug!(name: "HttpMetricsClient.ResponseParseError", error = e.to_string());
4746
return;
4847
}
4948
},
50-
_ => match Message::decode(response_body) {
49+
HttpEncoding::Protobuf => match Message::decode(response_body) {
50+
Ok(r) => r,
51+
Err(e) => {
52+
otel_debug!(name: "HttpMetricsClient.ResponseParseError", error = e.to_string());
53+
return;
54+
}
55+
},
56+
#[cfg(not(feature = "http-json"))]
57+
HttpEncoding::Json => match Message::decode(response_body) {
5158
Ok(r) => r,
5259
Err(e) => {
5360
otel_debug!(name: "HttpMetricsClient.ResponseParseError", error = e.to_string());
@@ -77,15 +84,15 @@ mod tests {
7784
let invalid = vec![0xFF, 0xFF, 0xFF, 0xFF];
7885

7986
// Should not panic - logs debug and returns early
80-
handle_partial_success(&invalid, Protocol::HttpBinary);
87+
handle_partial_success(&invalid, HttpEncoding::Protobuf);
8188
}
8289

8390
#[test]
8491
fn test_handle_empty_response() {
8592
let empty = vec![];
8693

8794
// Should not panic
88-
handle_partial_success(&empty, Protocol::HttpBinary);
95+
handle_partial_success(&empty, HttpEncoding::Protobuf);
8996
}
9097

9198
#[cfg(feature = "http-json")]
@@ -94,6 +101,6 @@ mod tests {
94101
let invalid_json = b"{not valid json}";
95102

96103
// Should not panic - logs debug and returns
97-
handle_partial_success(invalid_json, Protocol::HttpJson);
104+
handle_partial_success(invalid_json, HttpEncoding::Json);
98105
}
99106
}

0 commit comments

Comments
 (0)