Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ internal static class ActivityHandlersRegister
// Quartz handlers
new QuartzActivityHandler(),

// Azure Functions Durable Task host-side distributed tracing (V2)
new DurableTaskActivityHandler(),

// The default handler catches an activity and creates a datadog span from it.
new DefaultActivityHandler(),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// <copyright file="DurableTaskActivityHandler.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.Activity.DuckTypes;
using Datadog.Trace.Logging;
using Datadog.Trace.Tagging;

namespace Datadog.Trace.Activity.Handlers
{
/// <summary>
/// Bridges host-side Durable Functions distributed tracing activities emitted by the
/// <c>WebJobs.Extensions.DurableTask</c> ActivitySource into Datadog spans.
/// </summary>
internal sealed class DurableTaskActivityHandler : IActivityHandler
{
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor<DurableTaskActivityHandler>();

public bool ShouldListenTo(string sourceName, string? version)
=> sourceName == DurableTaskConstants.WebJobsActivitySourceName
|| sourceName == DurableTaskConstants.SdkActivitySourceName;

public void ActivityStarted<T>(string sourceName, T activity)
where T : IActivity
{
var tags = new OpenTelemetryTags
{
OtelLibraryName = sourceName,
};

ActivityHandlerCommon.ActivityStarted(sourceName, activity, tags: tags, out _);
}

public void ActivityStopped<T>(string sourceName, T activity)
where T : IActivity
{
ActivityKey key = activity switch
{
IW3CActivity { TraceId: not null, SpanId: not null } w3cActivity => new(w3cActivity.TraceId, w3cActivity.SpanId),
_ => new(activity.Id)
};

if (key.IsValid()
&& ActivityHandlerCommon.ActivityMappingById.TryRemove(key, out var activityMapping)
&& activityMapping.Scope.Span is Span span)
{
DurableTaskActivityHandlerCommon.EnhanceSpan(span, activity);
OtlpHelpers.UpdateSpanFromActivity(activity, span, Tracer.Instance.Settings.OtelSemanticsEnabled);
span.Finish(activity.StartTimeUtc.Add(activity.Duration));
activityMapping.Scope.Close();
return;
}

Log.Debug(
"Could not find span for Durable Task activity '{ActivityId}' with key '{Key}', falling back to common handler",
activity.Id,
key);

ActivityHandlerCommon.ActivityStopped(sourceName, activity);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// <copyright file="DurableTaskActivityHandlerCommon.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.Collections.Generic;
using Datadog.Trace.Activity.DuckTypes;
using Datadog.Trace.Tagging;

namespace Datadog.Trace.Activity.Handlers
{
internal static class DurableTaskActivityHandlerCommon
{
internal static string GetResourceName(string? operationName, string? taskName, string? taskType)
{
if (!string.IsNullOrEmpty(taskName))
{
return taskType switch
{
DurableTaskConstants.TaskTypes.Orchestration => $"orchestration:{taskName}",
DurableTaskConstants.TaskTypes.CreateOrchestration => $"create_orchestration:{taskName}",
DurableTaskConstants.TaskTypes.Activity => $"activity:{taskName}",
DurableTaskConstants.TaskTypes.Entity => $"entity:{taskName}",
_ => taskName!,
};
}

return operationName ?? "durabletask";
}

internal static string? MapActivityKindToSpanKind(ActivityKind activityKind)
=> activityKind switch
{
ActivityKind.Server => SpanKinds.Server,
ActivityKind.Client => SpanKinds.Client,
ActivityKind.Producer => SpanKinds.Producer,
ActivityKind.Consumer => SpanKinds.Consumer,
_ => SpanKinds.Internal,
};

internal static bool TryGetTag(IEnumerable<KeyValuePair<string, object?>> tags, string key, out string? value)
{
foreach (var tag in tags)
{
if (tag.Key == key)
{
value = tag.Value?.ToString();
return true;
}
}

value = null;
return false;
}

internal static void EnhanceSpan<T>(Span span, T activity)
where T : IActivity
{
span.Type = SpanTypes.Serverless;
span.SetTag(Tags.InstrumentationName, nameof(Configuration.IntegrationId.AzureFunctions));

string? taskType = null;
string? taskName = null;

if (activity is IActivity5 activity5)
{
var tags = activity5.TagObjects;
if (tags is not null)
{
TryGetTag(tags, DurableTaskConstants.Tags.Type, out taskType);
TryGetTag(tags, DurableTaskConstants.Tags.Name, out taskName);

foreach (var tag in tags)
{
if (tag.Key.StartsWith("durabletask.", System.StringComparison.Ordinal))
{
span.SetTag(tag.Key, tag.Value?.ToString());
}
}
}

if (span.Tags is OpenTelemetryTags otelTags)
{
otelTags.SpanKind = MapActivityKindToSpanKind(activity5.Kind);
}
}

span.ResourceName = GetResourceName(activity.OperationName, taskName, taskType);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// <copyright file="DurableTaskConstants.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.Handlers
{
/// <summary>
/// Constants for Azure Functions Durable Task host-side distributed tracing.
/// Mirrors <c>Microsoft.Azure.WebJobs.Extensions.DurableTask.Correlation.Schema</c>.
/// </summary>
internal static class DurableTaskConstants
{
// Host extension scheduling/metadata spans (create_orchestration, entity, etc.)
internal const string WebJobsActivitySourceName = "WebJobs.Extensions.DurableTask";

// Durable Task SDK execution spans (orchestration/activity/timer lifecycle in V2 tracing)
internal const string SdkActivitySourceName = "Microsoft.DurableTask";

internal static class Tags
{
internal const string Type = "durabletask.type";
internal const string Name = "durabletask.task.name";
internal const string Version = "durabletask.task.version";
internal const string InstanceId = "durabletask.task.instance_id";
internal const string ExecutionId = "durabletask.task.execution_id";
internal const string TaskId = "durabletask.task.task_id";
internal const string Operation = "durabletask.task.operation";
}

internal static class TaskTypes
{
internal const string Orchestration = "orchestration";
internal const string CreateOrchestration = "create_orchestration";
internal const string Activity = "activity";
internal const string Entity = "entity";
internal const string Event = "event";
internal const string Timer = "timer";
internal const string Client = "client";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// <copyright file="DurableTaskActivityHandlerCommonTests.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>

using System.Collections.Generic;
using Datadog.Trace.Activity.DuckTypes;
using Datadog.Trace.Activity.Handlers;
using FluentAssertions;
using Xunit;

namespace Datadog.Trace.Tests.Activity;

public class DurableTaskActivityHandlerCommonTests
{
[Theory]
[InlineData("orchestration", "MyOrchestrator", "orchestration:MyOrchestrator")]
[InlineData("create_orchestration", "MyOrchestrator", "create_orchestration:MyOrchestrator")]
[InlineData("activity", "MyActivity", "activity:MyActivity")]
[InlineData("entity", "MyEntity", "entity:MyEntity")]
[InlineData("timer", "MyOrchestrator", "MyOrchestrator")]
[InlineData(null, null, "create_orchestration:MyOrchestrator@1")]
public void GetResourceName_ReturnsExpectedValue(string? taskType, string? taskName, string expected)
{
var result = DurableTaskActivityHandlerCommon.GetResourceName(
operationName: "create_orchestration:MyOrchestrator@1",
taskName: taskName,
taskType: taskType);

result.Should().Be(expected);
}

[Fact]
public void TryGetTag_FindsMatchingTag()
{
var tags = new List<KeyValuePair<string, object?>>
{
new(DurableTaskConstants.Tags.Type, "orchestration"),
new(DurableTaskConstants.Tags.Name, "MyOrchestrator"),
};

DurableTaskActivityHandlerCommon.TryGetTag(tags, DurableTaskConstants.Tags.Name, out var value!)
.Should().BeTrue();
value.Should().Be("MyOrchestrator");
}

[Theory]
[InlineData(ActivityKind.Server, SpanKinds.Server)]
[InlineData(ActivityKind.Client, SpanKinds.Client)]
[InlineData(ActivityKind.Producer, SpanKinds.Producer)]
[InlineData(ActivityKind.Consumer, SpanKinds.Consumer)]
[InlineData(ActivityKind.Internal, SpanKinds.Internal)]
public void MapActivityKindToSpanKind_ReturnsExpectedValue(ActivityKind activityKind, string expected)
{
DurableTaskActivityHandlerCommon.MapActivityKindToSpanKind(activityKind).Should().Be(expected);
}
}
Loading