Skip to main content

libdd_trace_utils/span/v04/
mod.rs

1// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::span::{BytesData, SliceData, SpanKeyParseError, TraceData};
5use crate::tracer_payload::TraceChunks;
6use serde::ser::SerializeStruct;
7use serde::Serialize;
8use std::borrow::Borrow;
9use std::collections::HashMap;
10use std::str::FromStr;
11
12#[derive(Debug, PartialEq)]
13pub enum SpanKey {
14    Service,
15    Name,
16    Resource,
17    TraceId,
18    SpanId,
19    ParentId,
20    Start,
21    Duration,
22    Error,
23    Meta,
24    Metrics,
25    Type,
26    MetaStruct,
27    SpanLinks,
28    SpanEvents,
29}
30
31impl FromStr for SpanKey {
32    type Err = SpanKeyParseError;
33
34    fn from_str(s: &str) -> Result<Self, Self::Err> {
35        match s {
36            "service" => Ok(SpanKey::Service),
37            "name" => Ok(SpanKey::Name),
38            "resource" => Ok(SpanKey::Resource),
39            "trace_id" => Ok(SpanKey::TraceId),
40            "span_id" => Ok(SpanKey::SpanId),
41            "parent_id" => Ok(SpanKey::ParentId),
42            "start" => Ok(SpanKey::Start),
43            "duration" => Ok(SpanKey::Duration),
44            "error" => Ok(SpanKey::Error),
45            "meta" => Ok(SpanKey::Meta),
46            "metrics" => Ok(SpanKey::Metrics),
47            "type" => Ok(SpanKey::Type),
48            "meta_struct" => Ok(SpanKey::MetaStruct),
49            "span_links" => Ok(SpanKey::SpanLinks),
50            "span_events" => Ok(SpanKey::SpanEvents),
51            _ => Err(SpanKeyParseError::new(format!("Invalid span key: {s}"))),
52        }
53    }
54}
55
56/// Checks if the `value` represents an empty string. Used to skip serializing empty strings
57/// with serde.
58fn is_empty_str<T: Borrow<str>>(value: &T) -> bool {
59    value.borrow().is_empty()
60}
61
62/// The generic representation of a V04 span.
63///
64/// `T` is the type used to represent strings in the span, it can be either owned (e.g. BytesString)
65/// or borrowed (e.g. &str). To define a generic function taking any `Span<T>` you can use the
66/// [`SpanValue`] trait:
67/// ```
68/// use libdd_trace_utils::span::{v04::Span, TraceData};
69/// fn foo<T: TraceData>(span: Span<T>) {
70///     let _ = span.meta.get("foo");
71/// }
72/// ```
73#[derive(Debug, Default, PartialEq, Serialize)]
74pub struct Span<T: TraceData> {
75    pub service: T::Text,
76    pub name: T::Text,
77    pub resource: T::Text,
78    #[serde(skip_serializing_if = "is_empty_str")]
79    pub r#type: T::Text,
80    #[serde(serialize_with = "serialize_lower_64_bits")]
81    pub trace_id: u128,
82    pub span_id: u64,
83    #[serde(skip_serializing_if = "is_default")]
84    pub parent_id: u64,
85    pub start: i64,
86    pub duration: i64,
87    #[serde(skip_serializing_if = "is_default")]
88    pub error: i32,
89    #[serde(skip_serializing_if = "HashMap::is_empty")]
90    pub meta: HashMap<T::Text, T::Text>,
91    #[serde(skip_serializing_if = "HashMap::is_empty")]
92    pub metrics: HashMap<T::Text, f64>,
93    #[serde(skip_serializing_if = "HashMap::is_empty")]
94    pub meta_struct: HashMap<T::Text, T::Bytes>,
95    #[serde(skip_serializing_if = "Vec::is_empty")]
96    pub span_links: Vec<SpanLink<T>>,
97    #[serde(skip_serializing_if = "Vec::is_empty")]
98    pub span_events: Vec<SpanEvent<T>>,
99}
100
101impl<T: TraceData> Clone for Span<T>
102where
103    T::Text: Clone,
104    T::Bytes: Clone,
105{
106    fn clone(&self) -> Self {
107        Span {
108            service: self.service.clone(),
109            name: self.name.clone(),
110            resource: self.resource.clone(),
111            r#type: self.r#type.clone(),
112            trace_id: self.trace_id,
113            span_id: self.span_id,
114            parent_id: self.parent_id,
115            start: self.start,
116            duration: self.duration,
117            error: self.error,
118            meta: self.meta.clone(),
119            metrics: self.metrics.clone(),
120            meta_struct: self.meta_struct.clone(),
121            span_links: self.span_links.clone(),
122            span_events: self.span_events.clone(),
123        }
124    }
125}
126
127fn serialize_lower_64_bits<S>(v: &u128, serializer: S) -> Result<S::Ok, S::Error>
128where
129    S: serde::Serializer,
130{
131    serializer.serialize_u64(*v as u64)
132}
133
134/// The generic representation of a V04 span link.
135/// `T` is the type used to represent strings in the span link.
136#[derive(Debug, Default, PartialEq, Serialize)]
137pub struct SpanLink<T: TraceData> {
138    pub trace_id: u64,
139    pub trace_id_high: u64,
140    pub span_id: u64,
141    #[serde(skip_serializing_if = "HashMap::is_empty")]
142    pub attributes: HashMap<T::Text, T::Text>,
143    #[serde(skip_serializing_if = "is_empty_str")]
144    pub tracestate: T::Text,
145    #[serde(skip_serializing_if = "is_default")]
146    pub flags: u32,
147}
148
149impl<T: TraceData> Clone for SpanLink<T>
150where
151    T::Text: Clone,
152{
153    fn clone(&self) -> Self {
154        SpanLink {
155            trace_id: self.trace_id,
156            trace_id_high: self.trace_id_high,
157            span_id: self.span_id,
158            attributes: self.attributes.clone(),
159            tracestate: self.tracestate.clone(),
160            flags: self.flags,
161        }
162    }
163}
164
165/// The generic representation of a V04 span event.
166/// `T` is the type used to represent strings in the span event.
167#[derive(Debug, Default, PartialEq, Serialize)]
168pub struct SpanEvent<T: TraceData> {
169    pub time_unix_nano: u64,
170    pub name: T::Text,
171    #[serde(skip_serializing_if = "HashMap::is_empty")]
172    pub attributes: HashMap<T::Text, AttributeAnyValue<T>>,
173}
174
175impl<T: TraceData> Clone for SpanEvent<T>
176where
177    T::Text: Clone,
178{
179    fn clone(&self) -> Self {
180        SpanEvent {
181            time_unix_nano: self.time_unix_nano,
182            name: self.name.clone(),
183            attributes: self.attributes.clone(),
184        }
185    }
186}
187
188#[derive(Debug, PartialEq)]
189pub enum AttributeAnyValue<T: TraceData> {
190    SingleValue(AttributeArrayValue<T>),
191    Array(Vec<AttributeArrayValue<T>>),
192}
193
194#[derive(Serialize)]
195struct ArrayValueWrapper<'a, T: TraceData> {
196    #[serde(bound(serialize = "T::Text: Serialize"))]
197    values: &'a Vec<AttributeArrayValue<T>>,
198}
199
200impl<T: TraceData> Serialize for AttributeAnyValue<T> {
201    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
202    where
203        S: serde::Serializer,
204    {
205        let mut state = serializer.serialize_struct("AttributeAnyValue", 2)?;
206
207        match self {
208            AttributeAnyValue::SingleValue(attribute) => {
209                serialize_attribute_array::<S, T>(&mut state, attribute)?;
210            }
211            AttributeAnyValue::Array(value) => {
212                let value_type: u8 = self.into();
213                state.serialize_field("type", &value_type)?;
214                let wrapped_value = ArrayValueWrapper { values: value };
215                state.serialize_field("array_value", &wrapped_value)?;
216            }
217        }
218
219        state.end()
220    }
221}
222
223impl<T: TraceData> From<&AttributeAnyValue<T>> for u8 {
224    fn from(attribute: &AttributeAnyValue<T>) -> u8 {
225        match attribute {
226            AttributeAnyValue::SingleValue(value) => value.into(),
227            AttributeAnyValue::Array(_) => 4,
228        }
229    }
230}
231
232impl<T: TraceData> Clone for AttributeAnyValue<T>
233where
234    T::Text: Clone,
235{
236    fn clone(&self) -> Self {
237        match self {
238            AttributeAnyValue::SingleValue(v) => AttributeAnyValue::SingleValue(v.clone()),
239            AttributeAnyValue::Array(vec) => AttributeAnyValue::Array(vec.clone()),
240        }
241    }
242}
243
244#[derive(Debug, PartialEq)]
245pub enum AttributeArrayValue<T: TraceData> {
246    String(T::Text),
247    Boolean(bool),
248    Integer(i64),
249    Double(f64),
250}
251
252impl<T: TraceData> Clone for AttributeArrayValue<T>
253where
254    T::Text: Clone,
255{
256    fn clone(&self) -> Self {
257        match self {
258            AttributeArrayValue::String(v) => AttributeArrayValue::String(v.clone()),
259            AttributeArrayValue::Boolean(v) => AttributeArrayValue::Boolean(*v),
260            AttributeArrayValue::Integer(v) => AttributeArrayValue::Integer(*v),
261            AttributeArrayValue::Double(v) => AttributeArrayValue::Double(*v),
262        }
263    }
264}
265
266impl<T: TraceData> Serialize for AttributeArrayValue<T> {
267    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
268    where
269        S: serde::Serializer,
270    {
271        let mut state = serializer.serialize_struct("AttributeArrayValue", 2)?;
272        serialize_attribute_array::<S, T>(&mut state, self)?;
273        state.end()
274    }
275}
276
277fn serialize_attribute_array<S, T>(
278    state: &mut S::SerializeStruct,
279    attribute: &AttributeArrayValue<T>,
280) -> Result<(), <S>::Error>
281where
282    T: TraceData,
283    S: serde::Serializer,
284{
285    let attribute_type: u8 = attribute.into();
286    state.serialize_field("type", &attribute_type)?;
287    match attribute {
288        AttributeArrayValue::String(value) => state.serialize_field("string_value", value),
289        AttributeArrayValue::Boolean(value) => state.serialize_field("bool_value", value),
290        AttributeArrayValue::Integer(value) => state.serialize_field("int_value", value),
291        AttributeArrayValue::Double(value) => state.serialize_field("double_value", value),
292    }
293}
294
295impl<T: TraceData> From<&AttributeArrayValue<T>> for u8 {
296    fn from(attribute: &AttributeArrayValue<T>) -> u8 {
297        match attribute {
298            AttributeArrayValue::String(_) => 0,
299            AttributeArrayValue::Boolean(_) => 1,
300            AttributeArrayValue::Integer(_) => 2,
301            AttributeArrayValue::Double(_) => 3,
302        }
303    }
304}
305
306fn is_default<T: Default + PartialEq>(t: &T) -> bool {
307    t == &T::default()
308}
309
310pub type SpanBytes = Span<BytesData>;
311pub type SpanLinkBytes = SpanLink<BytesData>;
312pub type SpanEventBytes = SpanEvent<BytesData>;
313pub type AttributeAnyValueBytes = AttributeAnyValue<BytesData>;
314pub type AttributeArrayValueBytes = AttributeArrayValue<BytesData>;
315
316pub type SpanSlice<'a> = Span<SliceData<'a>>;
317pub type SpanLinkSlice<'a> = SpanLink<SliceData<'a>>;
318pub type SpanEventSlice<'a> = SpanEvent<SliceData<'a>>;
319pub type AttributeAnyValueSlice<'a> = AttributeAnyValue<SliceData<'a>>;
320pub type AttributeArrayValueSlice<'a> = AttributeArrayValue<SliceData<'a>>;
321
322pub type TraceChunksBytes = TraceChunks<BytesData>;
323
324#[cfg(test)]
325mod tests {
326    use super::{AttributeAnyValue, AttributeArrayValue, Span, SpanEvent, SpanLink};
327    use crate::msgpack_decoder::decode::buffer::Buffer;
328    use crate::msgpack_decoder::v04::span::decode_span;
329    use crate::span::SliceData;
330    use std::collections::HashMap;
331
332    #[test]
333    fn skip_serializing_empty_fields_test() {
334        let expected = b"\x87\xa7service\xa0\xa4name\xa0\xa8resource\xa0\xa8trace_id\x00\xa7span_id\x00\xa5start\x00\xa8duration\x00";
335        let val: Span<SliceData<'_>> = Span::default();
336        let serialized = rmp_serde::encode::to_vec_named(&val).unwrap();
337        assert_eq!(expected, serialized.as_slice());
338    }
339
340    #[test]
341    fn serialize_deserialize_test() {
342        let span: Span<SliceData<'_>> = Span {
343            name: "tracing.operation",
344            resource: "MyEndpoint",
345            span_links: vec![SpanLink {
346                trace_id: 42,
347                attributes: HashMap::from([("span", "link")]),
348                tracestate: "running",
349                ..Default::default()
350            }],
351            span_events: vec![SpanEvent {
352                time_unix_nano: 1727211691770716000,
353                name: "exception",
354                attributes: HashMap::from([
355                    (
356                        "exception.message",
357                        AttributeAnyValue::SingleValue(AttributeArrayValue::String(
358                            "Cannot divide by zero",
359                        )),
360                    ),
361                    (
362                        "exception.type",
363                        AttributeAnyValue::SingleValue(AttributeArrayValue::String("RuntimeError")),
364                    ),
365                    (
366                        "exception.escaped",
367                        AttributeAnyValue::SingleValue(AttributeArrayValue::Boolean(false)),
368                    ),
369                    (
370                        "exception.count",
371                        AttributeAnyValue::SingleValue(AttributeArrayValue::Integer(1)),
372                    ),
373                    (
374                        "exception.lines",
375                        AttributeAnyValue::Array(vec![
376                            AttributeArrayValue::String("  File \"<string>\", line 1, in <module>"),
377                            AttributeArrayValue::String("  File \"<string>\", line 1, in divide"),
378                            AttributeArrayValue::String("RuntimeError: Cannot divide by zero"),
379                        ]),
380                    ),
381                ]),
382            }],
383            ..Default::default()
384        };
385
386        let serialized = rmp_serde::encode::to_vec_named(&span).unwrap();
387        let mut serialized_slice = Buffer::<SliceData<'_>>::new(serialized.as_ref());
388        let deserialized = decode_span(&mut serialized_slice).unwrap();
389
390        assert_eq!(span.name, deserialized.name);
391        assert_eq!(span.resource, deserialized.resource);
392        assert_eq!(
393            span.span_links[0].trace_id,
394            deserialized.span_links[0].trace_id
395        );
396        assert_eq!(
397            span.span_links[0].tracestate,
398            deserialized.span_links[0].tracestate
399        );
400        assert_eq!(span.span_events[0].name, deserialized.span_events[0].name);
401        assert_eq!(
402            span.span_events[0].time_unix_nano,
403            deserialized.span_events[0].time_unix_nano
404        );
405        for attribut in &deserialized.span_events[0].attributes {
406            assert!(span.span_events[0].attributes.contains_key(attribut.0))
407        }
408    }
409
410    #[test]
411    fn serialize_event_test() {
412        // `expected` is created by transforming the span into bytes
413        // and passing each bytes through `escaped_default`
414        let expected = b"\x88\xa7service\xa0\xa4name\xa0\xa8resource\xa0\xa8trace_id\x00\xa7span_id\x00\xa5start\x00\xa8duration\x00\xabspan_events\x91\x83\xaetime_unix_nano\xcf\x17\xf8I\xe1\xeb\xe5\x1f`\xa4name\xa4test\xaaattributes\x81\xaatest.event\x82\xa4type\x03\xacdouble_value\xcb@\x10\xcc\xcc\xcc\xcc\xcc\xcd";
415
416        let span: Span<SliceData<'_>> = Span {
417            span_events: vec![SpanEvent {
418                time_unix_nano: 1727211691770716000,
419                name: "test",
420                attributes: HashMap::from([(
421                    "test.event",
422                    AttributeAnyValue::SingleValue(AttributeArrayValue::Double(4.2)),
423                )]),
424            }],
425            ..Default::default()
426        };
427
428        let serialized = rmp_serde::encode::to_vec_named(&span).unwrap();
429        assert_eq!(expected, serialized.as_slice());
430    }
431}