Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Datadog.Trace.AppSec;
using Datadog.Trace.ExtensionMethods;
using Datadog.Trace.Processors;
using Datadog.Trace.Propagators;
using Datadog.Trace.Tagging;
using Datadog.Trace.Util;
using Datadog.Trace.Vendors.MessagePack;
using Datadog.Trace.Vendors.MessagePack.Formatters;
using Datadog.Trace.Vendors.Newtonsoft.Json;

namespace Datadog.Trace.Agent.MessagePack
{
Expand Down Expand Up @@ -47,6 +47,7 @@ internal class SpanMessagePackFormatter : IMessagePackFormatter<TraceChunkModel>
private readonly byte[] _traceStateBytes = StringEncoding.UTF8.GetBytes("tracestate");
private readonly byte[] _traceFlagBytes = StringEncoding.UTF8.GetBytes("flags");
private readonly byte[] _attributesBytes = StringEncoding.UTF8.GetBytes("attributes");
private readonly byte[] _ddSpanLinksBytes = StringEncoding.UTF8.GetBytes("_dd.span_links");

// string tags
private readonly byte[] _metaBytes = StringEncoding.UTF8.GetBytes("meta");
Expand Down Expand Up @@ -253,19 +254,14 @@ private int WriteSpanLink(ref byte[] bytes, int offset, in SpanModel spanModel)
<= 0 => 1u << 31, // drop
};

var len = 3;
var len = 4;

// check to serialize tracestate
if (context.IsRemote)
{
len++;
}

if (traceFlags > 0)
{
len++;
}

var hasAttributes = spanLink.Attributes is { Count: > 0 };
if (hasAttributes)
{
Expand All @@ -282,6 +278,10 @@ private int WriteSpanLink(ref byte[] bytes, int offset, in SpanModel spanModel)
// spanid
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _spanIdBytes);
offset += MessagePackBinary.WriteUInt64(ref bytes, offset, context.SpanId);
// flags
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _traceFlagBytes);
offset += MessagePackBinary.WriteUInt32(ref bytes, offset, traceFlags);

// optional serialization
if (hasAttributes)
{
Expand All @@ -301,12 +301,6 @@ private int WriteSpanLink(ref byte[] bytes, int offset, in SpanModel spanModel)
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _traceStateBytes);
offset += MessagePackBinary.WriteString(ref bytes, offset, traceState);
}

if (traceFlags > 0)
{
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _traceFlagBytes);
offset += MessagePackBinary.WriteUInt32(ref bytes, offset, traceFlags);
}
}

return offset - originalOffset;
Expand Down Expand Up @@ -579,6 +573,14 @@ private int WriteTags(ref byte[] bytes, int offset, in SpanModel model, ITagProc
}
}

if (model.Span.SpanLinks is { Count: > 0 })
{
count++;
offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _ddSpanLinksBytes);
var json = ConvertSpanLinksToJson(span.SpanLinks);
offset += MessagePackBinary.WriteString(ref bytes, offset, json);
}

if (count > 0)
{
// Back-patch the count. End of "meta" dictionary. Do not add any string tags after this line.
Expand Down Expand Up @@ -765,6 +767,61 @@ private void InitializeAasTags()
}
}

private string ConvertSpanLinksToJson(IReadOnlyList<SpanLink> links)
{
var result = new List<Dictionary<string, object>>(links.Count);
Copy link
Member

@lucaspimentel lucaspimentel Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Between the heap allocations and the json serializer's use reflection, this won't be great for performance. The MessagePack library has a MessagePackSerializer.ConvertToJson(), but I don't think we can use it easily here since it takes the MessagePack bytes as an argument.

If we really need to include this json (which is a separate question, see my other comments), we should serialize it manually, like we do for the MessagePack itself. Here's an example from the Newtonsoft.Json docs:

public static string ToJson(this Person p)
{
    StringWriter sw = new StringWriter();
    JsonTextWriter writer = new JsonTextWriter(sw);

    // {
    writer.WriteStartObject();

    // "name" : "Jerry"
    writer.WritePropertyName("name");
    writer.WriteValue(p.Name);

    // "likes": ["Comedy", "Superman"]
    writer.WritePropertyName("likes");
    writer.WriteStartArray();
    foreach (string like in p.Likes)
    {
        writer.WriteValue(like);
    }
    writer.WriteEndArray();

    // }
    writer.WriteEndObject();

    return sw.ToString();
}


foreach (var link in links)
{
var context = link.Context;

var samplingPriority = context.TraceContext?.SamplingPriority ?? context.SamplingPriority;
var traceFlags = samplingPriority switch
{
null => 0u, // not set
> 0 => 1u + (1u << 31), // "keep" bits
<= 0 => 1u << 31, // "drop"
};

var linkDict = new Dictionary<string, object>
{
["trace_id"] = context.TraceId128.Lower,
["trace_id_high"] = context.TraceId128.Upper,
["span_id"] = context.SpanId,
["flags"] = traceFlags,
};

if (context.IsRemote)
{
linkDict["tracestate"] = W3CTraceContextPropagator.CreateTraceStateHeader(context);
}
else
{
linkDict["tracestate"] = string.Empty;
}

// convert attributes list to a dictionary
if (link.Attributes is { Count: > 0 })
{
var attributes = new Dictionary<string, string>(link.Attributes.Count);
foreach (var attr in link.Attributes)
{
attributes[attr.Key] = attr.Value;
}

linkDict["attributes"] = attributes;
}
else
{
linkDict["attributes"] = new Dictionary<string, string>();
}

result.Add(linkDict);
}

return JsonConvert.SerializeObject(result);
}

internal struct TagWriter : IItemProcessor<string>, IItemProcessor<double>, IItemProcessor<byte[]>
{
private readonly SpanMessagePackFormatter _formatter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ public async Task SubmitsTraces()
settings.AddRegexScrubber(traceIdRegexHigh, "TraceIdHigh: LinkIdHigh");
settings.AddRegexScrubber(traceIdRegexLow, "TraceIdLow: LinkIdLow");
settings.AddRegexScrubber(_timeUnixNanoRegex, @"time_unix_nano"":<DateTimeOffset.Now>");

var spanLinkTraceIdRegex = new Regex("\"trace_id\":[0-9]+");
var spanLinkTraceIdHighRegex = new Regex("\"trace_id_high\":[0-9]+");
var spanLinkSpanIdRegex = new Regex("\"span_id\":[0-9]+");
settings.AddRegexScrubber(spanLinkTraceIdRegex, "\"trace_id\":link_trace_id_low");
settings.AddRegexScrubber(spanLinkTraceIdHighRegex, "\"trace_id_high\":link_trace_id_high");
settings.AddRegexScrubber(spanLinkSpanIdRegex, "\"span_id\":link_span_id");

await VerifyHelper.VerifySpans(spans, settings)
.UseFileName(nameof(NetActivitySdkTests));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ public async Task SubmitsTraces(string packageVersion)
settings.AddRegexScrubber(traceStatePRegex, "p:TsParentId");
settings.AddRegexScrubber(traceIdRegexHigh, "TraceIdHigh: LinkIdHigh");
settings.AddRegexScrubber(traceIdRegexLow, "TraceIdLow: LinkIdLow");

var spanLinkTraceIdRegex = new Regex("\"trace_id\":[0-9]+");
var spanLinkTraceIdHighRegex = new Regex("\"trace_id_high\":[0-9]+");
var spanLinkSpanIdRegex = new Regex("\"span_id\":[0-9]+");
settings.AddRegexScrubber(spanLinkTraceIdRegex, "\"trace_id\":link_trace_id_low");
settings.AddRegexScrubber(spanLinkTraceIdHighRegex, "\"trace_id_high\":link_trace_id_high");
settings.AddRegexScrubber(spanLinkSpanIdRegex, "\"span_id\":link_span_id");

settings.AddRegexScrubber(_versionRegex, "telemetry.sdk.version: sdk-version");
settings.AddRegexScrubber(_timeUnixNanoRegex, @"time_unix_nano"":<DateTimeOffset.Now>");
settings.AddRegexScrubber(_exceptionStacktraceRegex, @"exception.stacktrace"":""System.ArgumentException: Example argument exception"",""");
Expand Down Expand Up @@ -171,6 +179,14 @@ public async Task SubmitsTracesWithActivitySource(string packageVersion)
settings.AddRegexScrubber(traceIdRegexLow, "TraceIdLow: LinkIdLow");
settings.AddRegexScrubber(_timeUnixNanoRegex, @"time_unix_nano"":<DateTimeOffset.Now>");
settings.AddRegexScrubber(_exceptionStacktraceRegex, @"exception.stacktrace"":""System.ArgumentException: Example argument exception"",""");

var spanLinkTraceIdRegex = new Regex("\"trace_id\":[0-9]+");
var spanLinkTraceIdHighRegex = new Regex("\"trace_id_high\":[0-9]+");
var spanLinkSpanIdRegex = new Regex("\"span_id\":[0-9]+");
settings.AddRegexScrubber(spanLinkTraceIdRegex, "\"trace_id\":link_trace_id_low");
settings.AddRegexScrubber(spanLinkTraceIdHighRegex, "\"trace_id_high\":link_trace_id_high");
settings.AddRegexScrubber(spanLinkSpanIdRegex, "\"span_id\":link_span_id");

await VerifyHelper.VerifySpans(spans, settings)
.UseFileName(filename)
.DisableRequireUniquePrefix();
Expand Down
3 changes: 2 additions & 1 deletion tracer/test/Datadog.Trace.TestHelpers/SpanTagAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public static void DefaultTagAssertions(SpanTagAssertion<T> s) => s
.IsOptional("error.type")
.IsOptional("error.stack")
.IsOptional("_dd.git.repository_url")
.IsOptional("_dd.git.commit.sha");
.IsOptional("_dd.git.commit.sha")
.IsOptional("_dd.span_links");

public static void DefaultMetricAssertions(SpanTagAssertion<T> s) => s
.IsOptional("_dd.tracer_kr")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,17 +194,41 @@ public void SpanLink_Tag_Serialization()
> 0 => 1u + (1u << 31), // keep
<= 0 => 1u << 31, // drop
};
if (expectedTraceFlags > 0)
{
actualSpanLink.TraceFlags.Should().Be(expectedTraceFlags);
}
actualSpanLink.TraceFlags.Should().Be(expectedTraceFlags);

if (expectedSpanlink.Attributes is { Count: > 0 })
{
actualSpanLink.Attributes.Should().BeEquivalentTo(expectedSpanlink.Attributes);
}
}
}

// Verify links added to span[meta][_dd.span_links]
if (expected.SpanLinks is { Count: > 0 })
{
actual.Tags.Should().ContainKey("_dd.span_links");
var linksJson = actual.Tags["_dd.span_links"];
linksJson.Should().NotBeNullOrEmpty();

var parsedLinks = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(linksJson);
parsedLinks.Should().HaveCount(expected.SpanLinks.Count);

for (var j = 0; j < expected.SpanLinks.Count; j++)
{
var expectedLink = expected.SpanLinks[j];
var jsonLinkObject = parsedLinks[j];
jsonLinkObject["trace_id"].Should().Be((long)expectedLink.Context.TraceId128.Lower);
jsonLinkObject["trace_id_high"].Should().Be((long)expectedLink.Context.TraceId128.Upper);
jsonLinkObject["span_id"].ToString().Should().Be(expectedLink.Context.SpanId.ToString());
jsonLinkObject["flags"].Should().NotBeNull();
jsonLinkObject["tracestate"].Should().NotBeNull();
jsonLinkObject["attributes"].Should().NotBeNull();
}
}
else
{
actual.Tags.Should().NotContainKey("_dd.span_links");
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions tracer/test/snapshots/NetActivitySdkTests.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
runtime-id: Guid_2,
some_tag: value,
span.kind: server,
version: 1.0.0
version: 1.0.0,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483648,"tracestate":"","attributes":{"some_string":"five","some_string[].0":"a","some_string[].1":"b","some_string[].2":"c","some_bool":"False","some_bool[].0":"True","some_bool[].1":"False","some_int":"5","some_int[].0":"5","some_int[].1":"55","some_int[].2":"555"}},{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"dd=s:2;p:TsParentId;t.dm:-4,foo=1,bar=baz","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down Expand Up @@ -140,7 +141,8 @@
otel.status_code: STATUS_CODE_UNSET,
otel.trace_id: Guid_4,
span.kind: server,
version: 1.0.0
version: 1.0.0,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483648,"tracestate":"","attributes":{}}]
},
Metrics: {
attribute-int: 1.0
Expand Down
6 changes: 4 additions & 2 deletions tracer/test/snapshots/OpenTelemetrySdkTests.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
telemetry.sdk.language: dotnet,
telemetry.sdk.name: opentelemetry,
telemetry.sdk.version: sdk-version,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down Expand Up @@ -949,7 +950,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
telemetry.sdk.language: dotnet,
telemetry.sdk.name: opentelemetry,
telemetry.sdk.version: sdk-version,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}},{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
telemetry.sdk.language: dotnet,
telemetry.sdk.name: opentelemetry,
telemetry.sdk.version: sdk-version,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down Expand Up @@ -949,7 +950,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
telemetry.sdk.language: dotnet,
telemetry.sdk.name: opentelemetry,
telemetry.sdk.version: sdk-version,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}},{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
service.name: MyServiceName,
service.version: 1.0.x,
span.kind: internal,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down Expand Up @@ -841,7 +842,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
service.name: MyServiceName,
service.version: 1.0.x,
span.kind: internal,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}},{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
service.name: MyServiceName,
service.version: 1.0.x,
span.kind: internal,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down Expand Up @@ -841,7 +842,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
service.name: MyServiceName,
service.version: 1.0.x,
span.kind: internal,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}},{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
telemetry.sdk.language: dotnet,
telemetry.sdk.name: opentelemetry,
telemetry.sdk.version: sdk-version,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down Expand Up @@ -949,7 +950,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
telemetry.sdk.language: dotnet,
telemetry.sdk.name: opentelemetry,
telemetry.sdk.version: sdk-version,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}},{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
service.name: MyServiceName,
service.version: 1.0.x,
span.kind: internal,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down Expand Up @@ -841,7 +842,8 @@ at Samples.OpenTelemetrySdk.Program.RunSpanUpdateMethods(TelemetrySpan span),
service.name: MyServiceName,
service.version: 1.0.x,
span.kind: internal,
_dd.base_service: CustomServiceName
_dd.base_service: CustomServiceName,
_dd.span_links: [{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}},{"trace_id":link_trace_id_low,"trace_id_high":link_trace_id_high,"span_id":link_span_id,"flags":2147483649,"tracestate":"","attributes":{}}]
},
Metrics: {
process_id: 0,
Expand Down
Loading
Loading