Skip to main content

enya_client/otlp/
proto.rs

1//! Minimal prost message definitions for OTLP protobuf decoding.
2//!
3//! These structs mirror the OpenTelemetry proto definitions just enough to
4//! decode `ExportTraceServiceRequest`, `ExportLogsServiceRequest`, and
5//! `ExportMetricsServiceRequest` protobuf payloads. Field numbers match the
6//! upstream `.proto` files exactly.
7//!
8//! After decoding, values are converted to the JSON-compatible types in
9//! [`super::types`] so the rest of the pipeline is shared.
10
11use rustc_hash::FxHashMap;
12
13// ============================================================================
14// Common types
15// ============================================================================
16
17#[derive(prost::Message, Clone)]
18pub struct Resource {
19    #[prost(message, repeated, tag = "1")]
20    pub attributes: Vec<KeyValue>,
21}
22
23#[derive(prost::Message, Clone)]
24pub struct KeyValue {
25    #[prost(string, tag = "1")]
26    pub key: String,
27    #[prost(message, optional, tag = "2")]
28    pub value: Option<AnyValue>,
29}
30
31#[derive(prost::Message, Clone)]
32pub struct AnyValue {
33    #[prost(oneof = "any_value::Value", tags = "1, 2, 3, 4")]
34    pub value: Option<any_value::Value>,
35}
36
37pub mod any_value {
38    #[derive(prost::Oneof, Clone)]
39    pub enum Value {
40        #[prost(string, tag = "1")]
41        StringValue(String),
42        #[prost(bool, tag = "2")]
43        BoolValue(bool),
44        #[prost(int64, tag = "3")]
45        IntValue(i64),
46        #[prost(double, tag = "4")]
47        DoubleValue(f64),
48    }
49}
50
51// ============================================================================
52// Traces
53// ============================================================================
54
55#[derive(prost::Message)]
56pub struct ExportTraceServiceRequest {
57    #[prost(message, repeated, tag = "1")]
58    pub resource_spans: Vec<ResourceSpans>,
59}
60
61#[derive(prost::Message)]
62pub struct ResourceSpans {
63    #[prost(message, optional, tag = "1")]
64    pub resource: Option<Resource>,
65    #[prost(message, repeated, tag = "2")]
66    pub scope_spans: Vec<ScopeSpans>,
67}
68
69#[derive(prost::Message)]
70pub struct ScopeSpans {
71    #[prost(message, repeated, tag = "2")]
72    pub spans: Vec<ProtoSpan>,
73}
74
75#[derive(prost::Message, Clone)]
76pub struct ProtoSpan {
77    #[prost(bytes, tag = "1")]
78    pub trace_id: Vec<u8>,
79    #[prost(bytes, tag = "2")]
80    pub span_id: Vec<u8>,
81    #[prost(bytes, tag = "4")]
82    pub parent_span_id: Vec<u8>,
83    #[prost(string, tag = "5")]
84    pub name: String,
85    #[prost(fixed64, tag = "7")]
86    pub start_time_unix_nano: u64,
87    #[prost(fixed64, tag = "8")]
88    pub end_time_unix_nano: u64,
89    #[prost(message, repeated, tag = "9")]
90    pub attributes: Vec<KeyValue>,
91    #[prost(message, repeated, tag = "11")]
92    pub events: Vec<ProtoEvent>,
93    #[prost(message, optional, tag = "15")]
94    pub status: Option<ProtoStatus>,
95}
96
97#[derive(prost::Message, Clone)]
98pub struct ProtoEvent {
99    #[prost(fixed64, tag = "1")]
100    pub time_unix_nano: u64,
101    #[prost(string, tag = "2")]
102    pub name: String,
103    #[prost(message, repeated, tag = "3")]
104    pub attributes: Vec<KeyValue>,
105}
106
107#[derive(prost::Message, Clone)]
108pub struct ProtoStatus {
109    #[prost(string, tag = "2")]
110    pub message: String,
111    #[prost(int32, tag = "3")]
112    pub code: i32,
113}
114
115// ============================================================================
116// Logs
117// ============================================================================
118
119#[derive(prost::Message)]
120pub struct ExportLogsServiceRequest {
121    #[prost(message, repeated, tag = "1")]
122    pub resource_logs: Vec<ResourceLogs>,
123}
124
125#[derive(prost::Message)]
126pub struct ResourceLogs {
127    #[prost(message, optional, tag = "1")]
128    pub resource: Option<Resource>,
129    #[prost(message, repeated, tag = "2")]
130    pub scope_logs: Vec<ScopeLogs>,
131}
132
133#[derive(prost::Message)]
134pub struct ScopeLogs {
135    #[prost(message, repeated, tag = "2")]
136    pub log_records: Vec<ProtoLogRecord>,
137}
138
139#[derive(prost::Message, Clone)]
140pub struct ProtoLogRecord {
141    #[prost(fixed64, tag = "1")]
142    pub time_unix_nano: u64,
143    #[prost(int32, tag = "2")]
144    pub severity_number: i32,
145    #[prost(string, tag = "3")]
146    pub severity_text: String,
147    #[prost(message, optional, tag = "5")]
148    pub body: Option<AnyValue>,
149    #[prost(message, repeated, tag = "6")]
150    pub attributes: Vec<KeyValue>,
151    #[prost(bytes, tag = "9")]
152    pub trace_id: Vec<u8>,
153    #[prost(bytes, tag = "10")]
154    pub span_id: Vec<u8>,
155    #[prost(fixed64, tag = "11")]
156    pub observed_time_unix_nano: u64,
157}
158
159// ============================================================================
160// Metrics
161// ============================================================================
162
163#[derive(prost::Message)]
164pub struct ExportMetricsServiceRequest {
165    #[prost(message, repeated, tag = "1")]
166    pub resource_metrics: Vec<ProtoResourceMetrics>,
167}
168
169#[derive(prost::Message)]
170pub struct ProtoResourceMetrics {
171    #[prost(message, optional, tag = "1")]
172    pub resource: Option<Resource>,
173    #[prost(message, repeated, tag = "2")]
174    pub scope_metrics: Vec<ProtoScopeMetrics>,
175}
176
177#[derive(prost::Message)]
178pub struct ProtoScopeMetrics {
179    #[prost(message, repeated, tag = "2")]
180    pub metrics: Vec<ProtoMetric>,
181}
182
183#[derive(prost::Message, Clone)]
184pub struct ProtoMetric {
185    #[prost(string, tag = "1")]
186    pub name: String,
187    #[prost(string, tag = "3")]
188    pub unit: String,
189    #[prost(oneof = "proto_metric::Data", tags = "5, 7, 9")]
190    pub data: Option<proto_metric::Data>,
191}
192
193pub mod proto_metric {
194    #[derive(prost::Oneof, Clone)]
195    pub enum Data {
196        #[prost(message, tag = "5")]
197        Gauge(super::ProtoGauge),
198        #[prost(message, tag = "7")]
199        Sum(super::ProtoSum),
200        #[prost(message, tag = "9")]
201        Histogram(super::ProtoHistogram),
202    }
203}
204
205#[derive(prost::Message, Clone)]
206pub struct ProtoGauge {
207    #[prost(message, repeated, tag = "1")]
208    pub data_points: Vec<ProtoNumberDataPoint>,
209}
210
211#[derive(prost::Message, Clone)]
212pub struct ProtoSum {
213    #[prost(message, repeated, tag = "1")]
214    pub data_points: Vec<ProtoNumberDataPoint>,
215    #[prost(bool, tag = "3")]
216    pub is_monotonic: bool,
217}
218
219#[derive(prost::Message, Clone)]
220pub struct ProtoNumberDataPoint {
221    #[prost(fixed64, tag = "2")]
222    pub start_time_unix_nano: u64,
223    #[prost(fixed64, tag = "3")]
224    pub time_unix_nano: u64,
225    #[prost(oneof = "proto_number_value::Value", tags = "4, 6")]
226    pub value: Option<proto_number_value::Value>,
227    #[prost(message, repeated, tag = "7")]
228    pub attributes: Vec<KeyValue>,
229}
230
231pub mod proto_number_value {
232    #[derive(prost::Oneof, Clone)]
233    pub enum Value {
234        #[prost(double, tag = "4")]
235        AsDouble(f64),
236        #[prost(sfixed64, tag = "6")]
237        AsInt(i64),
238    }
239}
240
241#[derive(prost::Message, Clone)]
242pub struct ProtoHistogram {
243    #[prost(message, repeated, tag = "1")]
244    pub data_points: Vec<ProtoHistogramDataPoint>,
245}
246
247#[derive(prost::Message, Clone)]
248pub struct ProtoHistogramDataPoint {
249    #[prost(fixed64, tag = "2")]
250    pub start_time_unix_nano: u64,
251    #[prost(fixed64, tag = "3")]
252    pub time_unix_nano: u64,
253    #[prost(uint64, tag = "4")]
254    pub count: u64,
255    #[prost(double, optional, tag = "5")]
256    pub sum: Option<f64>,
257    #[prost(uint64, repeated, tag = "6")]
258    pub bucket_counts: Vec<u64>,
259    #[prost(double, repeated, tag = "7")]
260    pub explicit_bounds: Vec<f64>,
261    #[prost(message, repeated, tag = "9")]
262    pub attributes: Vec<KeyValue>,
263    #[prost(double, optional, tag = "11")]
264    pub min: Option<f64>,
265    #[prost(double, optional, tag = "12")]
266    pub max: Option<f64>,
267}
268
269// ============================================================================
270// Conversion helpers: proto types → domain types
271// ============================================================================
272
273/// Encode bytes as a lowercase hex string.
274pub fn bytes_to_hex(bytes: &[u8]) -> String {
275    use std::fmt::Write;
276    let mut s = String::with_capacity(bytes.len() * 2);
277    for b in bytes {
278        let _ = write!(s, "{b:02x}");
279    }
280    s
281}
282
283/// Extract the `service.name` attribute from a Resource.
284pub fn resource_service_name(resource: &Option<Resource>) -> String {
285    resource
286        .as_ref()
287        .and_then(|r| {
288            r.attributes
289                .iter()
290                .find(|a| a.key == "service.name")
291                .and_then(|a| match &a.value {
292                    Some(AnyValue {
293                        value: Some(any_value::Value::StringValue(s)),
294                    }) => Some(s.clone()),
295                    _ => None,
296                })
297        })
298        .unwrap_or_else(|| "unknown".to_string())
299}
300
301/// Convert proto KeyValue attributes to a string map.
302pub fn attrs_to_map(attrs: &[KeyValue]) -> FxHashMap<String, String> {
303    attrs
304        .iter()
305        .filter_map(|kv| {
306            let value = kv.value.as_ref().and_then(any_value_to_string_inner)?;
307            Some((kv.key.clone(), value))
308        })
309        .collect()
310}
311
312/// Get string value from an optional AnyValue.
313pub fn any_value_to_string(value: &Option<AnyValue>) -> String {
314    value
315        .as_ref()
316        .and_then(any_value_to_string_inner)
317        .unwrap_or_default()
318}
319
320/// Get string value from an AnyValue reference.
321pub fn any_value_ref_to_string(value: &AnyValue) -> String {
322    any_value_to_string_inner(value).unwrap_or_default()
323}
324
325fn any_value_to_string_inner(av: &AnyValue) -> Option<String> {
326    match &av.value {
327        Some(any_value::Value::StringValue(s)) => Some(s.clone()),
328        Some(any_value::Value::IntValue(i)) => Some(i.to_string()),
329        Some(any_value::Value::DoubleValue(d)) => Some(d.to_string()),
330        Some(any_value::Value::BoolValue(b)) => Some(b.to_string()),
331        None => None,
332    }
333}
334
335/// Get numeric value from a NumberDataPoint.
336pub fn number_data_point_value(dp: &ProtoNumberDataPoint) -> f64 {
337    match &dp.value {
338        Some(proto_number_value::Value::AsDouble(d)) => *d,
339        Some(proto_number_value::Value::AsInt(i)) => *i as f64,
340        None => 0.0,
341    }
342}