libdd_trace_utils/span/
mod.rs

1// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4pub mod trace_utils;
5pub mod v05;
6
7use libdd_tinybytes::{Bytes, BytesString};
8use serde::ser::SerializeStruct;
9use serde::Serialize;
10use std::borrow::Borrow;
11use std::collections::HashMap;
12use std::fmt;
13use std::hash::Hash;
14use std::str::FromStr;
15use v05::dict::SharedDict;
16
17use crate::tracer_payload::TraceChunks;
18
19#[derive(Debug, PartialEq)]
20pub enum SpanKey {
21    Service,
22    Name,
23    Resource,
24    TraceId,
25    SpanId,
26    ParentId,
27    Start,
28    Duration,
29    Error,
30    Meta,
31    Metrics,
32    Type,
33    MetaStruct,
34    SpanLinks,
35    SpanEvents,
36}
37
38impl FromStr for SpanKey {
39    type Err = SpanKeyParseError;
40
41    fn from_str(s: &str) -> Result<Self, Self::Err> {
42        match s {
43            "service" => Ok(SpanKey::Service),
44            "name" => Ok(SpanKey::Name),
45            "resource" => Ok(SpanKey::Resource),
46            "trace_id" => Ok(SpanKey::TraceId),
47            "span_id" => Ok(SpanKey::SpanId),
48            "parent_id" => Ok(SpanKey::ParentId),
49            "start" => Ok(SpanKey::Start),
50            "duration" => Ok(SpanKey::Duration),
51            "error" => Ok(SpanKey::Error),
52            "meta" => Ok(SpanKey::Meta),
53            "metrics" => Ok(SpanKey::Metrics),
54            "type" => Ok(SpanKey::Type),
55            "meta_struct" => Ok(SpanKey::MetaStruct),
56            "span_links" => Ok(SpanKey::SpanLinks),
57            "span_events" => Ok(SpanKey::SpanEvents),
58            _ => Err(SpanKeyParseError::new(format!("Invalid span key: {s}"))),
59        }
60    }
61}
62
63/// Trait representing the requirements for a type to be used as a Span "string" type.
64/// Note: Borrow<str> is not required by the derived traits, but allows to access HashMap elements
65/// from a static str and check if the string is empty.
66pub trait SpanText: Eq + Hash + Borrow<str> + Serialize + Default {
67    fn from_static_str(value: &'static str) -> Self;
68}
69
70impl SpanText for &str {
71    fn from_static_str(value: &'static str) -> Self {
72        value
73    }
74}
75
76impl SpanText for BytesString {
77    fn from_static_str(value: &'static str) -> Self {
78        BytesString::from_static(value)
79    }
80}
81
82/// Checks if the `value` represents an empty string. Used to skip serializing empty strings
83/// with serde.
84fn is_empty_str<T: Borrow<str>>(value: &T) -> bool {
85    value.borrow().is_empty()
86}
87
88/// The generic representation of a V04 span.
89///
90/// `T` is the type used to represent strings in the span, it can be either owned (e.g. BytesString)
91/// or borrowed (e.g. &str). To define a generic function taking any `Span<T>` you can use the
92/// [`SpanValue`] trait:
93/// ```
94/// use libdd_trace_utils::span::{Span, SpanText};
95/// fn foo<T: SpanText>(span: Span<T>) {
96///     let _ = span.meta.get("foo");
97/// }
98/// ```
99#[derive(Clone, Debug, Default, PartialEq, Serialize)]
100pub struct Span<T>
101where
102    T: SpanText,
103{
104    pub service: T,
105    pub name: T,
106    pub resource: T,
107    #[serde(skip_serializing_if = "is_empty_str")]
108    pub r#type: T,
109    #[serde(serialize_with = "serialize_lower_64_bits")]
110    pub trace_id: u128,
111    pub span_id: u64,
112    #[serde(skip_serializing_if = "is_default")]
113    pub parent_id: u64,
114    pub start: i64,
115    pub duration: i64,
116    #[serde(skip_serializing_if = "is_default")]
117    pub error: i32,
118    #[serde(skip_serializing_if = "HashMap::is_empty")]
119    pub meta: HashMap<T, T>,
120    #[serde(skip_serializing_if = "HashMap::is_empty")]
121    pub metrics: HashMap<T, f64>,
122    // TODO: APMSP-1941 - Replace `Bytes` with a wrapper that borrows the underlying
123    // slice and serializes to bytes in MessagePack.
124    #[serde(skip_serializing_if = "HashMap::is_empty")]
125    pub meta_struct: HashMap<T, Bytes>,
126    #[serde(skip_serializing_if = "Vec::is_empty")]
127    pub span_links: Vec<SpanLink<T>>,
128    #[serde(skip_serializing_if = "Vec::is_empty")]
129    pub span_events: Vec<SpanEvent<T>>,
130}
131
132fn serialize_lower_64_bits<S>(v: &u128, serializer: S) -> Result<S::Ok, S::Error>
133where
134    S: serde::Serializer,
135{
136    serializer.serialize_u64(*v as u64)
137}
138
139/// The generic representation of a V04 span link.
140/// `T` is the type used to represent strings in the span link.
141#[derive(Clone, Debug, Default, PartialEq, Serialize)]
142pub struct SpanLink<T>
143where
144    T: SpanText,
145{
146    pub trace_id: u64,
147    pub trace_id_high: u64,
148    pub span_id: u64,
149    #[serde(skip_serializing_if = "HashMap::is_empty")]
150    pub attributes: HashMap<T, T>,
151    #[serde(skip_serializing_if = "is_empty_str")]
152    pub tracestate: T,
153    #[serde(skip_serializing_if = "is_default")]
154    pub flags: u32,
155}
156
157/// The generic representation of a V04 span event.
158/// `T` is the type used to represent strings in the span event.
159#[derive(Clone, Debug, Default, PartialEq, Serialize)]
160pub struct SpanEvent<T>
161where
162    T: SpanText,
163{
164    pub time_unix_nano: u64,
165    pub name: T,
166    #[serde(skip_serializing_if = "HashMap::is_empty")]
167    pub attributes: HashMap<T, AttributeAnyValue<T>>,
168}
169
170#[derive(Clone, Debug, PartialEq)]
171pub enum AttributeAnyValue<T>
172where
173    T: SpanText,
174{
175    SingleValue(AttributeArrayValue<T>),
176    Array(Vec<AttributeArrayValue<T>>),
177}
178
179#[derive(Serialize)]
180struct ArrayValueWrapper<'a, T: SpanText> {
181    values: &'a Vec<AttributeArrayValue<T>>,
182}
183
184impl<T> Serialize for AttributeAnyValue<T>
185where
186    T: SpanText,
187{
188    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
189    where
190        S: serde::Serializer,
191    {
192        let mut state = serializer.serialize_struct("AttributeAnyValue", 2)?;
193
194        match self {
195            AttributeAnyValue::SingleValue(attribute) => {
196                serialize_attribute_array::<S, T>(&mut state, attribute)?;
197            }
198            AttributeAnyValue::Array(value) => {
199                let value_type: u8 = self.into();
200                state.serialize_field("type", &value_type)?;
201                let wrapped_value = ArrayValueWrapper { values: value };
202                state.serialize_field("array_value", &wrapped_value)?;
203            }
204        }
205
206        state.end()
207    }
208}
209
210impl<T> From<&AttributeAnyValue<T>> for u8
211where
212    T: SpanText,
213{
214    fn from(attribute: &AttributeAnyValue<T>) -> u8 {
215        match attribute {
216            AttributeAnyValue::SingleValue(value) => value.into(),
217            AttributeAnyValue::Array(_) => 4,
218        }
219    }
220}
221
222#[derive(Clone, Debug, PartialEq)]
223pub enum AttributeArrayValue<T>
224where
225    T: SpanText,
226{
227    String(T),
228    Boolean(bool),
229    Integer(i64),
230    Double(f64),
231}
232
233impl<T> Serialize for AttributeArrayValue<T>
234where
235    T: SpanText,
236{
237    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238    where
239        S: serde::Serializer,
240    {
241        let mut state = serializer.serialize_struct("AttributeArrayValue", 2)?;
242        serialize_attribute_array::<S, T>(&mut state, self)?;
243        state.end()
244    }
245}
246
247fn serialize_attribute_array<S, T>(
248    state: &mut S::SerializeStruct,
249    attribute: &AttributeArrayValue<T>,
250) -> Result<(), <S>::Error>
251where
252    T: SpanText,
253    S: serde::Serializer,
254{
255    let attribute_type: u8 = attribute.into();
256    state.serialize_field("type", &attribute_type)?;
257    match attribute {
258        AttributeArrayValue::String(value) => state.serialize_field("string_value", value),
259        AttributeArrayValue::Boolean(value) => state.serialize_field("bool_value", value),
260        AttributeArrayValue::Integer(value) => state.serialize_field("int_value", value),
261        AttributeArrayValue::Double(value) => state.serialize_field("double_value", value),
262    }
263}
264
265impl<T> From<&AttributeArrayValue<T>> for u8
266where
267    T: SpanText,
268{
269    fn from(attribute: &AttributeArrayValue<T>) -> u8 {
270        match attribute {
271            AttributeArrayValue::String(_) => 0,
272            AttributeArrayValue::Boolean(_) => 1,
273            AttributeArrayValue::Integer(_) => 2,
274            AttributeArrayValue::Double(_) => 3,
275        }
276    }
277}
278
279pub type SpanBytes = Span<BytesString>;
280pub type SpanLinkBytes = SpanLink<BytesString>;
281pub type SpanEventBytes = SpanEvent<BytesString>;
282pub type AttributeAnyValueBytes = AttributeAnyValue<BytesString>;
283pub type AttributeArrayValueBytes = AttributeArrayValue<BytesString>;
284
285pub type SpanSlice<'a> = Span<&'a str>;
286pub type SpanLinkSlice<'a> = SpanLink<&'a str>;
287pub type SpanEventSlice<'a> = SpanEvent<&'a str>;
288pub type AttributeAnyValueSlice<'a> = AttributeAnyValue<&'a str>;
289pub type AttributeArrayValueSlice<'a> = AttributeArrayValue<&'a str>;
290
291pub type TraceChunksBytes = TraceChunks<BytesString>;
292
293pub type SharedDictBytes = SharedDict<BytesString>;
294
295impl SpanSlice<'_> {
296    /// Converts a borrowed `SpanSlice` into an owned `SpanBytes`, by resolving all internal
297    /// references into slices of the provided `Bytes` buffer. Returns `None` if any slice is
298    /// out of bounds or invalid.
299    pub fn try_to_bytes(&self, bytes: &Bytes) -> Option<SpanBytes> {
300        Some(SpanBytes {
301            service: BytesString::try_from_bytes_slice(bytes, self.service)?,
302            name: BytesString::try_from_bytes_slice(bytes, self.name)?,
303            resource: BytesString::try_from_bytes_slice(bytes, self.resource)?,
304            r#type: BytesString::try_from_bytes_slice(bytes, self.r#type)?,
305            trace_id: self.trace_id,
306            span_id: self.span_id,
307            parent_id: self.parent_id,
308            start: self.start,
309            duration: self.duration,
310            error: self.error,
311            meta: self
312                .meta
313                .iter()
314                .map(|(k, v)| {
315                    Some((
316                        BytesString::try_from_bytes_slice(bytes, k)?,
317                        BytesString::try_from_bytes_slice(bytes, v)?,
318                    ))
319                })
320                .collect::<Option<HashMap<BytesString, BytesString>>>()?,
321            metrics: self
322                .metrics
323                .iter()
324                .map(|(k, v)| Some((BytesString::try_from_bytes_slice(bytes, k)?, *v)))
325                .collect::<Option<HashMap<BytesString, f64>>>()?,
326            meta_struct: self
327                .meta_struct
328                .iter()
329                .map(|(k, v)| Some((BytesString::try_from_bytes_slice(bytes, k)?, v.clone())))
330                .collect::<Option<HashMap<BytesString, Bytes>>>()?,
331            span_links: self
332                .span_links
333                .iter()
334                .map(|link| link.try_to_bytes(bytes))
335                .collect::<Option<Vec<SpanLinkBytes>>>()?,
336            span_events: self
337                .span_events
338                .iter()
339                .map(|event| event.try_to_bytes(bytes))
340                .collect::<Option<Vec<SpanEventBytes>>>()?,
341        })
342    }
343}
344
345impl SpanLinkSlice<'_> {
346    /// Converts a borrowed `SpanLinkSlice` into an owned `SpanLinkBytes`, using the provided
347    /// `Bytes` buffer to resolve all referenced strings. Returns `None` if conversion fails due
348    /// to invalid slice ranges.
349    pub fn try_to_bytes(&self, bytes: &Bytes) -> Option<SpanLinkBytes> {
350        Some(SpanLinkBytes {
351            trace_id: self.trace_id,
352            trace_id_high: self.trace_id_high,
353            span_id: self.span_id,
354            attributes: self
355                .attributes
356                .iter()
357                .map(|(k, v)| {
358                    Some((
359                        BytesString::try_from_bytes_slice(bytes, k)?,
360                        BytesString::try_from_bytes_slice(bytes, v)?,
361                    ))
362                })
363                .collect::<Option<HashMap<BytesString, BytesString>>>()?,
364            tracestate: BytesString::try_from_bytes_slice(bytes, self.tracestate)?,
365            flags: self.flags,
366        })
367    }
368}
369
370impl SpanEventSlice<'_> {
371    /// Converts a borrowed `SpanEventSlice` into an owned `SpanEventBytes`, resolving references
372    /// into the provided `Bytes` buffer. Fails with `None` if any slice is invalid or cannot be
373    /// converted.
374    pub fn try_to_bytes(&self, bytes: &Bytes) -> Option<SpanEventBytes> {
375        Some(SpanEventBytes {
376            time_unix_nano: self.time_unix_nano,
377            name: BytesString::try_from_bytes_slice(bytes, self.name)?,
378            attributes: self
379                .attributes
380                .iter()
381                .map(|(k, v)| {
382                    Some((
383                        BytesString::try_from_bytes_slice(bytes, k)?,
384                        v.try_to_bytes(bytes)?,
385                    ))
386                })
387                .collect::<Option<HashMap<BytesString, AttributeAnyValueBytes>>>()?,
388        })
389    }
390}
391
392impl AttributeAnyValueSlice<'_> {
393    /// Converts a borrowed `AttributeAnyValueSlice` into its owned `AttributeAnyValueBytes`
394    /// representation, using the provided `Bytes` buffer. Recursively processes inner values if
395    /// it's an array.
396    pub fn try_to_bytes(&self, bytes: &Bytes) -> Option<AttributeAnyValueBytes> {
397        match self {
398            AttributeAnyValue::SingleValue(value) => {
399                Some(AttributeAnyValue::SingleValue(value.try_to_bytes(bytes)?))
400            }
401            AttributeAnyValue::Array(value) => Some(AttributeAnyValue::Array(
402                value
403                    .iter()
404                    .map(|attribute| attribute.try_to_bytes(bytes))
405                    .collect::<Option<Vec<AttributeArrayValueBytes>>>()?,
406            )),
407        }
408    }
409}
410
411impl AttributeArrayValueSlice<'_> {
412    /// Converts a single `AttributeArrayValueSlice` item into its owned form
413    /// (`AttributeArrayValueBytes`), borrowing data from the provided `Bytes` buffer when
414    /// necessary.
415    pub fn try_to_bytes(&self, bytes: &Bytes) -> Option<AttributeArrayValueBytes> {
416        match self {
417            AttributeArrayValue::String(value) => Some(AttributeArrayValue::String(
418                BytesString::try_from_bytes_slice(bytes, value)?,
419            )),
420            AttributeArrayValue::Boolean(value) => Some(AttributeArrayValue::Boolean(*value)),
421            AttributeArrayValue::Integer(value) => Some(AttributeArrayValue::Integer(*value)),
422            AttributeArrayValue::Double(value) => Some(AttributeArrayValue::Double(*value)),
423        }
424    }
425}
426
427#[derive(Debug)]
428pub struct SpanKeyParseError {
429    pub message: String,
430}
431
432impl SpanKeyParseError {
433    pub fn new(message: impl Into<String>) -> Self {
434        SpanKeyParseError {
435            message: message.into(),
436        }
437    }
438}
439impl fmt::Display for SpanKeyParseError {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        write!(f, "SpanKeyParseError: {}", self.message)
442    }
443}
444impl std::error::Error for SpanKeyParseError {}
445
446fn is_default<T: Default + PartialEq>(t: &T) -> bool {
447    t == &T::default()
448}
449
450#[cfg(test)]
451mod tests {
452    use super::{AttributeAnyValue, AttributeArrayValue, Span, SpanEvent, SpanLink};
453    use crate::msgpack_decoder::v04::span::decode_span;
454    use std::collections::HashMap;
455
456    #[test]
457    fn skip_serializing_empty_fields_test() {
458        let expected = b"\x87\xa7service\xa0\xa4name\xa0\xa8resource\xa0\xa8trace_id\x00\xa7span_id\x00\xa5start\x00\xa8duration\x00";
459        let val: Span<&str> = Span::default();
460        let serialized = rmp_serde::encode::to_vec_named(&val).unwrap();
461        assert_eq!(expected, serialized.as_slice());
462    }
463
464    #[test]
465    fn serialize_deserialize_test() {
466        let span: Span<&str> = Span {
467            name: "tracing.operation",
468            resource: "MyEndpoint",
469            span_links: vec![SpanLink {
470                trace_id: 42,
471                attributes: HashMap::from([("span", "link")]),
472                tracestate: "running",
473                ..Default::default()
474            }],
475            span_events: vec![SpanEvent {
476                time_unix_nano: 1727211691770716000,
477                name: "exception",
478                attributes: HashMap::from([
479                    (
480                        "exception.message",
481                        AttributeAnyValue::SingleValue(AttributeArrayValue::String(
482                            "Cannot divide by zero",
483                        )),
484                    ),
485                    (
486                        "exception.type",
487                        AttributeAnyValue::SingleValue(AttributeArrayValue::String("RuntimeError")),
488                    ),
489                    (
490                        "exception.escaped",
491                        AttributeAnyValue::SingleValue(AttributeArrayValue::Boolean(false)),
492                    ),
493                    (
494                        "exception.count",
495                        AttributeAnyValue::SingleValue(AttributeArrayValue::Integer(1)),
496                    ),
497                    (
498                        "exception.lines",
499                        AttributeAnyValue::Array(vec![
500                            AttributeArrayValue::String("  File \"<string>\", line 1, in <module>"),
501                            AttributeArrayValue::String("  File \"<string>\", line 1, in divide"),
502                            AttributeArrayValue::String("RuntimeError: Cannot divide by zero"),
503                        ]),
504                    ),
505                ]),
506            }],
507            ..Default::default()
508        };
509
510        let serialized = rmp_serde::encode::to_vec_named(&span).unwrap();
511        let mut serialized_slice = serialized.as_ref();
512        let deserialized = decode_span(&mut serialized_slice).unwrap();
513
514        assert_eq!(span.name, deserialized.name);
515        assert_eq!(span.resource, deserialized.resource);
516        assert_eq!(
517            span.span_links[0].trace_id,
518            deserialized.span_links[0].trace_id
519        );
520        assert_eq!(
521            span.span_links[0].tracestate,
522            deserialized.span_links[0].tracestate
523        );
524        assert_eq!(span.span_events[0].name, deserialized.span_events[0].name);
525        assert_eq!(
526            span.span_events[0].time_unix_nano,
527            deserialized.span_events[0].time_unix_nano
528        );
529        for attribut in &deserialized.span_events[0].attributes {
530            assert!(span.span_events[0].attributes.contains_key(attribut.0))
531        }
532    }
533
534    #[test]
535    fn serialize_event_test() {
536        // `expected` is created by transforming the span into bytes
537        // and passing each bytes through `escaped_default`
538        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";
539
540        let span: Span<&str> = Span {
541            span_events: vec![SpanEvent {
542                time_unix_nano: 1727211691770716000,
543                name: "test",
544                attributes: HashMap::from([(
545                    "test.event",
546                    AttributeAnyValue::SingleValue(AttributeArrayValue::Double(4.2)),
547                )]),
548            }],
549            ..Default::default()
550        };
551
552        let serialized = rmp_serde::encode::to_vec_named(&span).unwrap();
553        assert_eq!(expected, serialized.as_slice());
554    }
555}