diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs
index 4124f5f0e2ca..31d59bdbf6c2 100644
--- a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs
+++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs
@@ -159,7 +159,7 @@ internal static class Debugger
/// Default value is 8.
///
///
- public const string CodeOriginMaxUserFrames = "DD_CODE_ORIGIN_MAX_USER_FRAMES";
+ public const string CodeOriginMaxUserFrames = "DD_CODE_ORIGIN_FOR_SPANS_MAX_USER_FRAMES";
}
}
}
diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs
index 7f50fd09281f..0e6362bcb30c 100644
--- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs
+++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs
@@ -7,33 +7,52 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
+using System.Threading;
using Datadog.Trace.Debugger.Symbols;
using Datadog.Trace.Logging;
using Datadog.Trace.VendoredMicrosoftCode.System.Buffers;
+using Datadog.Trace.VendoredMicrosoftCode.System.Collections.Immutable;
namespace Datadog.Trace.Debugger.SpanCodeOrigin
{
internal class SpanCodeOriginManager
{
private const string CodeOriginTag = "_dd.code_origin";
+
private const string FramesPrefix = "frames";
- private static readonly DebuggerSettings Settings = LiveDebugger.Instance?.Settings ?? DebuggerSettings.FromDefaultSource();
+
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(SpanCodeOriginManager));
+ private static object _globalInstanceLock = new();
+
+ private static bool _globalInstanceInitialized;
+
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
+ private static SpanCodeOriginManager _instance;
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
+
+ private readonly DebuggerSettings _settings = LiveDebugger.Instance?.Settings ?? DebuggerSettings.FromDefaultSource();
+
+ internal static SpanCodeOriginManager Instance =>
+ LazyInitializer.EnsureInitialized(
+ ref _instance,
+ ref _globalInstanceInitialized,
+ ref _globalInstanceLock);
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static void SetCodeOrigin(Span? span)
+ internal void SetCodeOrigin(Span? span)
{
- if (span == null || !Settings.CodeOriginForSpansEnabled)
+ if (span == null || !this._settings.CodeOriginForSpansEnabled)
{
return;
}
- AddExitSpanTag(span);
+ Instance.AddExitSpanTag(span);
}
- private static void AddExitSpanTag(Span span)
+ private void AddExitSpanTag(Span span)
{
- var frames = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames);
+ var frames = ArrayPool.Shared.Rent(this._settings.CodeOriginMaxUserFrames);
try
{
var framesLength = PopulateUserFrames(frames);
@@ -94,7 +113,7 @@ private static void AddExitSpanTag(Span span)
}
}
- private static int PopulateUserFrames(FrameInfo[] frames)
+ private int PopulateUserFrames(FrameInfo[] frames)
{
var stackTrace = new StackTrace(true);
var stackFrames = stackTrace.GetFrames();
@@ -106,7 +125,7 @@ private static int PopulateUserFrames(FrameInfo[] frames)
}
var count = 0;
- for (var walkIndex = 0; walkIndex < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; walkIndex++)
+ for (var walkIndex = 0; walkIndex < stackFrames.Length && count < this._settings.CodeOriginMaxUserFrames; walkIndex++)
{
var frame = stackFrames[walkIndex];
@@ -116,7 +135,7 @@ private static int PopulateUserFrames(FrameInfo[] frames)
continue;
}
- if (AssemblyFilter.ShouldSkipAssembly(assembly, LiveDebugger.Instance.Settings.ThirdPartyDetectionExcludes, LiveDebugger.Instance.Settings.ThirdPartyDetectionIncludes))
+ if (AssemblyFilter.ShouldSkipAssembly(assembly, _settings.ThirdPartyDetectionExcludes, _settings.ThirdPartyDetectionIncludes))
{
// use cache when this will be merged: https://github.com/DataDog/dd-trace-dotnet/pull/6093
continue;
diff --git a/tracer/src/Datadog.Trace/Tracer.cs b/tracer/src/Datadog.Trace/Tracer.cs
index 9234e3cb3cbd..b6cff2e67408 100644
--- a/tracer/src/Datadog.Trace/Tracer.cs
+++ b/tracer/src/Datadog.Trace/Tracer.cs
@@ -550,7 +550,7 @@ internal Span StartSpan(string operationName, ITags tags = null, ISpanContext pa
// write them directly to the .
TracerManager.GitMetadataTagsProvider.TryExtractGitMetadata(out _);
- SpanCodeOriginManager.SetCodeOrigin(span);
+ SpanCodeOriginManager.Instance.SetCodeOrigin(span);
return span;
}
diff --git a/tracer/test/Datadog.Trace.Tests/Debugger/DebuggerSettingsTests.cs b/tracer/test/Datadog.Trace.Tests/Debugger/DebuggerSettingsTests.cs
index e934d8f62297..a84cf7805724 100644
--- a/tracer/test/Datadog.Trace.Tests/Debugger/DebuggerSettingsTests.cs
+++ b/tracer/test/Datadog.Trace.Tests/Debugger/DebuggerSettingsTests.cs
@@ -141,5 +141,61 @@ public void InvalidUploadFlushInterval_DefaultUsed(string value)
settings.UploadFlushIntervalMilliseconds.Should().Be(0);
}
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("False")]
+ [InlineData("false")]
+ [InlineData("0")]
+ [InlineData("2")]
+ [InlineData(null)]
+ public void CodeOriginEnabled_False(string value)
+ {
+ var settings = new DebuggerSettings(
+ new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, value }, }),
+ NullConfigurationTelemetry.Instance);
+
+ settings.CodeOriginForSpansEnabled.Should().BeFalse();
+ }
+
+ [Theory]
+ [InlineData("True")]
+ [InlineData("true")]
+ [InlineData("1")]
+ public void CodeOriginEnabled_True(string value)
+ {
+ var settings = new DebuggerSettings(
+ new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, value }, }),
+ NullConfigurationTelemetry.Instance);
+
+ settings.CodeOriginForSpansEnabled.Should().BeTrue();
+ }
+
+ [Theory]
+ [InlineData("8")]
+ [InlineData("1")]
+ [InlineData("1000")]
+ public void CodeOriginMaxUserFrames(string value)
+ {
+ var settings = new DebuggerSettings(
+ new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginMaxUserFrames, value }, }),
+ NullConfigurationTelemetry.Instance);
+
+ settings.CodeOriginMaxUserFrames.Should().Be(int.Parse(value));
+ }
+
+ [Theory]
+ [InlineData("-1")]
+ [InlineData("0")]
+ [InlineData("")]
+ [InlineData(null)]
+ public void InvalidCodeOriginMaxUserFrames_DefaultUsed(string value)
+ {
+ var settings = new DebuggerSettings(
+ new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginMaxUserFrames, value }, }),
+ NullConfigurationTelemetry.Instance);
+
+ settings.CodeOriginMaxUserFrames.Should().Be(8);
+ }
}
}
diff --git a/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs b/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs
new file mode 100644
index 000000000000..4c59ebf6055b
--- /dev/null
+++ b/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs
@@ -0,0 +1,118 @@
+//
+// 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.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Datadog.Trace.Configuration;
+using Datadog.Trace.Configuration.Telemetry;
+using Datadog.Trace.Debugger;
+using Datadog.Trace.Debugger.SpanCodeOrigin;
+using Datadog.Trace.VendoredMicrosoftCode.System.Collections.Immutable;
+using Xunit;
+
+namespace Datadog.Trace.Tests.Debugger
+{
+ public class SpanCodeOriginTests
+ {
+ private const string CodeOriginTag = "_dd.code_origin";
+
+ [Fact]
+ public void SetCodeOrigin_WhenSpanIsNull_DoesNotThrow()
+ {
+ // Should not throw
+ SpanCodeOriginManager.Instance.SetCodeOrigin(null);
+ }
+
+ [Fact]
+ public void SetCodeOrigin_WhenDisabled_DoesNotSetTags()
+ {
+ // Arrange
+ CreateCodeOriginManager();
+
+ var span = new Span(new SpanContext(1, 2, SamplingPriority.UserKeep), DateTimeOffset.UtcNow);
+
+ // Act
+ SpanCodeOriginManager.Instance.SetCodeOrigin(span);
+
+ // Assert
+ Assert.Null(span.Tags.GetTag(CodeOriginTag + ".type"));
+ }
+
+ [Fact]
+ public void SetCodeOrigin_WhenEnabled_SetsCorrectTags()
+ {
+ // Arrange
+ CreateCodeOriginManager(true);
+
+ var span = new Span(new SpanContext(1, 2, SamplingPriority.UserKeep), DateTimeOffset.UtcNow);
+
+ // Act
+ TestMethod(span);
+
+ // Assert
+ Assert.NotNull(span.Tags.GetTag($"{CodeOriginTag}.type"));
+ Assert.Equal("exit", span.Tags.GetTag($"{CodeOriginTag}.type"));
+
+ Assert.NotNull(span.Tags.GetTag($"{CodeOriginTag}.frames.0.method"));
+ Assert.Equal(nameof(TestMethod), span.Tags.GetTag($"{CodeOriginTag}.frames.0.method"));
+ Assert.NotNull(span.Tags.GetTag($"{CodeOriginTag}.frames.0.type"));
+ Assert.Contains(nameof(SpanCodeOriginTests), span.Tags.GetTag($"{CodeOriginTag}.frames.0.type"));
+ }
+
+ [Fact]
+ public void SetCodeOrigin_WithMaxFramesLimit_RespectsLimit()
+ {
+ // Arrange
+ CreateCodeOriginManager(true, 2);
+
+ var span = new Span(new SpanContext(1, 2, SamplingPriority.UserKeep), DateTimeOffset.UtcNow);
+
+ // Act
+ DeepTestMethod1(span);
+
+ // Assert
+ var tags = ((List>)(typeof(Datadog.Trace.Tagging.TagsList).GetField("_tags", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(span.Tags))).Select(i => i.Key).ToList();
+ Assert.Contains(tags, s => s.StartsWith($"{CodeOriginTag}.frames.0"));
+ Assert.Contains(tags, s => s.StartsWith($"{CodeOriginTag}.frames.1"));
+ Assert.DoesNotContain(tags, s => s.StartsWith($"{CodeOriginTag}.frames.2"));
+ }
+
+ private static void CreateCodeOriginManager(bool isEnable = false, int numberOfFrames = 8, string excludeFromFilter = "Datadog.Trace.Tests")
+ {
+ var overrideSettings = DebuggerSettings.FromSource(
+ new NameValueConfigurationSource(new()
+ {
+ { ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, isEnable.ToString() },
+ { ConfigurationKeys.Debugger.CodeOriginMaxUserFrames, numberOfFrames.ToString() },
+ { ConfigurationKeys.Debugger.ThirdPartyDetectionExcludes, excludeFromFilter }
+ }),
+ NullConfigurationTelemetry.Instance);
+ var instance = SpanCodeOriginManager.Instance;
+ instance.GetType().GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(instance, overrideSettings);
+ }
+
+ private void TestMethod(Span span)
+ {
+ SpanCodeOriginManager.Instance.SetCodeOrigin(span);
+ }
+
+ private void DeepTestMethod1(Span span)
+ {
+ DeepTestMethod2(span);
+ }
+
+ private void DeepTestMethod2(Span span)
+ {
+ DeepTestMethod3(span);
+ }
+
+ private void DeepTestMethod3(Span span)
+ {
+ SpanCodeOriginManager.Instance.SetCodeOrigin(span);
+ }
+ }
+}