1use std::net::SocketAddr;
2
3use crate::{
4 config::{
5 DataType, GenerateConfig, Resource, SourceAcknowledgementsConfig, SourceConfig,
6 SourceContext, SourceOutput,
7 },
8 http::KeepaliveConfig,
9 serde::bool_or_struct,
10 sources::{
11 Source,
12 http_server::{build_param_matcher, remove_duplicates},
13 opentelemetry::{
14 grpc::Service,
15 http::{build_warp_filter, run_http_server},
16 },
17 util::{
18 decompression::max_decompressed_size_bytes,
19 grpc::{GrpcKeepaliveConfig, run_grpc_server_with_routes},
20 },
21 },
22};
23use futures::FutureExt;
24use futures_util::{TryFutureExt, future::join};
25use tonic::transport::server::RoutesBuilder;
26use vector_config::indexmap::IndexSet;
27use vector_lib::{
28 codecs::decoding::{OtlpDeserializer, OtlpSignalType},
29 config::{LegacyKey, LogNamespace, log_schema},
30 configurable::configurable_component,
31 internal_event::{BytesReceived, EventsReceived, Protocol},
32 lookup::{OwnedTargetPath, owned_value_path},
33 opentelemetry::{
34 logs::{
35 ATTRIBUTES_KEY, DROPPED_ATTRIBUTES_COUNT_KEY, FLAGS_KEY, OBSERVED_TIMESTAMP_KEY,
36 RESOURCE_KEY, SEVERITY_NUMBER_KEY, SEVERITY_TEXT_KEY, SPAN_ID_KEY, TRACE_ID_KEY,
37 },
38 proto::collector::{
39 logs::v1::logs_service_server::LogsServiceServer,
40 metrics::v1::metrics_service_server::MetricsServiceServer,
41 trace::v1::trace_service_server::TraceServiceServer,
42 },
43 },
44 schema::Definition,
45 tls::{MaybeTlsSettings, TlsEnableableConfig},
46};
47use vrl::value::{Kind, kind::Collection};
48
49pub const LOGS: &str = "logs";
50pub const METRICS: &str = "metrics";
51pub const TRACES: &str = "traces";
52
53#[configurable_component]
55#[derive(Clone, Debug, Default, PartialEq, Eq)]
56#[serde(deny_unknown_fields)]
57pub struct OtlpDecodingConfig {
58 #[serde(default)]
63 pub logs: bool,
64
65 #[serde(default)]
70 pub metrics: bool,
71
72 #[serde(default)]
77 pub traces: bool,
78}
79
80impl From<bool> for OtlpDecodingConfig {
81 fn from(value: bool) -> Self {
87 Self {
88 logs: value,
89 metrics: value,
90 traces: value,
91 }
92 }
93}
94
95impl OtlpDecodingConfig {
96 pub const fn any_enabled(&self) -> bool {
98 self.logs || self.metrics || self.traces
99 }
100
101 pub const fn all_enabled(&self) -> bool {
103 self.logs && self.metrics && self.traces
104 }
105
106 pub const fn is_mixed(&self) -> bool {
108 self.any_enabled() && !self.all_enabled()
109 }
110}
111
112#[configurable_component(source("opentelemetry", "Receive OTLP data through gRPC or HTTP."))]
114#[derive(Clone, Debug)]
115#[serde(deny_unknown_fields)]
116pub struct OpentelemetryConfig {
117 #[configurable(derived)]
118 pub grpc: GrpcConfig,
119
120 #[configurable(derived)]
121 pub http: HttpConfig,
122
123 #[configurable(derived)]
124 #[serde(default, deserialize_with = "bool_or_struct")]
125 pub acknowledgements: SourceAcknowledgementsConfig,
126
127 #[configurable(metadata(docs::hidden))]
129 #[serde(default)]
130 pub log_namespace: Option<bool>,
131
132 #[serde(default, deserialize_with = "bool_or_struct")]
161 pub use_otlp_decoding: OtlpDecodingConfig,
162}
163
164#[configurable_component]
166#[configurable(metadata(docs::examples = "example_grpc_config()"))]
167#[derive(Clone, Debug)]
168#[serde(deny_unknown_fields)]
169pub struct GrpcConfig {
170 #[configurable(metadata(docs::examples = "0.0.0.0:4317", docs::examples = "localhost:4317"))]
174 pub address: SocketAddr,
175
176 #[configurable(derived)]
177 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub tls: Option<TlsEnableableConfig>,
179
180 #[configurable(derived)]
181 #[serde(default)]
182 pub keepalive: GrpcKeepaliveConfig,
183}
184
185fn example_grpc_config() -> GrpcConfig {
186 GrpcConfig {
187 address: "0.0.0.0:4317".parse().unwrap(),
188 tls: None,
189 keepalive: GrpcKeepaliveConfig::default(),
190 }
191}
192
193#[configurable_component]
195#[configurable(metadata(docs::examples = "example_http_config()"))]
196#[derive(Clone, Debug)]
197#[serde(deny_unknown_fields)]
198pub struct HttpConfig {
199 #[configurable(metadata(docs::examples = "0.0.0.0:4318", docs::examples = "localhost:4318"))]
203 pub address: SocketAddr,
204
205 #[configurable(derived)]
206 #[serde(default, skip_serializing_if = "Option::is_none")]
207 pub tls: Option<TlsEnableableConfig>,
208
209 #[configurable(derived)]
210 #[serde(default)]
211 pub keepalive: KeepaliveConfig,
212
213 #[serde(default)]
222 #[configurable(metadata(docs::examples = "User-Agent"))]
223 #[configurable(metadata(docs::examples = "X-My-Custom-Header"))]
224 #[configurable(metadata(docs::examples = "X-*"))]
225 #[configurable(metadata(docs::examples = "*"))]
226 pub headers: Vec<String>,
227}
228
229fn example_http_config() -> HttpConfig {
230 HttpConfig {
231 address: "0.0.0.0:4318".parse().unwrap(),
232 tls: None,
233 keepalive: KeepaliveConfig::default(),
234 headers: vec![],
235 }
236}
237
238impl GenerateConfig for OpentelemetryConfig {
239 fn generate_config() -> toml::Value {
240 toml::Value::try_from(Self {
241 grpc: example_grpc_config(),
242 http: example_http_config(),
243 acknowledgements: Default::default(),
244 log_namespace: None,
245 use_otlp_decoding: OtlpDecodingConfig::default(),
246 })
247 .unwrap()
248 }
249}
250
251impl OpentelemetryConfig {
252 pub(crate) fn get_signal_deserializer(
253 &self,
254 signal_type: OtlpSignalType,
255 ) -> vector_common::Result<Option<OtlpDeserializer>> {
256 let should_use_otlp = match signal_type {
257 OtlpSignalType::Logs => self.use_otlp_decoding.logs,
258 OtlpSignalType::Metrics => self.use_otlp_decoding.metrics,
259 OtlpSignalType::Traces => self.use_otlp_decoding.traces,
260 };
261
262 if should_use_otlp {
263 Ok(Some(OtlpDeserializer::new_with_signals(IndexSet::from([
264 signal_type,
265 ]))))
266 } else {
267 Ok(None)
268 }
269 }
270}
271
272#[async_trait::async_trait]
273#[typetag::serde(name = "opentelemetry")]
274impl SourceConfig for OpentelemetryConfig {
275 async fn build(&self, cx: SourceContext) -> crate::Result<Source> {
276 let acknowledgements = cx.do_acknowledgements(self.acknowledgements);
277 let events_received = register!(EventsReceived);
278 let log_namespace = cx.log_namespace(self.log_namespace);
279
280 let grpc_tls_settings = MaybeTlsSettings::from_config(self.grpc.tls.as_ref(), true)?;
281
282 if self.use_otlp_decoding.is_mixed() {
284 info!(
285 message = "Signals with OTLP decoding enabled will preserve raw format; others will use Vector native format.",
286 logs_otlp = self.use_otlp_decoding.logs,
287 metrics_otlp = self.use_otlp_decoding.metrics,
288 traces_otlp = self.use_otlp_decoding.traces,
289 );
290 }
291
292 let logs_deserializer = self.get_signal_deserializer(OtlpSignalType::Logs)?;
293 let metrics_deserializer = self.get_signal_deserializer(OtlpSignalType::Metrics)?;
294 let traces_deserializer = self.get_signal_deserializer(OtlpSignalType::Traces)?;
295
296 let log_service = LogsServiceServer::new(Service {
300 pipeline: cx.out.clone(),
301 acknowledgements,
302 log_namespace,
303 events_received: events_received.clone(),
304 deserializer: logs_deserializer.clone(),
305 })
306 .max_decoding_message_size(max_decompressed_size_bytes());
307
308 let metrics_service = MetricsServiceServer::new(Service {
309 pipeline: cx.out.clone(),
310 acknowledgements,
311 log_namespace,
312 events_received: events_received.clone(),
313 deserializer: metrics_deserializer.clone(),
314 })
315 .max_decoding_message_size(max_decompressed_size_bytes());
316
317 let trace_service = TraceServiceServer::new(Service {
318 pipeline: cx.out.clone(),
319 acknowledgements,
320 log_namespace,
321 events_received: events_received.clone(),
322 deserializer: traces_deserializer.clone(),
323 })
324 .max_decoding_message_size(max_decompressed_size_bytes());
325
326 let mut builder = RoutesBuilder::default();
327 builder
328 .add_service(log_service)
329 .add_service(metrics_service)
330 .add_service(trace_service);
331
332 let grpc_source = run_grpc_server_with_routes(
333 self.grpc.address,
334 grpc_tls_settings,
335 builder.routes(),
336 self.grpc.keepalive.clone(),
337 cx.shutdown.clone(),
338 )
339 .map_err(|error| {
340 error!(message = "OpenTelemetry source gRPC server failed.", %error);
341 });
342
343 let http_tls_settings = MaybeTlsSettings::from_config(self.http.tls.as_ref(), true)?;
344 let protocol = http_tls_settings.http_protocol_name();
345 let bytes_received = register!(BytesReceived::from(Protocol::from(protocol)));
346 let headers =
347 build_param_matcher(&remove_duplicates(self.http.headers.clone(), "headers"))?;
348
349 let filters = build_warp_filter(
350 acknowledgements,
351 log_namespace,
352 cx.out,
353 bytes_received,
354 events_received,
355 headers,
356 logs_deserializer,
357 metrics_deserializer,
358 traces_deserializer,
359 );
360
361 let http_source = run_http_server(
362 self.http.address,
363 http_tls_settings,
364 filters,
365 cx.shutdown,
366 self.http.keepalive.clone(),
367 )
368 .map_err(|error| {
369 error!(message = "OpenTelemetry source HTTP server failed.", %error);
370 });
371
372 Ok(join(grpc_source, http_source).map(|_| Ok(())).boxed())
373 }
374
375 fn outputs(&self, global_log_namespace: LogNamespace) -> Vec<SourceOutput> {
378 let log_namespace = global_log_namespace.merge(self.log_namespace);
379 let schema_definition = Definition::new_with_default_metadata(Kind::any(), [log_namespace])
380 .with_source_metadata(
381 Self::NAME,
382 Some(LegacyKey::Overwrite(owned_value_path!(RESOURCE_KEY))),
383 &owned_value_path!(RESOURCE_KEY),
384 Kind::object(Collection::from_unknown(Kind::any())).or_undefined(),
385 None,
386 )
387 .with_source_metadata(
388 Self::NAME,
389 Some(LegacyKey::Overwrite(owned_value_path!(ATTRIBUTES_KEY))),
390 &owned_value_path!(ATTRIBUTES_KEY),
391 Kind::object(Collection::from_unknown(Kind::any())).or_undefined(),
392 None,
393 )
394 .with_source_metadata(
395 Self::NAME,
396 Some(LegacyKey::Overwrite(owned_value_path!(TRACE_ID_KEY))),
397 &owned_value_path!(TRACE_ID_KEY),
398 Kind::bytes().or_undefined(),
399 None,
400 )
401 .with_source_metadata(
402 Self::NAME,
403 Some(LegacyKey::Overwrite(owned_value_path!(SPAN_ID_KEY))),
404 &owned_value_path!(SPAN_ID_KEY),
405 Kind::bytes().or_undefined(),
406 None,
407 )
408 .with_source_metadata(
409 Self::NAME,
410 Some(LegacyKey::Overwrite(owned_value_path!(SEVERITY_TEXT_KEY))),
411 &owned_value_path!(SEVERITY_TEXT_KEY),
412 Kind::bytes().or_undefined(),
413 Some("severity"),
414 )
415 .with_source_metadata(
416 Self::NAME,
417 Some(LegacyKey::Overwrite(owned_value_path!(SEVERITY_NUMBER_KEY))),
418 &owned_value_path!(SEVERITY_NUMBER_KEY),
419 Kind::integer().or_undefined(),
420 None,
421 )
422 .with_source_metadata(
423 Self::NAME,
424 Some(LegacyKey::Overwrite(owned_value_path!(FLAGS_KEY))),
425 &owned_value_path!(FLAGS_KEY),
426 Kind::integer().or_undefined(),
427 None,
428 )
429 .with_source_metadata(
430 Self::NAME,
431 Some(LegacyKey::Overwrite(owned_value_path!(
432 DROPPED_ATTRIBUTES_COUNT_KEY
433 ))),
434 &owned_value_path!(DROPPED_ATTRIBUTES_COUNT_KEY),
435 Kind::integer(),
436 None,
437 )
438 .with_source_metadata(
439 Self::NAME,
440 Some(LegacyKey::Overwrite(owned_value_path!(
441 OBSERVED_TIMESTAMP_KEY
442 ))),
443 &owned_value_path!(OBSERVED_TIMESTAMP_KEY),
444 Kind::timestamp(),
445 None,
446 )
447 .with_source_metadata(
448 Self::NAME,
449 None,
450 &owned_value_path!("timestamp"),
451 Kind::timestamp(),
452 Some("timestamp"),
453 )
454 .with_standard_vector_source_metadata();
455
456 let schema_definition = match log_namespace {
457 LogNamespace::Vector => {
458 schema_definition.with_meaning(OwnedTargetPath::event_root(), "message")
459 }
460 LogNamespace::Legacy => {
461 schema_definition.with_meaning(log_schema().owned_message_path(), "message")
462 }
463 };
464
465 let logs_output = if self.use_otlp_decoding.logs {
466 SourceOutput::new_maybe_logs(DataType::Log, Definition::any()).with_port(LOGS)
467 } else {
468 SourceOutput::new_maybe_logs(DataType::Log, schema_definition).with_port(LOGS)
469 };
470
471 let metrics_output = if self.use_otlp_decoding.metrics {
472 SourceOutput::new_maybe_logs(DataType::Log, Definition::any()).with_port(METRICS)
473 } else {
474 SourceOutput::new_metrics().with_port(METRICS)
475 };
476
477 vec![
478 logs_output,
479 metrics_output,
480 SourceOutput::new_traces().with_port(TRACES),
481 ]
482 }
483
484 fn resources(&self) -> Vec<Resource> {
485 vec![
486 Resource::tcp(self.grpc.address),
487 Resource::tcp(self.http.address),
488 ]
489 }
490
491 fn can_acknowledge(&self) -> bool {
492 true
493 }
494}