Skip to content

Commit

Permalink
[Tracer] Finishes Adding Support for ActivityLink (#5627)
Browse files Browse the repository at this point in the history
* 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 <steven.bouwkamp@datadoghq.com>
  • Loading branch information
link04 and bouwkast authored Jun 10, 2024
1 parent 33db3c2 commit 9c01290
Show file tree
Hide file tree
Showing 28 changed files with 1,264 additions and 297 deletions.
14 changes: 14 additions & 0 deletions tracer/src/Datadog.Trace/Activity/DuckTypes/ActivityTraceFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// <copyright file="ActivityTraceFlags.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

namespace Datadog.Trace.Activity.DuckTypes;

internal enum ActivityTraceFlags
{
None = 0,
Recorded = 1
}
6 changes: 6 additions & 0 deletions tracer/src/Datadog.Trace/Activity/DuckTypes/IActivity5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ internal interface IActivity5 : IW3CActivity

IEnumerable Events { get; }

/// <summary>
/// Gets the list of all <see cref="IActivityLink" /> objects attached to this Activity object.
/// If there is no any <see cref="IActivityLink" /> object attached to the Activity object, Links will return empty list.
/// </summary>
IEnumerable Links { get; }

object AddTag(string key, object value);
}
}
27 changes: 27 additions & 0 deletions tracer/src/Datadog.Trace/Activity/DuckTypes/IActivityContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// <copyright file="IActivityContext.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using System;
using System.Collections.Generic;
using Datadog.Trace.DuckTyping;

namespace Datadog.Trace.Activity.DuckTypes;

// https://github.com/dotnet/runtime/blob/f2a9ef8d392b72e6f039ec0b87f3eae4307c6cae/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.cs#L13

internal interface IActivityContext : IDuckType
{
IActivityTraceId TraceId { get; }

IActivitySpanId SpanId { get; }

ActivityTraceFlags TraceFlags { get; }

string? TraceState { get; }

bool IsRemote { get; }
}
21 changes: 21 additions & 0 deletions tracer/src/Datadog.Trace/Activity/DuckTypes/IActivityLink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// <copyright file="IActivityLink.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using System;
using System.Collections.Generic;
using Datadog.Trace.DuckTyping;

namespace Datadog.Trace.Activity.DuckTypes;

// https://github.com/dotnet/runtime/blob/f2a9ef8d392b72e6f039ec0b87f3eae4307c6cae/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityLink.cs#L15

internal interface IActivityLink : IDuckType
{
IActivityContext Context { get; }

IEnumerable<KeyValuePair<string, object?>>? Tags { get; }
}
16 changes: 16 additions & 0 deletions tracer/src/Datadog.Trace/Activity/DuckTypes/IActivitySpanId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// <copyright file="IActivitySpanId.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using Datadog.Trace.DuckTyping;

namespace Datadog.Trace.Activity.DuckTypes;

internal interface IActivitySpanId : IDuckType
{
[DuckField(Name = "_hexString")]
string? SpanId { get; }
}
16 changes: 16 additions & 0 deletions tracer/src/Datadog.Trace/Activity/DuckTypes/IActivityTraceId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// <copyright file="IActivityTraceId.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using Datadog.Trace.DuckTyping;

namespace Datadog.Trace.Activity.DuckTypes;

internal interface IActivityTraceId : IDuckType
{
[DuckField(Name = "_hexString")]
string? TraceId { get; }
}
188 changes: 172 additions & 16 deletions tracer/src/Datadog.Trace/Activity/OtlpHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Datadog.Trace.Activity.DuckTypes;
using Datadog.Trace.DuckTyping;
using Datadog.Trace.ExtensionMethods;
using Datadog.Trace.Logging;
using Datadog.Trace.Propagators;
using Datadog.Trace.Tagging;
using Datadog.Trace.Util;
using Datadog.Trace.Vendors.Newtonsoft.Json;
using Datadog.Trace.Vendors.Newtonsoft.Json.Linq;

namespace Datadog.Trace.Activity
{
Expand All @@ -42,7 +42,6 @@ private static void AgentConvertSpan<TInner>(TInner activity, Span span)
var w3cActivity = activity as IW3CActivity;
var activity5 = activity as IActivity5;
var activity6 = activity as IActivity6;

span.ResourceName = null; // Reset the resource name, it will be repopulated via the Datadog trace agent logic
span.OperationName = null; // Reset the operation name, it will be repopulated

Expand Down Expand Up @@ -204,6 +203,95 @@ private static void AgentConvertSpan<TInner>(TInner activity, Span span)
{
span.Type = activity5 is null ? SpanTypes.Custom : AgentSpanKind2Type(activity5.Kind, span);
}

// extract any ActivityLinks
ExtractActivityLinks<TInner>(span, activity5);
}

private static void ExtractActivityLinks<TInner>(Span span, IActivity5? activity5)
where TInner : IActivity
{
if (activity5 is null)
{
return;
}

foreach (var link in (activity5.Links))
{
if (link.TryDuckCast<IActivityLink>(out var duckLink))
{
if (duckLink.Context.TraceId.TraceId is null || duckLink.Context.SpanId.SpanId is null)
{
continue;
}

_ = HexString.TryParseTraceId(duckLink.Context.TraceId.TraceId, out var newActivityTraceId);
_ = HexString.TryParseUInt64(duckLink.Context.SpanId.SpanId, out var newActivitySpanId);
var traceParentSample = duckLink.Context.TraceFlags > 0;
var traceState = W3CTraceContextPropagator.ParseTraceState(duckLink.Context.TraceState ?? string.Empty);

var samplingPriority = traceParentSample switch
{
true when traceState.SamplingPriority is > 0 => traceState.SamplingPriority.Value,
true => SamplingPriorityValues.AutoKeep,
false when traceState.SamplingPriority is <= 0 => traceState.SamplingPriority.Value,
false => SamplingPriorityValues.AutoReject,
};

var spanContext = new SpanContext(
newActivityTraceId,
newActivitySpanId,
samplingPriority: samplingPriority,
serviceName: null,
origin: traceState.Origin,
isRemote: duckLink.Context.IsRemote);

var traceTags = TagPropagation.ParseHeader(traceState.PropagatedTags);

if (traceParentSample && traceState.SamplingPriority <= 0)
{
traceTags.SetTag(Tags.Propagated.DecisionMaker, "-0");
}
else if (!traceParentSample && traceState.SamplingPriority > 0)
{
traceTags.RemoveTag(Tags.Propagated.DecisionMaker);
}

spanContext.AdditionalW3CTraceState = traceState.AdditionalValues;
spanContext.LastParentId = traceState.LastParent;
spanContext.PropagatedTags = traceTags;

var extractedSpan = new Span(spanContext, DateTimeOffset.Now, new CommonTags());
var spanLink = span.AddSpanLink(extractedSpan);

if (duckLink.Tags is not null)
{
foreach (var kvp in duckLink.Tags)
{
if (!string.IsNullOrEmpty(kvp.Key)
&& IsAllowedAtributeType(kvp.Value))
{
if (kvp.Value is Array array)
{
int index = 0;
foreach (var item in array)
{
if (item is not null)
{
spanLink.AddAttribute($"{kvp.Key}.{index}", item.ToString()!);
index++;
}
}
}
else
{
spanLink.AddAttribute(kvp.Key, kvp.Value!.ToString()!);
}
}
}
}
}
}
}

internal static string GetSpanKind(ActivityKind activityKind) =>
Expand All @@ -216,7 +304,7 @@ internal static string GetSpanKind(ActivityKind activityKind) =>
_ => SpanKinds.Internal,
};

internal static void SetTagObject(Span span, string key, object? value)
internal static void SetTagObject(Span span, string key, object? value, bool allowUnrolling = true)
{
if (value is null)
{
Expand All @@ -226,7 +314,7 @@ internal static void SetTagObject(Span span, string key, object? value)

switch (value)
{
case char c: // TODO: Can't get here from OTEL API, test with Activity API
case char c:
AgentSetOtlpTag(span, key, c.ToString());
break;
case string s:
Expand All @@ -235,16 +323,16 @@ internal static void SetTagObject(Span span, string key, object? value)
case bool b:
AgentSetOtlpTag(span, key, b ? "true" : "false");
break;
case byte b: // TODO: Can't get here from OTEL API, test with Activity API
case byte b:
span.SetMetric(key, b);
break;
case sbyte sb: // TODO: Can't get here from OTEL API, test with Activity API
case sbyte sb:
span.SetMetric(key, sb);
break;
case short sh: // TODO: Can't get here from OTEL API, test with Activity API
case short sh:
span.SetMetric(key, sh);
break;
case ushort us: // TODO: Can't get here from OTEL API, test with Activity API
case ushort us:
span.SetMetric(key, us);
break;
case int i: // TODO: Can't get here from OTEL API, test with Activity API
Expand All @@ -259,23 +347,44 @@ internal static void SetTagObject(Span span, string key, object? value)
}

break;
case uint ui: // TODO: Can't get here from OTEL API, test with Activity API
case uint ui:
span.SetMetric(key, ui);
break;
case long l: // TODO: Can't get here from OTEL API, test with Activity API
case long l:
span.SetMetric(key, l);
break;
case ulong ul: // TODO: Can't get here from OTEL API, test with Activity API
case ulong ul:
span.SetMetric(key, ul);
break;
case float f: // TODO: Can't get here from OTEL API, test with Activity API
case float f:
span.SetMetric(key, f);
break;
case double d:
span.SetMetric(key, d);
break;
case IEnumerable enumerable:
AgentSetOtlpTag(span, key, JsonConvert.SerializeObject(enumerable));
if (allowUnrolling)
{
var index = 0;
foreach (var element in (enumerable))
{
// we are only supporting a single level of unrolling
SetTagObject(span, $"{key}.{index}", element, allowUnrolling: false);
index++;
}

if (index == 0)
{
// indicates that it was an empty array, we need to add the tag
AgentSetOtlpTag(span, key, JsonConvert.SerializeObject(value));
}
}
else
{
// we've already unrolled once, don't do it again for IEnumerable values
AgentSetOtlpTag(span, key, JsonConvert.SerializeObject(value));
}

break;
default:
AgentSetOtlpTag(span, key, value.ToString());
Expand Down Expand Up @@ -507,5 +616,52 @@ internal static void SerializeEventsToJson(Span span, IEnumerable events)

return null;
}

private static bool IsAllowedAtributeType(object? value)
{
if (value is null)
{
return false;
}

if (value is Array array)
{
if (array.Length == 0 ||
array.Rank > 1)
{
// Newtonsoft doesn't seem to support multidimensional arrays (e.g., [,]), but does support jagged (e.g., [][])
return false;
}

if (value.GetType() is { } type
&& type.IsArray
&& type.GetElementType() == typeof(object))
{
// Arrays may only have a primitive type, not 'object'
return false;
}

value = array.GetValue(0)!;

if (value is null)
{
return false;
}
}

return (value is string or bool ||
value is char ||
value is sbyte ||
value is byte ||
value is ushort ||
value is short ||
value is uint ||
value is int ||
value is ulong ||
value is long ||
value is float ||
value is double ||
value is decimal);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ internal static string CreateTraceStateHeader(SpanContext context)
}

// origin ("o:<value>")
var origin = context.TraceContext?.Origin;
var origin = context.Origin;

if (!string.IsNullOrWhiteSpace(origin))
{
Expand Down Expand Up @@ -189,8 +189,7 @@ internal static string CreateTraceStateHeader(SpanContext context)
sb.Length--;
}

// additional tracestate from other vendors
var additionalState = context.TraceContext?.AdditionalW3CTraceState;
var additionalState = context.AdditionalW3CTraceState;

if (!string.IsNullOrWhiteSpace(additionalState))
{
Expand Down
Loading

0 comments on commit 9c01290

Please sign in to comment.