Skip to content

Commit 9c01290

Browse files
link04bouwkast
andauthored
[Tracer] Finishes Adding Support for ActivityLink (#5627)
* Add some basic unit tests for Activity tags * Remove TODOs from OtlpHelpers * Support dot notation arrays in Activity tags * Update Activity-based snapshots for dot-notation * Add multi-dimensional test * Re-add empty arrays to tags * Update snapshots * Add initial ActivityLink support * Get snapshots passing * Update Benchmarks for Activity * Update snapshots * Setting SpanLink as a tag as opposed to using JsonSerializer * Setting isRemote based on context * Updating how we handle the tracestate * Updated to actually use the context state if dd= is present there * Fixing expected tracestate value * Fixing Origin and adding Attributes * Deleted ActivityLinkConverterTests class * Addressing nullability comments * Addressing nitpicks * Scrubbed and updated snapshots * Updating Snapshots from build * Updating difference on expected span count --------- Co-authored-by: Steven Bouwkamp <[email protected]>
1 parent 33db3c2 commit 9c01290

28 files changed

+1264
-297
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// <copyright file="ActivityTraceFlags.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
namespace Datadog.Trace.Activity.DuckTypes;
9+
10+
internal enum ActivityTraceFlags
11+
{
12+
None = 0,
13+
Recorded = 1
14+
}

tracer/src/Datadog.Trace/Activity/DuckTypes/IActivity5.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ internal interface IActivity5 : IW3CActivity
2424

2525
IEnumerable Events { get; }
2626

27+
/// <summary>
28+
/// Gets the list of all <see cref="IActivityLink" /> objects attached to this Activity object.
29+
/// If there is no any <see cref="IActivityLink" /> object attached to the Activity object, Links will return empty list.
30+
/// </summary>
31+
IEnumerable Links { get; }
32+
2733
object AddTag(string key, object value);
2834
}
2935
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// <copyright file="IActivityContext.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using System;
9+
using System.Collections.Generic;
10+
using Datadog.Trace.DuckTyping;
11+
12+
namespace Datadog.Trace.Activity.DuckTypes;
13+
14+
// https://github.com/dotnet/runtime/blob/f2a9ef8d392b72e6f039ec0b87f3eae4307c6cae/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.cs#L13
15+
16+
internal interface IActivityContext : IDuckType
17+
{
18+
IActivityTraceId TraceId { get; }
19+
20+
IActivitySpanId SpanId { get; }
21+
22+
ActivityTraceFlags TraceFlags { get; }
23+
24+
string? TraceState { get; }
25+
26+
bool IsRemote { get; }
27+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// <copyright file="IActivityLink.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using System;
9+
using System.Collections.Generic;
10+
using Datadog.Trace.DuckTyping;
11+
12+
namespace Datadog.Trace.Activity.DuckTypes;
13+
14+
// https://github.com/dotnet/runtime/blob/f2a9ef8d392b72e6f039ec0b87f3eae4307c6cae/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityLink.cs#L15
15+
16+
internal interface IActivityLink : IDuckType
17+
{
18+
IActivityContext Context { get; }
19+
20+
IEnumerable<KeyValuePair<string, object?>>? Tags { get; }
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// <copyright file="IActivitySpanId.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using Datadog.Trace.DuckTyping;
9+
10+
namespace Datadog.Trace.Activity.DuckTypes;
11+
12+
internal interface IActivitySpanId : IDuckType
13+
{
14+
[DuckField(Name = "_hexString")]
15+
string? SpanId { get; }
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// <copyright file="IActivityTraceId.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using Datadog.Trace.DuckTyping;
9+
10+
namespace Datadog.Trace.Activity.DuckTypes;
11+
12+
internal interface IActivityTraceId : IDuckType
13+
{
14+
[DuckField(Name = "_hexString")]
15+
string? TraceId { get; }
16+
}

tracer/src/Datadog.Trace/Activity/OtlpHelpers.cs

Lines changed: 172 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
using System.Collections;
1010
using System.Collections.Generic;
1111
using System.Globalization;
12-
using System.IO;
13-
using System.Linq;
14-
using System.Text;
1512
using Datadog.Trace.Activity.DuckTypes;
1613
using Datadog.Trace.DuckTyping;
17-
using Datadog.Trace.ExtensionMethods;
14+
using Datadog.Trace.Logging;
15+
using Datadog.Trace.Propagators;
16+
using Datadog.Trace.Tagging;
1817
using Datadog.Trace.Util;
1918
using Datadog.Trace.Vendors.Newtonsoft.Json;
19+
using Datadog.Trace.Vendors.Newtonsoft.Json.Linq;
2020

2121
namespace Datadog.Trace.Activity
2222
{
@@ -42,7 +42,6 @@ private static void AgentConvertSpan<TInner>(TInner activity, Span span)
4242
var w3cActivity = activity as IW3CActivity;
4343
var activity5 = activity as IActivity5;
4444
var activity6 = activity as IActivity6;
45-
4645
span.ResourceName = null; // Reset the resource name, it will be repopulated via the Datadog trace agent logic
4746
span.OperationName = null; // Reset the operation name, it will be repopulated
4847

@@ -204,6 +203,95 @@ private static void AgentConvertSpan<TInner>(TInner activity, Span span)
204203
{
205204
span.Type = activity5 is null ? SpanTypes.Custom : AgentSpanKind2Type(activity5.Kind, span);
206205
}
206+
207+
// extract any ActivityLinks
208+
ExtractActivityLinks<TInner>(span, activity5);
209+
}
210+
211+
private static void ExtractActivityLinks<TInner>(Span span, IActivity5? activity5)
212+
where TInner : IActivity
213+
{
214+
if (activity5 is null)
215+
{
216+
return;
217+
}
218+
219+
foreach (var link in (activity5.Links))
220+
{
221+
if (link.TryDuckCast<IActivityLink>(out var duckLink))
222+
{
223+
if (duckLink.Context.TraceId.TraceId is null || duckLink.Context.SpanId.SpanId is null)
224+
{
225+
continue;
226+
}
227+
228+
_ = HexString.TryParseTraceId(duckLink.Context.TraceId.TraceId, out var newActivityTraceId);
229+
_ = HexString.TryParseUInt64(duckLink.Context.SpanId.SpanId, out var newActivitySpanId);
230+
var traceParentSample = duckLink.Context.TraceFlags > 0;
231+
var traceState = W3CTraceContextPropagator.ParseTraceState(duckLink.Context.TraceState ?? string.Empty);
232+
233+
var samplingPriority = traceParentSample switch
234+
{
235+
true when traceState.SamplingPriority is > 0 => traceState.SamplingPriority.Value,
236+
true => SamplingPriorityValues.AutoKeep,
237+
false when traceState.SamplingPriority is <= 0 => traceState.SamplingPriority.Value,
238+
false => SamplingPriorityValues.AutoReject,
239+
};
240+
241+
var spanContext = new SpanContext(
242+
newActivityTraceId,
243+
newActivitySpanId,
244+
samplingPriority: samplingPriority,
245+
serviceName: null,
246+
origin: traceState.Origin,
247+
isRemote: duckLink.Context.IsRemote);
248+
249+
var traceTags = TagPropagation.ParseHeader(traceState.PropagatedTags);
250+
251+
if (traceParentSample && traceState.SamplingPriority <= 0)
252+
{
253+
traceTags.SetTag(Tags.Propagated.DecisionMaker, "-0");
254+
}
255+
else if (!traceParentSample && traceState.SamplingPriority > 0)
256+
{
257+
traceTags.RemoveTag(Tags.Propagated.DecisionMaker);
258+
}
259+
260+
spanContext.AdditionalW3CTraceState = traceState.AdditionalValues;
261+
spanContext.LastParentId = traceState.LastParent;
262+
spanContext.PropagatedTags = traceTags;
263+
264+
var extractedSpan = new Span(spanContext, DateTimeOffset.Now, new CommonTags());
265+
var spanLink = span.AddSpanLink(extractedSpan);
266+
267+
if (duckLink.Tags is not null)
268+
{
269+
foreach (var kvp in duckLink.Tags)
270+
{
271+
if (!string.IsNullOrEmpty(kvp.Key)
272+
&& IsAllowedAtributeType(kvp.Value))
273+
{
274+
if (kvp.Value is Array array)
275+
{
276+
int index = 0;
277+
foreach (var item in array)
278+
{
279+
if (item is not null)
280+
{
281+
spanLink.AddAttribute($"{kvp.Key}.{index}", item.ToString()!);
282+
index++;
283+
}
284+
}
285+
}
286+
else
287+
{
288+
spanLink.AddAttribute(kvp.Key, kvp.Value!.ToString()!);
289+
}
290+
}
291+
}
292+
}
293+
}
294+
}
207295
}
208296

209297
internal static string GetSpanKind(ActivityKind activityKind) =>
@@ -216,7 +304,7 @@ internal static string GetSpanKind(ActivityKind activityKind) =>
216304
_ => SpanKinds.Internal,
217305
};
218306

219-
internal static void SetTagObject(Span span, string key, object? value)
307+
internal static void SetTagObject(Span span, string key, object? value, bool allowUnrolling = true)
220308
{
221309
if (value is null)
222310
{
@@ -226,7 +314,7 @@ internal static void SetTagObject(Span span, string key, object? value)
226314

227315
switch (value)
228316
{
229-
case char c: // TODO: Can't get here from OTEL API, test with Activity API
317+
case char c:
230318
AgentSetOtlpTag(span, key, c.ToString());
231319
break;
232320
case string s:
@@ -235,16 +323,16 @@ internal static void SetTagObject(Span span, string key, object? value)
235323
case bool b:
236324
AgentSetOtlpTag(span, key, b ? "true" : "false");
237325
break;
238-
case byte b: // TODO: Can't get here from OTEL API, test with Activity API
326+
case byte b:
239327
span.SetMetric(key, b);
240328
break;
241-
case sbyte sb: // TODO: Can't get here from OTEL API, test with Activity API
329+
case sbyte sb:
242330
span.SetMetric(key, sb);
243331
break;
244-
case short sh: // TODO: Can't get here from OTEL API, test with Activity API
332+
case short sh:
245333
span.SetMetric(key, sh);
246334
break;
247-
case ushort us: // TODO: Can't get here from OTEL API, test with Activity API
335+
case ushort us:
248336
span.SetMetric(key, us);
249337
break;
250338
case int i: // TODO: Can't get here from OTEL API, test with Activity API
@@ -259,23 +347,44 @@ internal static void SetTagObject(Span span, string key, object? value)
259347
}
260348

261349
break;
262-
case uint ui: // TODO: Can't get here from OTEL API, test with Activity API
350+
case uint ui:
263351
span.SetMetric(key, ui);
264352
break;
265-
case long l: // TODO: Can't get here from OTEL API, test with Activity API
353+
case long l:
266354
span.SetMetric(key, l);
267355
break;
268-
case ulong ul: // TODO: Can't get here from OTEL API, test with Activity API
356+
case ulong ul:
269357
span.SetMetric(key, ul);
270358
break;
271-
case float f: // TODO: Can't get here from OTEL API, test with Activity API
359+
case float f:
272360
span.SetMetric(key, f);
273361
break;
274362
case double d:
275363
span.SetMetric(key, d);
276364
break;
277365
case IEnumerable enumerable:
278-
AgentSetOtlpTag(span, key, JsonConvert.SerializeObject(enumerable));
366+
if (allowUnrolling)
367+
{
368+
var index = 0;
369+
foreach (var element in (enumerable))
370+
{
371+
// we are only supporting a single level of unrolling
372+
SetTagObject(span, $"{key}.{index}", element, allowUnrolling: false);
373+
index++;
374+
}
375+
376+
if (index == 0)
377+
{
378+
// indicates that it was an empty array, we need to add the tag
379+
AgentSetOtlpTag(span, key, JsonConvert.SerializeObject(value));
380+
}
381+
}
382+
else
383+
{
384+
// we've already unrolled once, don't do it again for IEnumerable values
385+
AgentSetOtlpTag(span, key, JsonConvert.SerializeObject(value));
386+
}
387+
279388
break;
280389
default:
281390
AgentSetOtlpTag(span, key, value.ToString());
@@ -507,5 +616,52 @@ internal static void SerializeEventsToJson(Span span, IEnumerable events)
507616

508617
return null;
509618
}
619+
620+
private static bool IsAllowedAtributeType(object? value)
621+
{
622+
if (value is null)
623+
{
624+
return false;
625+
}
626+
627+
if (value is Array array)
628+
{
629+
if (array.Length == 0 ||
630+
array.Rank > 1)
631+
{
632+
// Newtonsoft doesn't seem to support multidimensional arrays (e.g., [,]), but does support jagged (e.g., [][])
633+
return false;
634+
}
635+
636+
if (value.GetType() is { } type
637+
&& type.IsArray
638+
&& type.GetElementType() == typeof(object))
639+
{
640+
// Arrays may only have a primitive type, not 'object'
641+
return false;
642+
}
643+
644+
value = array.GetValue(0)!;
645+
646+
if (value is null)
647+
{
648+
return false;
649+
}
650+
}
651+
652+
return (value is string or bool ||
653+
value is char ||
654+
value is sbyte ||
655+
value is byte ||
656+
value is ushort ||
657+
value is short ||
658+
value is uint ||
659+
value is int ||
660+
value is ulong ||
661+
value is long ||
662+
value is float ||
663+
value is double ||
664+
value is decimal);
665+
}
510666
}
511667
}

tracer/src/Datadog.Trace/Propagators/W3CTraceContextPropagator.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ internal static string CreateTraceStateHeader(SpanContext context)
157157
}
158158

159159
// origin ("o:<value>")
160-
var origin = context.TraceContext?.Origin;
160+
var origin = context.Origin;
161161

162162
if (!string.IsNullOrWhiteSpace(origin))
163163
{
@@ -189,8 +189,7 @@ internal static string CreateTraceStateHeader(SpanContext context)
189189
sb.Length--;
190190
}
191191

192-
// additional tracestate from other vendors
193-
var additionalState = context.TraceContext?.AdditionalW3CTraceState;
192+
var additionalState = context.AdditionalW3CTraceState;
194193

195194
if (!string.IsNullOrWhiteSpace(additionalState))
196195
{

0 commit comments

Comments
 (0)