Skip to content

Commit d112a1b

Browse files
prontclaude
andauthored
feat(opentelemetry source): Support per-signal OTLP decoding config (#24822)
* feat(opentelemetry source): Support per-signal OTLP decoding configuration (#24455) Allow independent configuration of OTLP decoding for logs, metrics, and traces. Maintains backward compatibility with boolean configuration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * changelog * generate component docs * regen docs * fixes * linting * consolidate tests * add usage example * fix trace output * fix test too * make generate-component-docs * address review point * attempt to fix blocking workflow * attempt optimal fix * revert changelog workflow changes --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2958f98 commit d112a1b

File tree

5 files changed

+472
-24
lines changed

5 files changed

+472
-24
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The `opentelemetry` source now supports independent configuration of OTLP decoding for logs, metrics, and traces. This allows more granular
2+
control over which signal types are decoded, while maintaining backward compatibility with the existing boolean configuration.
3+
4+
## Simple boolean form (applies to all signals)
5+
6+
```yaml
7+
use_otlp_decoding: true # All signals preserve OTLP format
8+
# or
9+
use_otlp_decoding: false # All signals use Vector native format (default)
10+
```
11+
12+
## Per-signal configuration
13+
14+
```yaml
15+
use_otlp_decoding:
16+
logs: false # Convert to Vector native format
17+
metrics: false # Convert to Vector native format
18+
traces: true # Preserve OTLP format
19+
```
20+
21+
authors: pront

src/sources/opentelemetry/config.rs

Lines changed: 109 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,65 @@ pub const LOGS: &str = "logs";
4747
pub const METRICS: &str = "metrics";
4848
pub const TRACES: &str = "traces";
4949

50+
/// Configuration for OTLP decoding behavior.
51+
#[configurable_component]
52+
#[derive(Clone, Debug, Default, PartialEq, Eq)]
53+
#[serde(deny_unknown_fields)]
54+
pub struct OtlpDecodingConfig {
55+
/// Whether to use OTLP decoding for logs.
56+
///
57+
/// When `true`, logs preserve their OTLP format.
58+
/// When `false` (default), logs are converted to Vector's native format.
59+
#[serde(default)]
60+
pub logs: bool,
61+
62+
/// Whether to use OTLP decoding for metrics.
63+
///
64+
/// When `true`, metrics preserve their OTLP format but are processed as logs.
65+
/// When `false` (default), metrics are converted to Vector's native metric format.
66+
#[serde(default)]
67+
pub metrics: bool,
68+
69+
/// Whether to use OTLP decoding for traces.
70+
///
71+
/// When `true`, traces preserve their OTLP format.
72+
/// When `false` (default), traces are converted to Vector's native format.
73+
#[serde(default)]
74+
pub traces: bool,
75+
}
76+
77+
impl From<bool> for OtlpDecodingConfig {
78+
/// Converts a boolean value to an OtlpDecodingConfig.
79+
///
80+
/// This provides backward compatibility with the previous boolean configuration.
81+
/// - `true` enables OTLP decoding for all signals
82+
/// - `false` disables OTLP decoding for all signals (uses Vector native format)
83+
fn from(value: bool) -> Self {
84+
Self {
85+
logs: value,
86+
metrics: value,
87+
traces: value,
88+
}
89+
}
90+
}
91+
92+
impl OtlpDecodingConfig {
93+
/// Returns true if any signal is configured to use OTLP decoding.
94+
pub const fn any_enabled(&self) -> bool {
95+
self.logs || self.metrics || self.traces
96+
}
97+
98+
/// Returns true if all signals are configured to use OTLP decoding.
99+
pub const fn all_enabled(&self) -> bool {
100+
self.logs && self.metrics && self.traces
101+
}
102+
103+
/// Returns true if signals have mixed configuration (some enabled, some disabled).
104+
pub const fn is_mixed(&self) -> bool {
105+
self.any_enabled() && !self.all_enabled()
106+
}
107+
}
108+
50109
/// Configuration for the `opentelemetry` source.
51110
#[configurable_component(source("opentelemetry", "Receive OTLP data through gRPC or HTTP."))]
52111
#[derive(Clone, Debug)]
@@ -67,14 +126,36 @@ pub struct OpentelemetryConfig {
67126
#[serde(default)]
68127
pub log_namespace: Option<bool>,
69128

70-
/// Setting this field will override the legacy mapping of OTEL protos to Vector events and use the proto directly.
129+
/// Configuration for OTLP decoding behavior.
71130
///
72-
/// One major caveat here is that the incoming metrics will be parsed as logs but they will preserve the OTLP format.
73-
/// This means that components that work on metrics, will not be compatible with this output.
74-
/// However, these events can be forwarded directly to a downstream OTEL collector.
75-
#[configurable(derived)]
76-
#[serde(default)]
77-
pub use_otlp_decoding: bool,
131+
/// This configuration controls how OpenTelemetry Protocol (OTLP) data is decoded for each
132+
/// signal type (logs, metrics, traces). When a signal is configured to use OTLP decoding, the raw OTLP format is
133+
/// preserved, allowing the data to be forwarded to downstream OTLP collectors without transformation.
134+
/// Otherwise, the signal is converted to Vector's native event format.
135+
///
136+
/// Simple boolean form:
137+
///
138+
/// ```yaml
139+
/// use_otlp_decoding: true # All signals preserve OTLP format
140+
/// # or
141+
/// use_otlp_decoding: false # All signals use Vector native format (default)
142+
/// ```
143+
///
144+
/// Per-signal configuration:
145+
///
146+
/// ```yaml
147+
/// use_otlp_decoding:
148+
/// logs: false # Convert to Vector native format
149+
/// metrics: false # Convert to Vector native format
150+
/// traces: true # Preserve OTLP format
151+
/// ```
152+
///
153+
/// **Note:** When OTLP decoding is enabled for metrics:
154+
/// - Metrics are parsed as logs while preserving the OTLP format
155+
/// - Vector's metric transforms will NOT be compatible with this output
156+
/// - The events can be forwarded directly (passthrough) to a downstream OTLP collector
157+
#[serde(default, deserialize_with = "bool_or_struct")]
158+
pub use_otlp_decoding: OtlpDecodingConfig,
78159
}
79160

80161
/// Configuration for the `opentelemetry` gRPC server.
@@ -152,18 +233,24 @@ impl GenerateConfig for OpentelemetryConfig {
152233
http: example_http_config(),
153234
acknowledgements: Default::default(),
154235
log_namespace: None,
155-
use_otlp_decoding: false,
236+
use_otlp_decoding: OtlpDecodingConfig::default(),
156237
})
157238
.unwrap()
158239
}
159240
}
160241

161242
impl OpentelemetryConfig {
162-
fn get_signal_deserializer(
243+
pub(crate) fn get_signal_deserializer(
163244
&self,
164245
signal_type: OtlpSignalType,
165246
) -> vector_common::Result<Option<OtlpDeserializer>> {
166-
if self.use_otlp_decoding {
247+
let should_use_otlp = match signal_type {
248+
OtlpSignalType::Logs => self.use_otlp_decoding.logs,
249+
OtlpSignalType::Metrics => self.use_otlp_decoding.metrics,
250+
OtlpSignalType::Traces => self.use_otlp_decoding.traces,
251+
};
252+
253+
if should_use_otlp {
167254
Ok(Some(OtlpDeserializer::new_with_signals(IndexSet::from([
168255
signal_type,
169256
]))))
@@ -183,6 +270,16 @@ impl SourceConfig for OpentelemetryConfig {
183270

184271
let grpc_tls_settings = MaybeTlsSettings::from_config(self.grpc.tls.as_ref(), true)?;
185272

273+
// Log info message when using mixed OTLP decoding formats
274+
if self.use_otlp_decoding.is_mixed() {
275+
info!(
276+
message = "Signals with OTLP decoding enabled will preserve raw format; others will use Vector native format.",
277+
logs_otlp = self.use_otlp_decoding.logs,
278+
metrics_otlp = self.use_otlp_decoding.metrics,
279+
traces_otlp = self.use_otlp_decoding.traces,
280+
);
281+
}
282+
186283
let logs_deserializer = self.get_signal_deserializer(OtlpSignalType::Logs)?;
187284
let metrics_deserializer = self.get_signal_deserializer(OtlpSignalType::Metrics)?;
188285
let traces_deserializer = self.get_signal_deserializer(OtlpSignalType::Traces)?;
@@ -352,13 +449,13 @@ impl SourceConfig for OpentelemetryConfig {
352449
}
353450
};
354451

355-
let logs_output = if self.use_otlp_decoding {
452+
let logs_output = if self.use_otlp_decoding.logs {
356453
SourceOutput::new_maybe_logs(DataType::Log, Definition::any()).with_port(LOGS)
357454
} else {
358455
SourceOutput::new_maybe_logs(DataType::Log, schema_definition).with_port(LOGS)
359456
};
360457

361-
let metrics_output = if self.use_otlp_decoding {
458+
let metrics_output = if self.use_otlp_decoding.metrics {
362459
SourceOutput::new_maybe_logs(DataType::Log, Definition::any()).with_port(METRICS)
363460
} else {
364461
SourceOutput::new_metrics().with_port(METRICS)

src/sources/opentelemetry/integration_tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async fn receive_logs_legacy_namespace() {
6262
},
6363
acknowledgements: Default::default(),
6464
log_namespace: Default::default(),
65-
use_otlp_decoding: false,
65+
use_otlp_decoding: false.into(),
6666
};
6767

6868
let (sender, logs_output, _) = new_source(EventStatus::Delivered, LOGS.to_string());
@@ -161,7 +161,7 @@ async fn receive_trace() {
161161
},
162162
acknowledgements: Default::default(),
163163
log_namespace: Default::default(),
164-
use_otlp_decoding: false,
164+
use_otlp_decoding: false.into(),
165165
};
166166

167167
let (sender, trace_output, _) = new_source(EventStatus::Delivered, TRACES.to_string());
@@ -266,7 +266,7 @@ async fn receive_metric() {
266266
},
267267
acknowledgements: Default::default(),
268268
log_namespace: Default::default(),
269-
use_otlp_decoding: false,
269+
use_otlp_decoding: false.into(),
270270
};
271271

272272
let (sender, metrics_output, _) = new_source(EventStatus::Delivered, METRICS.to_string());

0 commit comments

Comments
 (0)