From 27a2f1e62ccc55446f003458dbd982d861860233 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Wed, 29 Jul 2026 18:36:15 +0200 Subject: [PATCH 1/8] feat(data-pipeline): add batched span links FFI Expose a C representation and batched setter for complete span links. The setter copies link IDs, attributes, trace state, and flags while preserving caller order, so native tracers can populate links in one call without retaining input storage. Convert every link before replacing the span's current links. Invalid slices or UTF-8 therefore return an error without partially updating the span. --- libdd-data-pipeline-ffi/cbindgen.toml | 2 + libdd-data-pipeline-ffi/src/tracer.rs | 254 +++++++++++++++++++++++++- 2 files changed, 254 insertions(+), 2 deletions(-) diff --git a/libdd-data-pipeline-ffi/cbindgen.toml b/libdd-data-pipeline-ffi/cbindgen.toml index d3e36b4945..965904f5ff 100644 --- a/libdd-data-pipeline-ffi/cbindgen.toml +++ b/libdd-data-pipeline-ffi/cbindgen.toml @@ -32,6 +32,8 @@ exclude = ["TraceExporter", "TracerSpan", "TracerTraceChunks", "TokioCancellatio "ExporterError" = "ddog_TraceExporterError" "TracerSpan" = "ddog_TracerSpan" "TracerSpanFields" = "ddog_TracerSpanFields" +"TracerSpanLink" = "ddog_TracerSpanLink" +"TracerSpanLinkAttribute" = "ddog_TracerSpanLinkAttribute" "TracerTraceChunks" = "ddog_TracerTraceChunks" "TokioCancellationToken" = "ddog_TraceExporterCancelToken" diff --git a/libdd-data-pipeline-ffi/src/tracer.rs b/libdd-data-pipeline-ffi/src/tracer.rs index 9489527ad6..6e8ae680df 100644 --- a/libdd-data-pipeline-ffi/src/tracer.rs +++ b/libdd-data-pipeline-ffi/src/tracer.rs @@ -14,9 +14,10 @@ use crate::response::ExporterResponse; use crate::trace_exporter::TraceExporter; use crate::{catch_panic, gen_error}; use libdd_common_ffi::slice::AsBytes; -use libdd_common_ffi::CharSlice; +use libdd_common_ffi::{CharSlice, Slice}; use libdd_tinybytes::BytesString; -use libdd_trace_utils::span::v04::SpanBytes; +use libdd_trace_utils::span::v04::{SpanBytes, SpanLinkBytes}; +use std::collections::HashMap; use std::ptr::NonNull; type TokioCancellationToken = tokio_util::sync::CancellationToken; @@ -65,6 +66,26 @@ pub struct TracerSpanFields<'a> { pub error: i32, } +/// A string attribute belonging to a [`TracerSpanLink`]. +#[derive(Debug)] +#[repr(C)] +pub struct TracerSpanLinkAttribute<'a> { + pub key: CharSlice<'a>, + pub value: CharSlice<'a>, +} + +/// FFI-safe representation of one complete span link. +#[derive(Debug)] +#[repr(C)] +pub struct TracerSpanLink<'a> { + pub trace_id_low: u64, + pub trace_id_high: u64, + pub span_id: u64, + pub attributes: Slice<'a, TracerSpanLinkAttribute<'a>>, + pub tracestate: CharSlice<'a>, + pub flags: u32, +} + /// Create a new span with all scalar fields set. /// /// String fields are copied from the provided slices. The `meta` and @@ -192,6 +213,70 @@ pub unsafe extern "C" fn ddog_tracer_span_set_metric( ) } +/// Replace all span links in one atomic operation. +/// +/// The links, attributes, and strings are copied before this function returns. +/// If any slice or string is invalid, the span's existing links are unchanged. +/// Link order is preserved. +/// +/// # Safety +/// +/// `handle` must be a valid pointer to a `TracerSpan`. All slices must point to +/// valid memory for their stated lengths. +#[no_mangle] +pub unsafe extern "C" fn ddog_tracer_span_set_links( + handle: Option<&mut TracerSpan>, + links: Slice, +) -> Option> { + catch_panic!( + if let Some(span) = handle { + let links = match links.try_as_slice() { + Ok(links) => links, + Err(_) => return gen_error!(ErrorCode::InvalidInput), + }; + let mut converted = Vec::with_capacity(links.len()); + + for link in links { + let attributes = match link.attributes.try_as_slice() { + Ok(attributes) => attributes, + Err(_) => return gen_error!(ErrorCode::InvalidInput), + }; + let mut converted_attributes = HashMap::with_capacity(attributes.len()); + for attribute in attributes { + let key = match charslice_to_bytesstring(attribute.key) { + Ok(key) => key, + Err(err) => return Some(err), + }; + let value = match charslice_to_bytesstring(attribute.value) { + Ok(value) => value, + Err(err) => return Some(err), + }; + converted_attributes.insert(key, value); + } + + let tracestate = match charslice_to_bytesstring(link.tracestate) { + Ok(tracestate) => tracestate, + Err(err) => return Some(err), + }; + converted.push(SpanLinkBytes { + trace_id: link.trace_id_low, + trace_id_high: link.trace_id_high, + span_id: link.span_id, + attributes: converted_attributes, + tracestate, + flags: link.flags, + }); + } + + span.0.span_links = converted; + None + } else { + gen_error!(ErrorCode::InvalidArgument) + }, + gen_error!(ErrorCode::Panic) + ) +} + // --------------------------------------------------------------------------- // TracerTraceChunks // --------------------------------------------------------------------------- @@ -527,6 +612,171 @@ mod tests { } } + #[test] + fn set_links_copies_complete_links_in_order() { + unsafe { + let mut span = make_minimal_span(); + let first_attributes = [TracerSpanLinkAttribute { + key: cs("messaging.operation"), + value: cs("receive"), + }]; + let links = [ + TracerSpanLink { + trace_id_low: 0x0123, + trace_id_high: 0x4567, + span_id: 0x89ab, + attributes: Slice::from(&first_attributes[..]), + tracestate: cs("vendor=value"), + flags: 0x8000_0001, + }, + TracerSpanLink { + trace_id_low: 2, + trace_id_high: 0, + span_id: 3, + attributes: Slice::default(), + tracestate: cs(""), + flags: 0, + }, + ]; + + let err = ddog_tracer_span_set_links(Some(&mut span), Slice::from(&links[..])); + assert!(err.is_none()); + + assert_eq!(span.0.span_links.len(), 2); + assert_eq!(span.0.span_links[0].trace_id, 0x0123); + assert_eq!(span.0.span_links[0].trace_id_high, 0x4567); + assert_eq!(span.0.span_links[0].span_id, 0x89ab); + assert_eq!(span.0.span_links[0].flags, 0x8000_0001); + assert_eq!(span.0.span_links[0].tracestate.as_ref(), "vendor=value"); + assert_eq!( + span.0.span_links[0] + .attributes + .get("messaging.operation") + .unwrap() + .as_ref(), + "receive" + ); + assert_eq!(span.0.span_links[1].trace_id, 2); + + ddog_tracer_span_free(span); + } + } + + #[test] + fn set_links_replaces_existing_links() { + unsafe { + let mut span = make_minimal_span(); + let first = [TracerSpanLink { + trace_id_low: 1, + trace_id_high: 0, + span_id: 2, + attributes: Slice::default(), + tracestate: cs(""), + flags: 0, + }]; + assert!(ddog_tracer_span_set_links(Some(&mut span), Slice::from(&first[..])).is_none()); + + let second = [TracerSpanLink { + trace_id_low: 3, + trace_id_high: 4, + span_id: 5, + attributes: Slice::default(), + tracestate: cs("state=value"), + flags: 6, + }]; + assert!( + ddog_tracer_span_set_links(Some(&mut span), Slice::from(&second[..])).is_none() + ); + + assert_eq!(span.0.span_links.len(), 1); + assert_eq!(span.0.span_links[0].trace_id, 3); + assert_eq!(span.0.span_links[0].trace_id_high, 4); + + ddog_tracer_span_free(span); + } + } + + #[test] + fn set_links_failure_is_atomic() { + unsafe { + let mut span = make_minimal_span(); + span.0.span_links.push(SpanLinkBytes { + trace_id: 7, + span_id: 8, + ..Default::default() + }); + let invalid = [0xff]; + let links = [ + TracerSpanLink { + trace_id_low: 1, + trace_id_high: 2, + span_id: 3, + attributes: Slice::default(), + tracestate: cs("valid=value"), + flags: 4, + }, + TracerSpanLink { + trace_id_low: 5, + trace_id_high: 6, + span_id: 7, + attributes: Slice::default(), + tracestate: CharSlice::from_bytes(&invalid), + flags: 8, + }, + ]; + + let err = ddog_tracer_span_set_links(Some(&mut span), Slice::from(&links[..])); + assert!(err.is_some()); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidInput); + ddog_trace_exporter_error_free(err); + assert_eq!(span.0.span_links.len(), 1); + assert_eq!(span.0.span_links[0].trace_id, 7); + + ddog_tracer_span_free(span); + } + } + + #[test] + fn set_links_rejects_invalid_attribute_utf8_atomically() { + unsafe { + let mut span = make_minimal_span(); + span.0.span_links.push(SpanLinkBytes { + trace_id: 7, + ..Default::default() + }); + let invalid = [0xff]; + let attributes = [TracerSpanLinkAttribute { + key: cs("key"), + value: CharSlice::from_bytes(&invalid), + }]; + let links = [TracerSpanLink { + trace_id_low: 1, + trace_id_high: 2, + span_id: 3, + attributes: Slice::from(&attributes[..]), + tracestate: cs(""), + flags: 4, + }]; + + let err = ddog_tracer_span_set_links(Some(&mut span), Slice::from(&links[..])); + assert!(err.is_some()); + ddog_trace_exporter_error_free(err); + assert_eq!(span.0.span_links.len(), 1); + assert_eq!(span.0.span_links[0].trace_id, 7); + + ddog_tracer_span_free(span); + } + } + + #[test] + fn set_links_null_handle_returns_error() { + unsafe { + let err = ddog_tracer_span_set_links(None, Slice::default()); + assert!(err.is_some()); + ddog_trace_exporter_error_free(err); + } + } + #[test] fn set_meta_null_handle_returns_error() { unsafe { From c0f8e8dbe53527a576763f43e44f4f278f6b9de3 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Wed, 29 Jul 2026 20:17:37 +0200 Subject: [PATCH 2/8] feat(trace): preserve dropped span link counts Carry `dropped_attributes_count` through span link models, the C FFI, and every supported decoder and encoder. This prevents tracers from losing the number of link attributes discarded before export. Emit non-zero counts in JSON, MessagePack, and OTLP output while keeping the field absent where optional encodings previously omitted zero values. --- libdd-data-pipeline-ffi/src/tracer.rs | 10 +++++ .../src/agentless_encoder/tests.rs | 1 + libdd-trace-utils/src/json_log_encoder/mod.rs | 2 + .../src/json_log_encoder/span.rs | 5 +++ .../src/msgpack_decoder/decode/span_link.rs | 9 +++++ .../src/msgpack_decoder/v1/mod.rs | 1 + .../src/msgpack_decoder/v1/span.rs | 12 ++++++ .../src/msgpack_encoder/v04/span_v04.rs | 40 +++++++++++++++++++ .../src/msgpack_encoder/v04/span_v1.rs | 11 +++++ .../src/msgpack_encoder/v1/mod.rs | 1 + .../src/msgpack_encoder/v1/span_v04.rs | 6 +++ .../src/msgpack_encoder/v1/span_v1.rs | 6 +++ libdd-trace-utils/src/otlp_encoder/mapper.rs | 4 +- libdd-trace-utils/src/span/v04/mod.rs | 8 ++++ libdd-trace-utils/src/span/v05/mod.rs | 16 ++++++-- libdd-trace-utils/src/span/v1/mod.rs | 1 + libdd-trace-utils/tests/test_send_data.rs | 2 + 17 files changed, 131 insertions(+), 4 deletions(-) diff --git a/libdd-data-pipeline-ffi/src/tracer.rs b/libdd-data-pipeline-ffi/src/tracer.rs index 6e8ae680df..9caae2685f 100644 --- a/libdd-data-pipeline-ffi/src/tracer.rs +++ b/libdd-data-pipeline-ffi/src/tracer.rs @@ -82,6 +82,7 @@ pub struct TracerSpanLink<'a> { pub trace_id_high: u64, pub span_id: u64, pub attributes: Slice<'a, TracerSpanLinkAttribute<'a>>, + pub dropped_attributes_count: u32, pub tracestate: CharSlice<'a>, pub flags: u32, } @@ -263,6 +264,7 @@ pub unsafe extern "C" fn ddog_tracer_span_set_links( trace_id_high: link.trace_id_high, span_id: link.span_id, attributes: converted_attributes, + dropped_attributes_count: link.dropped_attributes_count, tracestate, flags: link.flags, }); @@ -626,6 +628,7 @@ mod tests { trace_id_high: 0x4567, span_id: 0x89ab, attributes: Slice::from(&first_attributes[..]), + dropped_attributes_count: 7, tracestate: cs("vendor=value"), flags: 0x8000_0001, }, @@ -634,6 +637,7 @@ mod tests { trace_id_high: 0, span_id: 3, attributes: Slice::default(), + dropped_attributes_count: 0, tracestate: cs(""), flags: 0, }, @@ -646,6 +650,7 @@ mod tests { assert_eq!(span.0.span_links[0].trace_id, 0x0123); assert_eq!(span.0.span_links[0].trace_id_high, 0x4567); assert_eq!(span.0.span_links[0].span_id, 0x89ab); + assert_eq!(span.0.span_links[0].dropped_attributes_count, 7); assert_eq!(span.0.span_links[0].flags, 0x8000_0001); assert_eq!(span.0.span_links[0].tracestate.as_ref(), "vendor=value"); assert_eq!( @@ -671,6 +676,7 @@ mod tests { trace_id_high: 0, span_id: 2, attributes: Slice::default(), + dropped_attributes_count: 0, tracestate: cs(""), flags: 0, }]; @@ -681,6 +687,7 @@ mod tests { trace_id_high: 4, span_id: 5, attributes: Slice::default(), + dropped_attributes_count: 0, tracestate: cs("state=value"), flags: 6, }]; @@ -712,6 +719,7 @@ mod tests { trace_id_high: 2, span_id: 3, attributes: Slice::default(), + dropped_attributes_count: 0, tracestate: cs("valid=value"), flags: 4, }, @@ -720,6 +728,7 @@ mod tests { trace_id_high: 6, span_id: 7, attributes: Slice::default(), + dropped_attributes_count: 0, tracestate: CharSlice::from_bytes(&invalid), flags: 8, }, @@ -754,6 +763,7 @@ mod tests { trace_id_high: 2, span_id: 3, attributes: Slice::from(&attributes[..]), + dropped_attributes_count: 0, tracestate: cs(""), flags: 4, }]; diff --git a/libdd-trace-utils/src/agentless_encoder/tests.rs b/libdd-trace-utils/src/agentless_encoder/tests.rs index 57554c250c..ca92d092ed 100644 --- a/libdd-trace-utils/src/agentless_encoder/tests.rs +++ b/libdd-trace-utils/src/agentless_encoder/tests.rs @@ -141,6 +141,7 @@ fn span_links_serialised_into_meta_as_json_string() { trace_id_high: 0x0011_2233_4455_6677, span_id: 0xfeed_face_dead_beef, attributes: HashMap::from([(bs("link.name"), bs("scheduled_by"))]), + dropped_attributes_count: 0, flags: 1, tracestate: bs("dd=s:1"), }; diff --git a/libdd-trace-utils/src/json_log_encoder/mod.rs b/libdd-trace-utils/src/json_log_encoder/mod.rs index f98f47231a..6d62827a2c 100644 --- a/libdd-trace-utils/src/json_log_encoder/mod.rs +++ b/libdd-trace-utils/src/json_log_encoder/mod.rs @@ -421,6 +421,7 @@ mod tests { span_links: vec![SpanLink { trace_id: 7, span_id: 8, + dropped_attributes_count: 3, ..Default::default() }], span_events: vec![SpanEvent { @@ -439,6 +440,7 @@ mod tests { // Lock the inner wire shape (field names/values), not just presence. assert_eq!(span_json["span_links"][0]["trace_id"], 7); assert_eq!(span_json["span_links"][0]["span_id"], 8); + assert_eq!(span_json["span_links"][0]["dropped_attributes_count"], 3); assert_eq!(span_json["span_events"][0]["name"], "evt"); assert_eq!(span_json["span_events"][0]["time_unix_nano"], 123); } diff --git a/libdd-trace-utils/src/json_log_encoder/span.rs b/libdd-trace-utils/src/json_log_encoder/span.rs index c39363ed95..49e26bcafc 100644 --- a/libdd-trace-utils/src/json_log_encoder/span.rs +++ b/libdd-trace-utils/src/json_log_encoder/span.rs @@ -82,12 +82,14 @@ impl Serialize for LogSpanLink<'_, T> { fn serialize(&self, serializer: S) -> Result { let link = self.0; let has_attributes = !link.attributes.is_empty(); + let has_dropped_attributes = link.dropped_attributes_count != 0; let has_tracestate = !Borrow::::borrow(&link.tracestate).is_empty(); let has_flags = link.flags != 0; // Always: trace_id, trace_id_high, span_id. let mut len = 3; len += has_attributes as usize; + len += has_dropped_attributes as usize; len += has_tracestate as usize; len += has_flags as usize; @@ -98,6 +100,9 @@ impl Serialize for LogSpanLink<'_, T> { if has_attributes { state.serialize_field("attributes", &link.attributes)?; } + if has_dropped_attributes { + state.serialize_field("dropped_attributes_count", &link.dropped_attributes_count)?; + } if has_tracestate { state.serialize_field("tracestate", &link.tracestate)?; } diff --git a/libdd-trace-utils/src/msgpack_decoder/decode/span_link.rs b/libdd-trace-utils/src/msgpack_decoder/decode/span_link.rs index af32dc3ff9..6deafa507e 100644 --- a/libdd-trace-utils/src/msgpack_decoder/decode/span_link.rs +++ b/libdd-trace-utils/src/msgpack_decoder/decode/span_link.rs @@ -50,6 +50,7 @@ enum SpanLinkKey { TraceIdHigh, SpanId, Attributes, + DroppedAttributesCount, Tracestate, Flags, } @@ -63,6 +64,7 @@ impl FromStr for SpanLinkKey { "trace_id_high" => Ok(SpanLinkKey::TraceIdHigh), "span_id" => Ok(SpanLinkKey::SpanId), "attributes" => Ok(SpanLinkKey::Attributes), + "dropped_attributes_count" => Ok(SpanLinkKey::DroppedAttributesCount), "tracestate" => Ok(SpanLinkKey::Tracestate), "flags" => Ok(SpanLinkKey::Flags), _ => Err(DecodeError::InvalidFormat( @@ -85,6 +87,9 @@ fn decode_span_link( SpanLinkKey::TraceIdHigh => span.trace_id_high = read_number(buf)?, SpanLinkKey::SpanId => span.span_id = read_number(buf)?, SpanLinkKey::Attributes => span.attributes = read_str_map_to_hashmap(buf)?, + SpanLinkKey::DroppedAttributesCount => { + span.dropped_attributes_count = read_number(buf)? + } SpanLinkKey::Tracestate => span.tracestate = buf.read_string()?, SpanLinkKey::Flags => span.flags = read_number(buf)?, } @@ -118,6 +123,10 @@ mod tests { SpanLinkKey::from_str("attributes").unwrap(), SpanLinkKey::Attributes ); + assert_eq!( + SpanLinkKey::from_str("dropped_attributes_count").unwrap(), + SpanLinkKey::DroppedAttributesCount + ); assert_eq!( SpanLinkKey::from_str("tracestate").unwrap(), SpanLinkKey::Tracestate diff --git a/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs b/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs index b7ee7c45d7..a877f85bab 100644 --- a/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs +++ b/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs @@ -61,6 +61,7 @@ pub(super) mod span_link_key { pub const ATTRIBUTES: u8 = 3; pub const TRACE_STATE: u8 = 4; pub const FLAGS: u8 = 5; + pub const DROPPED_ATTRIBUTES_COUNT: u8 = 6; } pub(super) mod span_event_key { diff --git a/libdd-trace-utils/src/msgpack_decoder/v1/span.rs b/libdd-trace-utils/src/msgpack_decoder/v1/span.rs index 62fd2837bc..37dd17ec58 100644 --- a/libdd-trace-utils/src/msgpack_decoder/v1/span.rs +++ b/libdd-trace-utils/src/msgpack_decoder/v1/span.rs @@ -276,6 +276,18 @@ where DecodeError::InvalidFormat(format!("V1 span_link flags {v} exceeds u32::MAX")) })?; } + span_link_key::DROPPED_ATTRIBUTES_COUNT => { + let v: u64 = decode::read_int(buf.as_mut_slice()).map_err(|_| { + DecodeError::InvalidFormat( + "V1 span_link dropped_attributes_count read failure".to_owned(), + ) + })?; + link.dropped_attributes_count = u32::try_from(v).map_err(|_| { + DecodeError::InvalidFormat(format!( + "V1 span_link dropped_attributes_count {v} exceeds u32::MAX" + )) + })?; + } _unknown => skip_unknown_value(buf)?, } } diff --git a/libdd-trace-utils/src/msgpack_encoder/v04/span_v04.rs b/libdd-trace-utils/src/msgpack_encoder/v04/span_v04.rs index 9d079f3649..48890bb930 100644 --- a/libdd-trace-utils/src/msgpack_encoder/v04/span_v04.rs +++ b/libdd-trace-utils/src/msgpack_encoder/v04/span_v04.rs @@ -37,6 +37,7 @@ pub fn encode_span_links( for link in span_links.iter() { let link_len = 3 /* minimal span link: trace_id, trace_id_high, span_id */ + (!link.attributes.is_empty()) as u32 + + (link.dropped_attributes_count != 0) as u32 + (!link.tracestate.borrow().is_empty()) as u32 + (link.flags != 0) as u32; @@ -60,6 +61,11 @@ pub fn encode_span_links( } } + if link.dropped_attributes_count != 0 { + write_const_msgpack_str!(writer, "dropped_attributes_count")?; + write_u32(writer, link.dropped_attributes_count)?; + } + if !link.tracestate.borrow().is_empty() { write_const_msgpack_str!(writer, "tracestate")?; write_str(writer, link.tracestate.borrow())?; @@ -281,3 +287,37 @@ pub fn encode_span( Ok(()) } + +#[cfg(test)] +mod tests { + use super::encode_span; + use crate::span::v04::{SpanBytes, SpanLinkBytes}; + use rmpv::Value; + use std::io::Cursor; + + #[test] + fn span_link_encodes_dropped_attributes_count() { + let span = SpanBytes { + span_links: vec![SpanLinkBytes { + dropped_attributes_count: 7, + ..Default::default() + }], + ..Default::default() + }; + let mut encoded = Vec::new(); + encode_span(&mut encoded, &span).unwrap(); + let decoded = rmpv::decode::read_value(&mut Cursor::new(encoded)).unwrap(); + let links = decoded + .as_map() + .unwrap() + .iter() + .find(|(key, _)| key.as_str() == Some("span_links")) + .unwrap() + .1 + .as_array() + .unwrap(); + let link = links[0].as_map().unwrap(); + + assert!(link.contains(&(Value::from("dropped_attributes_count"), Value::from(7),))); + } +} diff --git a/libdd-trace-utils/src/msgpack_encoder/v04/span_v1.rs b/libdd-trace-utils/src/msgpack_encoder/v04/span_v1.rs index 991e6eaebb..8980bbf9c4 100644 --- a/libdd-trace-utils/src/msgpack_encoder/v04/span_v1.rs +++ b/libdd-trace-utils/src/msgpack_encoder/v04/span_v1.rs @@ -466,6 +466,7 @@ fn encode_span_links( let link_len = 3 // trace_id, trace_id_high, span_id (always) + (attr_count > 0) as u32 + + (link.dropped_attributes_count != 0) as u32 + (!link.tracestate.borrow().is_empty()) as u32 + (link.flags != 0) as u32; @@ -498,6 +499,11 @@ fn encode_span_links( } } + if link.dropped_attributes_count != 0 { + write_const_msgpack_str!(writer, "dropped_attributes_count")?; + write_u32(writer, link.dropped_attributes_count)?; + } + if !link.tracestate.borrow().is_empty() { write_const_msgpack_str!(writer, "tracestate")?; write_str(writer, link.tracestate.borrow())?; @@ -1308,6 +1314,7 @@ mod tests { trace_id: link_tid, span_id: 7, attributes: link_attrs, + dropped_attributes_count: 9, tracestate: bs("dd=t.dm:-1"), flags: 3, }]), @@ -1335,6 +1342,10 @@ mod tests { Some("dd=t.dm:-1") ); assert_eq!(map_get(link, "flags").unwrap().as_u64(), Some(3)); + assert_eq!( + map_get(link, "dropped_attributes_count").unwrap().as_u64(), + Some(9) + ); let attrs = map_get(link, "attributes").expect("string attrs preserved"); assert_eq!( diff --git a/libdd-trace-utils/src/msgpack_encoder/v1/mod.rs b/libdd-trace-utils/src/msgpack_encoder/v1/mod.rs index d64f80e28d..bddde3b266 100644 --- a/libdd-trace-utils/src/msgpack_encoder/v1/mod.rs +++ b/libdd-trace-utils/src/msgpack_encoder/v1/mod.rs @@ -72,6 +72,7 @@ pub(super) enum SpanLinkKey { Attributes = 3, TraceState = 4, Flags = 5, + DroppedAttributesCount = 6, } /// Integer keys for V1 span event fields. diff --git a/libdd-trace-utils/src/msgpack_encoder/v1/span_v04.rs b/libdd-trace-utils/src/msgpack_encoder/v1/span_v04.rs index a1b580101c..7ad1e5e227 100644 --- a/libdd-trace-utils/src/msgpack_encoder/v1/span_v04.rs +++ b/libdd-trace-utils/src/msgpack_encoder/v1/span_v04.rs @@ -45,6 +45,7 @@ pub fn encode_span_links( let link_len = 1 // trace_id (always) + (link.span_id != 0) as u32 + (!link.attributes.is_empty()) as u32 + + (link.dropped_attributes_count != 0) as u32 + (!link.tracestate.borrow().is_empty()) as u32 + (link.flags != 0) as u32; @@ -68,6 +69,11 @@ pub fn encode_span_links( } } + if link.dropped_attributes_count != 0 { + write_uint8(writer, SpanLinkKey::DroppedAttributesCount as u8)?; + write_uint(writer, link.dropped_attributes_count as u64)?; + } + if !link.tracestate.borrow().is_empty() { write_uint8(writer, SpanLinkKey::TraceState as u8)?; table.write_interned(writer, link.tracestate.borrow())?; diff --git a/libdd-trace-utils/src/msgpack_encoder/v1/span_v1.rs b/libdd-trace-utils/src/msgpack_encoder/v1/span_v1.rs index e63a68b212..0e127557d2 100644 --- a/libdd-trace-utils/src/msgpack_encoder/v1/span_v1.rs +++ b/libdd-trace-utils/src/msgpack_encoder/v1/span_v1.rs @@ -140,6 +140,7 @@ pub(super) fn encode_span_links( let link_len = 1 // trace_id (always) + (link.span_id != 0) as u32 + (!link.attributes.is_empty()) as u32 + + (link.dropped_attributes_count != 0) as u32 + (!link.tracestate.borrow().is_empty()) as u32 + (link.flags != 0) as u32; @@ -158,6 +159,11 @@ pub(super) fn encode_span_links( encode_attributes_map(writer, &link.attributes, table)?; } + if link.dropped_attributes_count != 0 { + write_uint8(writer, SpanLinkKey::DroppedAttributesCount as u8)?; + write_uint(writer, link.dropped_attributes_count as u64)?; + } + if !link.tracestate.borrow().is_empty() { write_uint8(writer, SpanLinkKey::TraceState as u8)?; table.write_interned(writer, link.tracestate.borrow())?; diff --git a/libdd-trace-utils/src/otlp_encoder/mapper.rs b/libdd-trace-utils/src/otlp_encoder/mapper.rs index bc3a94c65c..eb3f987a53 100644 --- a/libdd-trace-utils/src/otlp_encoder/mapper.rs +++ b/libdd-trace-utils/src/otlp_encoder/mapper.rs @@ -467,7 +467,7 @@ fn map_span_link(link: &SpanLink) -> ProtoLink { ) }) .collect(), - dropped_attributes_count: 0, + dropped_attributes_count: link.dropped_attributes_count, // W3C trace flags of the linked context (sampled bit, etc.); carry them through so OTLP // consumers see the same link metadata the tracer recorded. flags: link.flags, @@ -994,6 +994,7 @@ mod tests { span.span_links.push(SpanLink { trace_id: 0x11, span_id: 0x22, + dropped_attributes_count: 7, flags: 1, ..Default::default() }); @@ -1003,6 +1004,7 @@ mod tests { link.flags, 1, "OTLP Link.flags must carry the span link's flags" ); + assert_eq!(link.dropped_attributes_count, 7); } #[test] diff --git a/libdd-trace-utils/src/span/v04/mod.rs b/libdd-trace-utils/src/span/v04/mod.rs index ba6079dca5..48992f6466 100644 --- a/libdd-trace-utils/src/span/v04/mod.rs +++ b/libdd-trace-utils/src/span/v04/mod.rs @@ -143,6 +143,8 @@ pub struct SpanLink { pub span_id: u64, #[serde(skip_serializing_if = "HashMap::is_empty")] pub attributes: HashMap, + #[serde(skip_serializing_if = "is_default")] + pub dropped_attributes_count: u32, #[serde(skip_serializing_if = "is_empty_str")] pub tracestate: T::Text, #[serde(skip_serializing_if = "is_default")] @@ -159,6 +161,7 @@ where trace_id_high: self.trace_id_high, span_id: self.span_id, attributes: self.attributes.clone(), + dropped_attributes_count: self.dropped_attributes_count, tracestate: self.tracestate.clone(), flags: self.flags, } @@ -357,6 +360,7 @@ mod tests { span_links: vec![SpanLink { trace_id: 42, attributes: HashMap::from([("span", "link")]), + dropped_attributes_count: 7, tracestate: "running", ..Default::default() }], @@ -409,6 +413,10 @@ mod tests { span.span_links[0].tracestate, deserialized.span_links[0].tracestate ); + assert_eq!( + span.span_links[0].dropped_attributes_count, + deserialized.span_links[0].dropped_attributes_count + ); assert_eq!(span.span_events[0].name, deserialized.span_events[0].name); assert_eq!( span.span_events[0].time_unix_nano, diff --git a/libdd-trace-utils/src/span/v05/mod.rs b/libdd-trace-utils/src/span/v05/mod.rs index 849a082a30..97272b9c5b 100644 --- a/libdd-trace-utils/src/span/v05/mod.rs +++ b/libdd-trace-utils/src/span/v05/mod.rs @@ -43,7 +43,7 @@ pub struct Span { /// low 64 bits). /// - `span_id` is the 64-bit id hex-encoded as 16 lowercase chars. /// - `tracestate` and `attributes` are only emitted when non-empty. -/// - `flags` is only emitted when not zero. +/// - `dropped_attributes_count` and `flags` are only emitted when not zero. struct SpanLinksSerializerV05<'a, T: TraceData>(&'a [SpanLink]); struct SpanLinkSerializerV05<'a, T: TraceData>(&'a SpanLink); @@ -63,8 +63,13 @@ impl<'a, T: TraceData> Serialize for SpanLinkSerializerV05<'a, T> { let tracestate: &str = link.tracestate.borrow(); let has_tracestate = !tracestate.is_empty(); let has_attributes = !link.attributes.is_empty(); + let has_dropped_attributes = link.dropped_attributes_count != 0; let has_flags = link.flags != 0; - let len = 2 + has_tracestate as usize + has_attributes as usize + has_flags as usize; + let len = 2 + + has_tracestate as usize + + has_attributes as usize + + has_dropped_attributes as usize + + has_flags as usize; let mut map = serializer.serialize_map(Some(len))?; map.serialize_entry( "trace_id", @@ -80,6 +85,9 @@ impl<'a, T: TraceData> Serialize for SpanLinkSerializerV05<'a, T> { &SortedStrMapSerializerV05::(&link.attributes), )?; } + if has_dropped_attributes { + map.serialize_entry("dropped_attributes_count", &link.dropped_attributes_count)?; + } if has_flags { map.serialize_entry("flags", &link.flags)?; } @@ -405,6 +413,7 @@ mod tests { trace_id_high: 67890, span_id: 54321, attributes: HashMap::from([(BytesString::from("key"), BytesString::from("val"))]), + dropped_attributes_count: 7, tracestate: BytesString::from("tracestate_value"), flags: 1, }]; @@ -425,7 +434,7 @@ mod tests { let links_json = meta_json(&dict, &v05_span, "_dd.span_links").unwrap(); assert_eq!( links_json, - "[{\"trace_id\":\"00000000000109320000000000003039\",\"span_id\":\"000000000000d431\",\"tracestate\":\"tracestate_value\",\"attributes\":{\"key\":\"val\"},\"flags\":1}]" + "[{\"trace_id\":\"00000000000109320000000000003039\",\"span_id\":\"000000000000d431\",\"tracestate\":\"tracestate_value\",\"attributes\":{\"key\":\"val\"},\"dropped_attributes_count\":7,\"flags\":1}]" ); let events_json = meta_json(&dict, &v05_span, "events").unwrap(); assert_eq!( @@ -474,6 +483,7 @@ mod tests { trace_id_high: 0, span_id: 0xfeed, attributes: HashMap::new(), + dropped_attributes_count: 0, tracestate: BytesString::from(""), flags: 7, }]; diff --git a/libdd-trace-utils/src/span/v1/mod.rs b/libdd-trace-utils/src/span/v1/mod.rs index 575447c31c..65c9095f52 100644 --- a/libdd-trace-utils/src/span/v1/mod.rs +++ b/libdd-trace-utils/src/span/v1/mod.rs @@ -115,6 +115,7 @@ pub struct SpanLink { pub trace_id: [u8; 16], pub span_id: u64, pub attributes: VecMap>, + pub dropped_attributes_count: u32, pub tracestate: T::Text, pub flags: u32, } diff --git a/libdd-trace-utils/tests/test_send_data.rs b/libdd-trace-utils/tests/test_send_data.rs index 3075362a53..03f836c45f 100644 --- a/libdd-trace-utils/tests/test_send_data.rs +++ b/libdd-trace-utils/tests/test_send_data.rs @@ -463,6 +463,7 @@ mod tracing_integration_tests { let span_link = SpanLinkBytes { trace_id: tid_bytes(0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210), span_id: 0xa0a0_a0a0_a0a0_a0a0, + dropped_attributes_count: 0, tracestate: bs_v1("dd=t.tid:abc"), flags: 1, attributes: VecMap::new(), @@ -658,6 +659,7 @@ mod tracing_integration_tests { let span_link = SpanLinkBytes { trace_id: tid_bytes(0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210), span_id: 0xa0a0_a0a0_a0a0_a0a0, + dropped_attributes_count: 0, tracestate: bs_v1("dd=t.tid:abc"), flags: 1, attributes: link_attrs, From 961044ea58de70075efe60808e88a89ef10ebad6 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Tue, 4 Aug 2026 09:51:37 +0200 Subject: [PATCH 3/8] fix(trace): defer dropped span link counts Remove uncoordinated `dropped_attributes_count` support from the native span-link models and encoders. The Agent v0.4 and indexed V1 schemas do not define this field, while current tracers do not produce non-zero counts. This is the exact inverse of `c0f8e8dbe535`, retained as an additive commit so it can be reverted if the protocols formally add the field. Reverts: c0f8e8dbe53527a576763f43e44f4f278f6b9de3 --- libdd-data-pipeline-ffi/src/tracer.rs | 10 ----- .../src/agentless_encoder/tests.rs | 1 - libdd-trace-utils/src/json_log_encoder/mod.rs | 2 - .../src/json_log_encoder/span.rs | 5 --- .../src/msgpack_decoder/decode/span_link.rs | 9 ----- .../src/msgpack_decoder/v1/mod.rs | 1 - .../src/msgpack_decoder/v1/span.rs | 12 ------ .../src/msgpack_encoder/v04/span_v04.rs | 40 ------------------- .../src/msgpack_encoder/v04/span_v1.rs | 11 ----- .../src/msgpack_encoder/v1/mod.rs | 1 - .../src/msgpack_encoder/v1/span_v04.rs | 6 --- .../src/msgpack_encoder/v1/span_v1.rs | 6 --- libdd-trace-utils/src/otlp_encoder/mapper.rs | 4 +- libdd-trace-utils/src/span/v04/mod.rs | 8 ---- libdd-trace-utils/src/span/v05/mod.rs | 16 ++------ libdd-trace-utils/src/span/v1/mod.rs | 1 - libdd-trace-utils/tests/test_send_data.rs | 2 - 17 files changed, 4 insertions(+), 131 deletions(-) diff --git a/libdd-data-pipeline-ffi/src/tracer.rs b/libdd-data-pipeline-ffi/src/tracer.rs index 9caae2685f..6e8ae680df 100644 --- a/libdd-data-pipeline-ffi/src/tracer.rs +++ b/libdd-data-pipeline-ffi/src/tracer.rs @@ -82,7 +82,6 @@ pub struct TracerSpanLink<'a> { pub trace_id_high: u64, pub span_id: u64, pub attributes: Slice<'a, TracerSpanLinkAttribute<'a>>, - pub dropped_attributes_count: u32, pub tracestate: CharSlice<'a>, pub flags: u32, } @@ -264,7 +263,6 @@ pub unsafe extern "C" fn ddog_tracer_span_set_links( trace_id_high: link.trace_id_high, span_id: link.span_id, attributes: converted_attributes, - dropped_attributes_count: link.dropped_attributes_count, tracestate, flags: link.flags, }); @@ -628,7 +626,6 @@ mod tests { trace_id_high: 0x4567, span_id: 0x89ab, attributes: Slice::from(&first_attributes[..]), - dropped_attributes_count: 7, tracestate: cs("vendor=value"), flags: 0x8000_0001, }, @@ -637,7 +634,6 @@ mod tests { trace_id_high: 0, span_id: 3, attributes: Slice::default(), - dropped_attributes_count: 0, tracestate: cs(""), flags: 0, }, @@ -650,7 +646,6 @@ mod tests { assert_eq!(span.0.span_links[0].trace_id, 0x0123); assert_eq!(span.0.span_links[0].trace_id_high, 0x4567); assert_eq!(span.0.span_links[0].span_id, 0x89ab); - assert_eq!(span.0.span_links[0].dropped_attributes_count, 7); assert_eq!(span.0.span_links[0].flags, 0x8000_0001); assert_eq!(span.0.span_links[0].tracestate.as_ref(), "vendor=value"); assert_eq!( @@ -676,7 +671,6 @@ mod tests { trace_id_high: 0, span_id: 2, attributes: Slice::default(), - dropped_attributes_count: 0, tracestate: cs(""), flags: 0, }]; @@ -687,7 +681,6 @@ mod tests { trace_id_high: 4, span_id: 5, attributes: Slice::default(), - dropped_attributes_count: 0, tracestate: cs("state=value"), flags: 6, }]; @@ -719,7 +712,6 @@ mod tests { trace_id_high: 2, span_id: 3, attributes: Slice::default(), - dropped_attributes_count: 0, tracestate: cs("valid=value"), flags: 4, }, @@ -728,7 +720,6 @@ mod tests { trace_id_high: 6, span_id: 7, attributes: Slice::default(), - dropped_attributes_count: 0, tracestate: CharSlice::from_bytes(&invalid), flags: 8, }, @@ -763,7 +754,6 @@ mod tests { trace_id_high: 2, span_id: 3, attributes: Slice::from(&attributes[..]), - dropped_attributes_count: 0, tracestate: cs(""), flags: 4, }]; diff --git a/libdd-trace-utils/src/agentless_encoder/tests.rs b/libdd-trace-utils/src/agentless_encoder/tests.rs index ca92d092ed..57554c250c 100644 --- a/libdd-trace-utils/src/agentless_encoder/tests.rs +++ b/libdd-trace-utils/src/agentless_encoder/tests.rs @@ -141,7 +141,6 @@ fn span_links_serialised_into_meta_as_json_string() { trace_id_high: 0x0011_2233_4455_6677, span_id: 0xfeed_face_dead_beef, attributes: HashMap::from([(bs("link.name"), bs("scheduled_by"))]), - dropped_attributes_count: 0, flags: 1, tracestate: bs("dd=s:1"), }; diff --git a/libdd-trace-utils/src/json_log_encoder/mod.rs b/libdd-trace-utils/src/json_log_encoder/mod.rs index 6d62827a2c..f98f47231a 100644 --- a/libdd-trace-utils/src/json_log_encoder/mod.rs +++ b/libdd-trace-utils/src/json_log_encoder/mod.rs @@ -421,7 +421,6 @@ mod tests { span_links: vec![SpanLink { trace_id: 7, span_id: 8, - dropped_attributes_count: 3, ..Default::default() }], span_events: vec![SpanEvent { @@ -440,7 +439,6 @@ mod tests { // Lock the inner wire shape (field names/values), not just presence. assert_eq!(span_json["span_links"][0]["trace_id"], 7); assert_eq!(span_json["span_links"][0]["span_id"], 8); - assert_eq!(span_json["span_links"][0]["dropped_attributes_count"], 3); assert_eq!(span_json["span_events"][0]["name"], "evt"); assert_eq!(span_json["span_events"][0]["time_unix_nano"], 123); } diff --git a/libdd-trace-utils/src/json_log_encoder/span.rs b/libdd-trace-utils/src/json_log_encoder/span.rs index 49e26bcafc..c39363ed95 100644 --- a/libdd-trace-utils/src/json_log_encoder/span.rs +++ b/libdd-trace-utils/src/json_log_encoder/span.rs @@ -82,14 +82,12 @@ impl Serialize for LogSpanLink<'_, T> { fn serialize(&self, serializer: S) -> Result { let link = self.0; let has_attributes = !link.attributes.is_empty(); - let has_dropped_attributes = link.dropped_attributes_count != 0; let has_tracestate = !Borrow::::borrow(&link.tracestate).is_empty(); let has_flags = link.flags != 0; // Always: trace_id, trace_id_high, span_id. let mut len = 3; len += has_attributes as usize; - len += has_dropped_attributes as usize; len += has_tracestate as usize; len += has_flags as usize; @@ -100,9 +98,6 @@ impl Serialize for LogSpanLink<'_, T> { if has_attributes { state.serialize_field("attributes", &link.attributes)?; } - if has_dropped_attributes { - state.serialize_field("dropped_attributes_count", &link.dropped_attributes_count)?; - } if has_tracestate { state.serialize_field("tracestate", &link.tracestate)?; } diff --git a/libdd-trace-utils/src/msgpack_decoder/decode/span_link.rs b/libdd-trace-utils/src/msgpack_decoder/decode/span_link.rs index 6deafa507e..af32dc3ff9 100644 --- a/libdd-trace-utils/src/msgpack_decoder/decode/span_link.rs +++ b/libdd-trace-utils/src/msgpack_decoder/decode/span_link.rs @@ -50,7 +50,6 @@ enum SpanLinkKey { TraceIdHigh, SpanId, Attributes, - DroppedAttributesCount, Tracestate, Flags, } @@ -64,7 +63,6 @@ impl FromStr for SpanLinkKey { "trace_id_high" => Ok(SpanLinkKey::TraceIdHigh), "span_id" => Ok(SpanLinkKey::SpanId), "attributes" => Ok(SpanLinkKey::Attributes), - "dropped_attributes_count" => Ok(SpanLinkKey::DroppedAttributesCount), "tracestate" => Ok(SpanLinkKey::Tracestate), "flags" => Ok(SpanLinkKey::Flags), _ => Err(DecodeError::InvalidFormat( @@ -87,9 +85,6 @@ fn decode_span_link( SpanLinkKey::TraceIdHigh => span.trace_id_high = read_number(buf)?, SpanLinkKey::SpanId => span.span_id = read_number(buf)?, SpanLinkKey::Attributes => span.attributes = read_str_map_to_hashmap(buf)?, - SpanLinkKey::DroppedAttributesCount => { - span.dropped_attributes_count = read_number(buf)? - } SpanLinkKey::Tracestate => span.tracestate = buf.read_string()?, SpanLinkKey::Flags => span.flags = read_number(buf)?, } @@ -123,10 +118,6 @@ mod tests { SpanLinkKey::from_str("attributes").unwrap(), SpanLinkKey::Attributes ); - assert_eq!( - SpanLinkKey::from_str("dropped_attributes_count").unwrap(), - SpanLinkKey::DroppedAttributesCount - ); assert_eq!( SpanLinkKey::from_str("tracestate").unwrap(), SpanLinkKey::Tracestate diff --git a/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs b/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs index a877f85bab..b7ee7c45d7 100644 --- a/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs +++ b/libdd-trace-utils/src/msgpack_decoder/v1/mod.rs @@ -61,7 +61,6 @@ pub(super) mod span_link_key { pub const ATTRIBUTES: u8 = 3; pub const TRACE_STATE: u8 = 4; pub const FLAGS: u8 = 5; - pub const DROPPED_ATTRIBUTES_COUNT: u8 = 6; } pub(super) mod span_event_key { diff --git a/libdd-trace-utils/src/msgpack_decoder/v1/span.rs b/libdd-trace-utils/src/msgpack_decoder/v1/span.rs index 37dd17ec58..62fd2837bc 100644 --- a/libdd-trace-utils/src/msgpack_decoder/v1/span.rs +++ b/libdd-trace-utils/src/msgpack_decoder/v1/span.rs @@ -276,18 +276,6 @@ where DecodeError::InvalidFormat(format!("V1 span_link flags {v} exceeds u32::MAX")) })?; } - span_link_key::DROPPED_ATTRIBUTES_COUNT => { - let v: u64 = decode::read_int(buf.as_mut_slice()).map_err(|_| { - DecodeError::InvalidFormat( - "V1 span_link dropped_attributes_count read failure".to_owned(), - ) - })?; - link.dropped_attributes_count = u32::try_from(v).map_err(|_| { - DecodeError::InvalidFormat(format!( - "V1 span_link dropped_attributes_count {v} exceeds u32::MAX" - )) - })?; - } _unknown => skip_unknown_value(buf)?, } } diff --git a/libdd-trace-utils/src/msgpack_encoder/v04/span_v04.rs b/libdd-trace-utils/src/msgpack_encoder/v04/span_v04.rs index 48890bb930..9d079f3649 100644 --- a/libdd-trace-utils/src/msgpack_encoder/v04/span_v04.rs +++ b/libdd-trace-utils/src/msgpack_encoder/v04/span_v04.rs @@ -37,7 +37,6 @@ pub fn encode_span_links( for link in span_links.iter() { let link_len = 3 /* minimal span link: trace_id, trace_id_high, span_id */ + (!link.attributes.is_empty()) as u32 - + (link.dropped_attributes_count != 0) as u32 + (!link.tracestate.borrow().is_empty()) as u32 + (link.flags != 0) as u32; @@ -61,11 +60,6 @@ pub fn encode_span_links( } } - if link.dropped_attributes_count != 0 { - write_const_msgpack_str!(writer, "dropped_attributes_count")?; - write_u32(writer, link.dropped_attributes_count)?; - } - if !link.tracestate.borrow().is_empty() { write_const_msgpack_str!(writer, "tracestate")?; write_str(writer, link.tracestate.borrow())?; @@ -287,37 +281,3 @@ pub fn encode_span( Ok(()) } - -#[cfg(test)] -mod tests { - use super::encode_span; - use crate::span::v04::{SpanBytes, SpanLinkBytes}; - use rmpv::Value; - use std::io::Cursor; - - #[test] - fn span_link_encodes_dropped_attributes_count() { - let span = SpanBytes { - span_links: vec![SpanLinkBytes { - dropped_attributes_count: 7, - ..Default::default() - }], - ..Default::default() - }; - let mut encoded = Vec::new(); - encode_span(&mut encoded, &span).unwrap(); - let decoded = rmpv::decode::read_value(&mut Cursor::new(encoded)).unwrap(); - let links = decoded - .as_map() - .unwrap() - .iter() - .find(|(key, _)| key.as_str() == Some("span_links")) - .unwrap() - .1 - .as_array() - .unwrap(); - let link = links[0].as_map().unwrap(); - - assert!(link.contains(&(Value::from("dropped_attributes_count"), Value::from(7),))); - } -} diff --git a/libdd-trace-utils/src/msgpack_encoder/v04/span_v1.rs b/libdd-trace-utils/src/msgpack_encoder/v04/span_v1.rs index 8980bbf9c4..991e6eaebb 100644 --- a/libdd-trace-utils/src/msgpack_encoder/v04/span_v1.rs +++ b/libdd-trace-utils/src/msgpack_encoder/v04/span_v1.rs @@ -466,7 +466,6 @@ fn encode_span_links( let link_len = 3 // trace_id, trace_id_high, span_id (always) + (attr_count > 0) as u32 - + (link.dropped_attributes_count != 0) as u32 + (!link.tracestate.borrow().is_empty()) as u32 + (link.flags != 0) as u32; @@ -499,11 +498,6 @@ fn encode_span_links( } } - if link.dropped_attributes_count != 0 { - write_const_msgpack_str!(writer, "dropped_attributes_count")?; - write_u32(writer, link.dropped_attributes_count)?; - } - if !link.tracestate.borrow().is_empty() { write_const_msgpack_str!(writer, "tracestate")?; write_str(writer, link.tracestate.borrow())?; @@ -1314,7 +1308,6 @@ mod tests { trace_id: link_tid, span_id: 7, attributes: link_attrs, - dropped_attributes_count: 9, tracestate: bs("dd=t.dm:-1"), flags: 3, }]), @@ -1342,10 +1335,6 @@ mod tests { Some("dd=t.dm:-1") ); assert_eq!(map_get(link, "flags").unwrap().as_u64(), Some(3)); - assert_eq!( - map_get(link, "dropped_attributes_count").unwrap().as_u64(), - Some(9) - ); let attrs = map_get(link, "attributes").expect("string attrs preserved"); assert_eq!( diff --git a/libdd-trace-utils/src/msgpack_encoder/v1/mod.rs b/libdd-trace-utils/src/msgpack_encoder/v1/mod.rs index bddde3b266..d64f80e28d 100644 --- a/libdd-trace-utils/src/msgpack_encoder/v1/mod.rs +++ b/libdd-trace-utils/src/msgpack_encoder/v1/mod.rs @@ -72,7 +72,6 @@ pub(super) enum SpanLinkKey { Attributes = 3, TraceState = 4, Flags = 5, - DroppedAttributesCount = 6, } /// Integer keys for V1 span event fields. diff --git a/libdd-trace-utils/src/msgpack_encoder/v1/span_v04.rs b/libdd-trace-utils/src/msgpack_encoder/v1/span_v04.rs index 7ad1e5e227..a1b580101c 100644 --- a/libdd-trace-utils/src/msgpack_encoder/v1/span_v04.rs +++ b/libdd-trace-utils/src/msgpack_encoder/v1/span_v04.rs @@ -45,7 +45,6 @@ pub fn encode_span_links( let link_len = 1 // trace_id (always) + (link.span_id != 0) as u32 + (!link.attributes.is_empty()) as u32 - + (link.dropped_attributes_count != 0) as u32 + (!link.tracestate.borrow().is_empty()) as u32 + (link.flags != 0) as u32; @@ -69,11 +68,6 @@ pub fn encode_span_links( } } - if link.dropped_attributes_count != 0 { - write_uint8(writer, SpanLinkKey::DroppedAttributesCount as u8)?; - write_uint(writer, link.dropped_attributes_count as u64)?; - } - if !link.tracestate.borrow().is_empty() { write_uint8(writer, SpanLinkKey::TraceState as u8)?; table.write_interned(writer, link.tracestate.borrow())?; diff --git a/libdd-trace-utils/src/msgpack_encoder/v1/span_v1.rs b/libdd-trace-utils/src/msgpack_encoder/v1/span_v1.rs index 0e127557d2..e63a68b212 100644 --- a/libdd-trace-utils/src/msgpack_encoder/v1/span_v1.rs +++ b/libdd-trace-utils/src/msgpack_encoder/v1/span_v1.rs @@ -140,7 +140,6 @@ pub(super) fn encode_span_links( let link_len = 1 // trace_id (always) + (link.span_id != 0) as u32 + (!link.attributes.is_empty()) as u32 - + (link.dropped_attributes_count != 0) as u32 + (!link.tracestate.borrow().is_empty()) as u32 + (link.flags != 0) as u32; @@ -159,11 +158,6 @@ pub(super) fn encode_span_links( encode_attributes_map(writer, &link.attributes, table)?; } - if link.dropped_attributes_count != 0 { - write_uint8(writer, SpanLinkKey::DroppedAttributesCount as u8)?; - write_uint(writer, link.dropped_attributes_count as u64)?; - } - if !link.tracestate.borrow().is_empty() { write_uint8(writer, SpanLinkKey::TraceState as u8)?; table.write_interned(writer, link.tracestate.borrow())?; diff --git a/libdd-trace-utils/src/otlp_encoder/mapper.rs b/libdd-trace-utils/src/otlp_encoder/mapper.rs index eb3f987a53..bc3a94c65c 100644 --- a/libdd-trace-utils/src/otlp_encoder/mapper.rs +++ b/libdd-trace-utils/src/otlp_encoder/mapper.rs @@ -467,7 +467,7 @@ fn map_span_link(link: &SpanLink) -> ProtoLink { ) }) .collect(), - dropped_attributes_count: link.dropped_attributes_count, + dropped_attributes_count: 0, // W3C trace flags of the linked context (sampled bit, etc.); carry them through so OTLP // consumers see the same link metadata the tracer recorded. flags: link.flags, @@ -994,7 +994,6 @@ mod tests { span.span_links.push(SpanLink { trace_id: 0x11, span_id: 0x22, - dropped_attributes_count: 7, flags: 1, ..Default::default() }); @@ -1004,7 +1003,6 @@ mod tests { link.flags, 1, "OTLP Link.flags must carry the span link's flags" ); - assert_eq!(link.dropped_attributes_count, 7); } #[test] diff --git a/libdd-trace-utils/src/span/v04/mod.rs b/libdd-trace-utils/src/span/v04/mod.rs index 48992f6466..ba6079dca5 100644 --- a/libdd-trace-utils/src/span/v04/mod.rs +++ b/libdd-trace-utils/src/span/v04/mod.rs @@ -143,8 +143,6 @@ pub struct SpanLink { pub span_id: u64, #[serde(skip_serializing_if = "HashMap::is_empty")] pub attributes: HashMap, - #[serde(skip_serializing_if = "is_default")] - pub dropped_attributes_count: u32, #[serde(skip_serializing_if = "is_empty_str")] pub tracestate: T::Text, #[serde(skip_serializing_if = "is_default")] @@ -161,7 +159,6 @@ where trace_id_high: self.trace_id_high, span_id: self.span_id, attributes: self.attributes.clone(), - dropped_attributes_count: self.dropped_attributes_count, tracestate: self.tracestate.clone(), flags: self.flags, } @@ -360,7 +357,6 @@ mod tests { span_links: vec![SpanLink { trace_id: 42, attributes: HashMap::from([("span", "link")]), - dropped_attributes_count: 7, tracestate: "running", ..Default::default() }], @@ -413,10 +409,6 @@ mod tests { span.span_links[0].tracestate, deserialized.span_links[0].tracestate ); - assert_eq!( - span.span_links[0].dropped_attributes_count, - deserialized.span_links[0].dropped_attributes_count - ); assert_eq!(span.span_events[0].name, deserialized.span_events[0].name); assert_eq!( span.span_events[0].time_unix_nano, diff --git a/libdd-trace-utils/src/span/v05/mod.rs b/libdd-trace-utils/src/span/v05/mod.rs index 97272b9c5b..849a082a30 100644 --- a/libdd-trace-utils/src/span/v05/mod.rs +++ b/libdd-trace-utils/src/span/v05/mod.rs @@ -43,7 +43,7 @@ pub struct Span { /// low 64 bits). /// - `span_id` is the 64-bit id hex-encoded as 16 lowercase chars. /// - `tracestate` and `attributes` are only emitted when non-empty. -/// - `dropped_attributes_count` and `flags` are only emitted when not zero. +/// - `flags` is only emitted when not zero. struct SpanLinksSerializerV05<'a, T: TraceData>(&'a [SpanLink]); struct SpanLinkSerializerV05<'a, T: TraceData>(&'a SpanLink); @@ -63,13 +63,8 @@ impl<'a, T: TraceData> Serialize for SpanLinkSerializerV05<'a, T> { let tracestate: &str = link.tracestate.borrow(); let has_tracestate = !tracestate.is_empty(); let has_attributes = !link.attributes.is_empty(); - let has_dropped_attributes = link.dropped_attributes_count != 0; let has_flags = link.flags != 0; - let len = 2 - + has_tracestate as usize - + has_attributes as usize - + has_dropped_attributes as usize - + has_flags as usize; + let len = 2 + has_tracestate as usize + has_attributes as usize + has_flags as usize; let mut map = serializer.serialize_map(Some(len))?; map.serialize_entry( "trace_id", @@ -85,9 +80,6 @@ impl<'a, T: TraceData> Serialize for SpanLinkSerializerV05<'a, T> { &SortedStrMapSerializerV05::(&link.attributes), )?; } - if has_dropped_attributes { - map.serialize_entry("dropped_attributes_count", &link.dropped_attributes_count)?; - } if has_flags { map.serialize_entry("flags", &link.flags)?; } @@ -413,7 +405,6 @@ mod tests { trace_id_high: 67890, span_id: 54321, attributes: HashMap::from([(BytesString::from("key"), BytesString::from("val"))]), - dropped_attributes_count: 7, tracestate: BytesString::from("tracestate_value"), flags: 1, }]; @@ -434,7 +425,7 @@ mod tests { let links_json = meta_json(&dict, &v05_span, "_dd.span_links").unwrap(); assert_eq!( links_json, - "[{\"trace_id\":\"00000000000109320000000000003039\",\"span_id\":\"000000000000d431\",\"tracestate\":\"tracestate_value\",\"attributes\":{\"key\":\"val\"},\"dropped_attributes_count\":7,\"flags\":1}]" + "[{\"trace_id\":\"00000000000109320000000000003039\",\"span_id\":\"000000000000d431\",\"tracestate\":\"tracestate_value\",\"attributes\":{\"key\":\"val\"},\"flags\":1}]" ); let events_json = meta_json(&dict, &v05_span, "events").unwrap(); assert_eq!( @@ -483,7 +474,6 @@ mod tests { trace_id_high: 0, span_id: 0xfeed, attributes: HashMap::new(), - dropped_attributes_count: 0, tracestate: BytesString::from(""), flags: 7, }]; diff --git a/libdd-trace-utils/src/span/v1/mod.rs b/libdd-trace-utils/src/span/v1/mod.rs index 65c9095f52..575447c31c 100644 --- a/libdd-trace-utils/src/span/v1/mod.rs +++ b/libdd-trace-utils/src/span/v1/mod.rs @@ -115,7 +115,6 @@ pub struct SpanLink { pub trace_id: [u8; 16], pub span_id: u64, pub attributes: VecMap>, - pub dropped_attributes_count: u32, pub tracestate: T::Text, pub flags: u32, } diff --git a/libdd-trace-utils/tests/test_send_data.rs b/libdd-trace-utils/tests/test_send_data.rs index 03f836c45f..3075362a53 100644 --- a/libdd-trace-utils/tests/test_send_data.rs +++ b/libdd-trace-utils/tests/test_send_data.rs @@ -463,7 +463,6 @@ mod tracing_integration_tests { let span_link = SpanLinkBytes { trace_id: tid_bytes(0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210), span_id: 0xa0a0_a0a0_a0a0_a0a0, - dropped_attributes_count: 0, tracestate: bs_v1("dd=t.tid:abc"), flags: 1, attributes: VecMap::new(), @@ -659,7 +658,6 @@ mod tracing_integration_tests { let span_link = SpanLinkBytes { trace_id: tid_bytes(0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210), span_id: 0xa0a0_a0a0_a0a0_a0a0, - dropped_attributes_count: 0, tracestate: bs_v1("dd=t.tid:abc"), flags: 1, attributes: link_attrs, From f80519a561c790be9a1953b04d33d7658987eb74 Mon Sep 17 00:00:00 2001 From: Edmund Kump Date: Tue, 4 Aug 2026 14:49:56 -0400 Subject: [PATCH 4/8] improve test coverage for ddog_tracer_span_set_links: an empty slice clears existing links, and that the outer links array or a link's nested attributes returns InvalidInput while leaving the span's existing links intact. --- libdd-data-pipeline-ffi/src/tracer.rs | 74 +++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/libdd-data-pipeline-ffi/src/tracer.rs b/libdd-data-pipeline-ffi/src/tracer.rs index 6e8ae680df..ddc5f708b7 100644 --- a/libdd-data-pipeline-ffi/src/tracer.rs +++ b/libdd-data-pipeline-ffi/src/tracer.rs @@ -696,6 +696,28 @@ mod tests { } } + #[test] + fn set_links_empty_slice_clears_existing_links() { + unsafe { + let mut span = make_minimal_span(); + let links = [TracerSpanLink { + trace_id_low: 1, + trace_id_high: 0, + span_id: 2, + attributes: Slice::default(), + tracestate: cs(""), + flags: 0, + }]; + assert!(ddog_tracer_span_set_links(Some(&mut span), Slice::from(&links[..])).is_none()); + assert_eq!(span.0.span_links.len(), 1); + + assert!(ddog_tracer_span_set_links(Some(&mut span), Slice::default()).is_none()); + assert!(span.0.span_links.is_empty()); + + ddog_tracer_span_free(span); + } + } + #[test] fn set_links_failure_is_atomic() { unsafe { @@ -768,6 +790,58 @@ mod tests { } } + /// An invalid `links` slice must be rejected rather than dereferenced: `try_as_slice` + /// fails and the span's existing links are left alone. + #[test] + fn set_links_rejects_invalid_links_slice_atomically() { + unsafe { + let mut span = make_minimal_span(); + span.0.span_links.push(SpanLinkBytes { + trace_id: 7, + ..Default::default() + }); + + let bad: Slice<'_, TracerSpanLink<'_>> = Slice::from_raw_parts(std::ptr::null(), 1); + let err = ddog_tracer_span_set_links(Some(&mut span), bad); + assert!(err.is_some()); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidInput); + ddog_trace_exporter_error_free(err); + assert_eq!(span.0.span_links.len(), 1); + assert_eq!(span.0.span_links[0].trace_id, 7); + + ddog_tracer_span_free(span); + } + } + + /// Same for a nested `attributes` slice, which is validated per link. + #[test] + fn set_links_rejects_invalid_attributes_slice_atomically() { + unsafe { + let mut span = make_minimal_span(); + span.0.span_links.push(SpanLinkBytes { + trace_id: 7, + ..Default::default() + }); + let links = [TracerSpanLink { + trace_id_low: 1, + trace_id_high: 2, + span_id: 3, + attributes: Slice::from_raw_parts(std::ptr::null(), 1), + tracestate: cs(""), + flags: 4, + }]; + + let err = ddog_tracer_span_set_links(Some(&mut span), Slice::from(&links[..])); + assert!(err.is_some()); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidInput); + ddog_trace_exporter_error_free(err); + assert_eq!(span.0.span_links.len(), 1); + assert_eq!(span.0.span_links[0].trace_id, 7); + + ddog_tracer_span_free(span); + } + } + #[test] fn set_links_null_handle_returns_error() { unsafe { From 3e10e36a921a0821bd633d7d95832be8fc6422fb Mon Sep 17 00:00:00 2001 From: Julio Date: Wed, 5 Aug 2026 15:13:30 +0200 Subject: [PATCH 5/8] chore(libdd-data-pipeline-ffi): address PR concerns - Add multi-attribute test. - Fix error handling where malformed or invalid pointers were treated like invalid input instead of invalid arguments. --- libdd-data-pipeline-ffi/src/tracer.rs | 47 +++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/libdd-data-pipeline-ffi/src/tracer.rs b/libdd-data-pipeline-ffi/src/tracer.rs index 9bb53f2881..d17cd5c609 100644 --- a/libdd-data-pipeline-ffi/src/tracer.rs +++ b/libdd-data-pipeline-ffi/src/tracer.rs @@ -287,14 +287,14 @@ pub unsafe extern "C" fn ddog_tracer_span_set_links( if let Some(span) = handle { let links = match links.try_as_slice() { Ok(links) => links, - Err(_) => return gen_error!(ErrorCode::InvalidInput), + Err(_) => return gen_error!(ErrorCode::InvalidArgument), }; let mut converted = Vec::with_capacity(links.len()); for link in links { let attributes = match link.attributes.try_as_slice() { Ok(attributes) => attributes, - Err(_) => return gen_error!(ErrorCode::InvalidInput), + Err(_) => return gen_error!(ErrorCode::InvalidArgument), }; let mut converted_attributes = HashMap::with_capacity(attributes.len()); for attribute in attributes { @@ -744,6 +744,49 @@ mod tests { } } + #[test] + fn set_links_copies_every_attribute_of_a_link() { + unsafe { + let mut span = make_minimal_span(); + let attributes = [ + TracerSpanLinkAttribute { + key: cs("messaging.operation"), + value: cs("receive"), + }, + TracerSpanLinkAttribute { + key: cs("messaging.system"), + value: cs("kafka"), + }, + TracerSpanLinkAttribute { + key: cs("link.kind"), + value: cs("follows_from"), + }, + ]; + let links = [TracerSpanLink { + trace_id_low: 1, + trace_id_high: 2, + span_id: 3, + attributes: Slice::from(&attributes[..]), + tracestate: cs(""), + flags: 0, + }]; + + let err = ddog_tracer_span_set_links(Some(&mut span), Slice::from(&links[..])); + assert!(err.is_none()); + + let copied = &span.0.span_links[0].attributes; + assert_eq!(copied.len(), 3); + assert_eq!( + copied.get("messaging.operation").unwrap().as_ref(), + "receive" + ); + assert_eq!(copied.get("messaging.system").unwrap().as_ref(), "kafka"); + assert_eq!(copied.get("link.kind").unwrap().as_ref(), "follows_from"); + + ddog_tracer_span_free(span); + } + } + #[test] fn set_links_replaces_existing_links() { unsafe { From 93ba876b89054c88f9e5eaa7614c702634dce01e Mon Sep 17 00:00:00 2001 From: Julio Date: Wed, 5 Aug 2026 15:24:28 +0200 Subject: [PATCH 6/8] chore: address unit test failures --- libdd-data-pipeline-ffi/src/tracer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libdd-data-pipeline-ffi/src/tracer.rs b/libdd-data-pipeline-ffi/src/tracer.rs index d17cd5c609..2723c76530 100644 --- a/libdd-data-pipeline-ffi/src/tracer.rs +++ b/libdd-data-pipeline-ffi/src/tracer.rs @@ -929,7 +929,7 @@ mod tests { let bad: Slice<'_, TracerSpanLink<'_>> = Slice::from_raw_parts(std::ptr::null(), 1); let err = ddog_tracer_span_set_links(Some(&mut span), bad); assert!(err.is_some()); - assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidInput); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidArgument); ddog_trace_exporter_error_free(err); assert_eq!(span.0.span_links.len(), 1); assert_eq!(span.0.span_links[0].trace_id, 7); @@ -958,7 +958,7 @@ mod tests { let err = ddog_tracer_span_set_links(Some(&mut span), Slice::from(&links[..])); assert!(err.is_some()); - assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidInput); + assert_eq!(err.as_ref().unwrap().code, ErrorCode::InvalidArgument); ddog_trace_exporter_error_free(err); assert_eq!(span.0.span_links.len(), 1); assert_eq!(span.0.span_links[0].trace_id, 7); From 7181554923f1d389d6fda1d5ba21aa202bf0a813 Mon Sep 17 00:00:00 2001 From: Julio Date: Wed, 5 Aug 2026 18:26:27 +0200 Subject: [PATCH 7/8] fix: solve merge conflicts --- libdd-data-pipeline-ffi/src/tracer.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/libdd-data-pipeline-ffi/src/tracer.rs b/libdd-data-pipeline-ffi/src/tracer.rs index f959fe1bc7..aba1a5355c 100644 --- a/libdd-data-pipeline-ffi/src/tracer.rs +++ b/libdd-data-pipeline-ffi/src/tracer.rs @@ -17,7 +17,7 @@ use libdd_common_ffi::slice::{AsBytes, ByteSlice, Slice}; use libdd_common_ffi::CharSlice; use libdd_tinybytes::{Bytes, BytesString}; use libdd_trace_utils::span::v04::{ - AttributeAnyValueBytes, AttributeArrayValueBytes, SpanBytes, SpanEventBytes, + AttributeAnyValueBytes, AttributeArrayValueBytes, SpanBytes, SpanEventBytes, SpanLinkBytes }; use std::collections::HashMap; use std::ptr::NonNull; @@ -295,18 +295,18 @@ pub unsafe extern "C" fn ddog_tracer_span_set_links( for attribute in attributes { let key = match charslice_to_bytesstring(attribute.key) { Ok(key) => key, - Err(err) => return Some(err), + Err(err) => return err, }; let value = match charslice_to_bytesstring(attribute.value) { Ok(value) => value, - Err(err) => return Some(err), + Err(err) => return err, }; converted_attributes.insert(key, value); } let tracestate = match charslice_to_bytesstring(link.tracestate) { Ok(tracestate) => tracestate, - Err(err) => return Some(err), + Err(err) => return err, }; converted.push(SpanLinkBytes { trace_id: link.trace_id_low, @@ -975,7 +975,7 @@ mod tests { ); assert_eq!(span.0.span_links[1].trace_id, 2); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -1018,7 +1018,7 @@ mod tests { assert_eq!(copied.get("messaging.system").unwrap().as_ref(), "kafka"); assert_eq!(copied.get("link.kind").unwrap().as_ref(), "follows_from"); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -1052,7 +1052,7 @@ mod tests { assert_eq!(span.0.span_links[0].trace_id, 3); assert_eq!(span.0.span_links[0].trace_id_high, 4); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -1074,7 +1074,7 @@ mod tests { assert!(ddog_tracer_span_set_links(Some(&mut span), Slice::default()).is_none()); assert!(span.0.span_links.is_empty()); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -1114,7 +1114,7 @@ mod tests { assert_eq!(span.0.span_links.len(), 1); assert_eq!(span.0.span_links[0].trace_id, 7); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -1146,7 +1146,7 @@ mod tests { assert_eq!(span.0.span_links.len(), 1); assert_eq!(span.0.span_links[0].trace_id, 7); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -1169,7 +1169,7 @@ mod tests { assert_eq!(span.0.span_links.len(), 1); assert_eq!(span.0.span_links[0].trace_id, 7); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } @@ -1198,7 +1198,7 @@ mod tests { assert_eq!(span.0.span_links.len(), 1); assert_eq!(span.0.span_links[0].trace_id, 7); - ddog_tracer_span_free(span); + ddog_tracer_span_free(Some(span)); } } From f21ea8189669a33f434324597c19afc8c48d159b Mon Sep 17 00:00:00 2001 From: Julio Date: Wed, 5 Aug 2026 18:30:17 +0200 Subject: [PATCH 8/8] fix: lint --- libdd-data-pipeline-ffi/src/tracer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdd-data-pipeline-ffi/src/tracer.rs b/libdd-data-pipeline-ffi/src/tracer.rs index aba1a5355c..763e4c56c3 100644 --- a/libdd-data-pipeline-ffi/src/tracer.rs +++ b/libdd-data-pipeline-ffi/src/tracer.rs @@ -17,7 +17,7 @@ use libdd_common_ffi::slice::{AsBytes, ByteSlice, Slice}; use libdd_common_ffi::CharSlice; use libdd_tinybytes::{Bytes, BytesString}; use libdd_trace_utils::span::v04::{ - AttributeAnyValueBytes, AttributeArrayValueBytes, SpanBytes, SpanEventBytes, SpanLinkBytes + AttributeAnyValueBytes, AttributeArrayValueBytes, SpanBytes, SpanEventBytes, SpanLinkBytes, }; use std::collections::HashMap; use std::ptr::NonNull;