saluki_components/decoders/otlp/
mod.rs1use std::time::Duration;
2
3use agent_data_plane_config::domains;
4use async_trait::async_trait;
5use otlp_protos::opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest;
6use prost::Message;
7use saluki_core::accounting::{MemoryBounds, MemoryBoundsBuilder};
8use saluki_core::{
9 components::{
10 decoders::{Decoder, DecoderBuilder, DecoderContext},
11 ComponentContext,
12 },
13 data_model::{event::EventType, payload::PayloadType},
14 topology::interconnect::EventBufferManager,
15};
16use saluki_error::GenericError;
17use tokio::{
18 select,
19 time::{interval, MissedTickBehavior},
20};
21use tracing::{debug, error, warn};
22
23use crate::common::otlp::traces::translator::OtlpTracesTranslator;
24use crate::common::otlp::{
25 build_metrics, config::TracesConfig, Metrics, OTLP_LOGS_GRPC_SERVICE_PATH, OTLP_METRICS_GRPC_SERVICE_PATH,
26 OTLP_TRACES_GRPC_SERVICE_PATH,
27};
28
29#[derive(Default)]
31pub struct OtlpDecoderConfiguration {
32 traces: domains::otlp::Traces,
34}
35
36impl OtlpDecoderConfiguration {
37 pub fn from_configuration(traces: &domains::otlp::Traces) -> Self {
39 Self { traces: traces.clone() }
40 }
41}
42
43#[async_trait]
44impl DecoderBuilder for OtlpDecoderConfiguration {
45 fn input_payload_type(&self) -> PayloadType {
46 PayloadType::Grpc
47 }
48
49 fn output_event_type(&self) -> EventType {
50 EventType::Trace
51 }
52
53 async fn build(&self, context: ComponentContext) -> Result<Box<dyn Decoder + Send>, GenericError> {
54 let metrics = build_metrics(&context);
55 let traces_config = TracesConfig {
56 enable_otlp_compute_top_level_by_span_kind: self.traces.enable_compute_top_level_by_span_kind,
57 ignore_missing_datadog_fields: self.traces.ignore_missing_datadog_fields,
58 ..Default::default()
59 };
60 let traces_translator = OtlpTracesTranslator::new(traces_config, self.traces.string_interner_size);
61
62 Ok(Box::new(OtlpDecoder {
63 traces_translator,
64 metrics,
65 }))
66 }
67}
68
69impl MemoryBounds for OtlpDecoderConfiguration {
70 fn specify_bounds(&self, builder: &mut MemoryBoundsBuilder) {
71 builder.minimum().with_single_value::<OtlpDecoder>("decoder struct");
72 }
73}
74
75pub struct OtlpDecoder {
77 traces_translator: OtlpTracesTranslator,
78 metrics: Metrics,
79}
80
81#[async_trait]
82impl Decoder for OtlpDecoder {
83 async fn run(self: Box<Self>, mut context: DecoderContext) -> Result<(), GenericError> {
84 let Self {
85 mut traces_translator,
86 metrics,
87 } = *self;
88 let mut health = context.take_health_handle();
89 health.mark_ready();
90
91 debug!("OTLP decoder started.");
92
93 let mut buffer_flush = interval(Duration::from_millis(100));
95 buffer_flush.set_missed_tick_behavior(MissedTickBehavior::Delay);
96
97 let mut event_buffer_manager = EventBufferManager::default();
98
99 loop {
100 select! {
101 maybe_payload = context.payloads().next() => {
102 let payload = match maybe_payload {
103 Some(payload) => payload,
104 None => {
105 debug!("Payloads stream closed, shutting down decoder.");
106 break;
107 }
108 };
109
110 let grpc_payload = match payload.try_into_grpc_payload() {
111 Some(grpc) => grpc,
112 None => {
113 warn!("Received non-gRPC payload in OTLP decoder. Dropping payload.");
114 continue;
115 }
116 };
117
118 match grpc_payload.service_path() {
119 path if path == &*OTLP_TRACES_GRPC_SERVICE_PATH => {
120 let (_, _, _, body) = grpc_payload.into_parts();
121 let request = match ExportTraceServiceRequest::decode(body.into_bytes()) {
122 Ok(req) => req,
123 Err(e) => {
124 error!(error = %e, "Failed to decode OTLP trace request.");
125 continue;
126 }
127 };
128
129 for resource_spans in request.resource_spans {
130 for trace_event in traces_translator.translate_spans(resource_spans, &metrics) {
131 if let Some(event_buffer) = event_buffer_manager.try_push(trace_event) {
132 if let Err(e) = context.dispatcher().dispatch(event_buffer).await {
133 error!(error = %e, "Failed to dispatch trace events.");
134 }
135 }
136 }
137 }
138 }
139 path if path == &*OTLP_METRICS_GRPC_SERVICE_PATH => {
140 warn!("OTLP metrics decoding not yet implemented. Dropping metrics payload.");
141 }
142 path if path == &*OTLP_LOGS_GRPC_SERVICE_PATH => {
143 warn!("OTLP logs decoding not yet implemented. Dropping logs payload.");
144 }
145 path => {
146 warn!(service_path = path, "Received gRPC payload with unknown service path. Dropping payload.");
147 }
148 }
149 },
150 _ = buffer_flush.tick() => {
151 if let Some(event_buffer) = event_buffer_manager.consume() {
152 if let Err(e) = context.dispatcher().dispatch(event_buffer).await {
153 error!(error = %e, "Failed to dispatch buffered trace events.");
154 }
155 }
156 },
157 _ = health.live() => continue,
158 }
159 }
160
161 if let Some(event_buffer) = event_buffer_manager.consume() {
162 if let Err(e) = context.dispatcher().dispatch(event_buffer).await {
163 error!(error = %e, "Failed to dispatch final trace events.");
164 }
165 }
166
167 debug!("OTLP decoder stopped.");
168
169 Ok(())
170 }
171}