From d92172c7b1306e6192b6d2ceb3b7a22799def146 Mon Sep 17 00:00:00 2001 From: dudik Date: Fri, 25 Oct 2024 01:03:09 +0200 Subject: [PATCH 01/16] base implementation of code origin for exit span --- .../ConfigurationKeys.Debugger.cs | 14 + .../Configurations/Models/SpanOriginProbe.cs | 62 +++++ .../Debugger/DebuggerSettings.cs | 16 +- .../Debugger/Expressions/Enums.cs | 3 +- .../Debugger/Expressions/ProbeProcessor.cs | 19 +- .../SpanCodeOrigin/SpanCodeOriginManager.cs | 255 ++++++++++++++++++ tracer/src/Datadog.Trace/TraceContext.cs | 5 + 7 files changed, 368 insertions(+), 6 deletions(-) create mode 100644 tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs create mode 100644 tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs index 18358b29ea6c..e8e88525ca51 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs @@ -146,6 +146,20 @@ internal static class Debugger /// /// public const string MaxExceptionAnalysisLimit = "DD_EXCEPTION_REPLAY_MAX_EXCEPTION_ANALYSIS_LIMIT"; + + /// + /// Configuration key to enable tag code origin for span. + /// Default value is false. + /// + /// + public const string CodeOriginForSpansEnabled = "DD_CODE_ORIGIN_FOR_SPANS_ENABLED"; + + /// + /// Configuration key for setting the number of frames to be tagged in exit span code origin. + /// Default value is 8. + /// + /// + public const string CodeOriginMaxUserFrames = "DD_CODE_ORIGIN_MAX_USER_FRAMES"; } } } diff --git a/tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs b/tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs new file mode 100644 index 000000000000..cc5fcc4ab686 --- /dev/null +++ b/tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs @@ -0,0 +1,62 @@ +// +// 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; + +#nullable enable + +namespace Datadog.Trace.Debugger.Configurations.Models +{ + internal enum SpanOriginKind + { + Entry, + Exit + } + + internal class SpanOriginProbe : ProbeDefinition, IEquatable + { + private SpanOriginKind Kind { get; set; } + + public bool Equals(SpanOriginProbe other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) && Kind == other.Kind; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((SpanOriginProbe)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(base.GetHashCode(), (int)Kind); + } + } +} diff --git a/tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs b/tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs index 1b8019d923c3..9e803dbd88b3 100644 --- a/tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs +++ b/tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs @@ -27,6 +27,7 @@ internal class DebuggerSettings public const int DefaultSymbolBatchSizeInBytes = 100000; private const int DefaultDiagnosticsIntervalSeconds = 60 * 60; // 1 hour private const int DefaultUploadFlushIntervalMilliseconds = 0; + public const int DefaultCodeOriginExitSpanFrames = 8; public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry telemetry) { @@ -80,7 +81,7 @@ public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry te .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty(); - SymDbThirdPartyDetectionIncludes = new HashSet([..symDb3rdPartyIncludeLibraries, ..ThirdPartyDetectionIncludes]).ToImmutableHashSet(); + SymDbThirdPartyDetectionIncludes = new HashSet([.. symDb3rdPartyIncludeLibraries, .. ThirdPartyDetectionIncludes]).ToImmutableHashSet(); var symDb3rdPartyExcludeLibraries = config .WithKeys(ConfigurationKeys.Debugger.SymDbThirdPartyDetectionExcludes) @@ -88,7 +89,7 @@ public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry te .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty(); - SymDbThirdPartyDetectionExcludes = new HashSet([..symDb3rdPartyExcludeLibraries, ..ThirdPartyDetectionExcludes]).ToImmutableHashSet(); + SymDbThirdPartyDetectionExcludes = new HashSet([.. symDb3rdPartyExcludeLibraries, .. ThirdPartyDetectionExcludes]).ToImmutableHashSet(); DiagnosticsIntervalSeconds = config .WithKeys(ConfigurationKeys.Debugger.DiagnosticsInterval) @@ -115,6 +116,13 @@ public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry te Enumerable.Empty(); RedactedTypes = new HashSet(redactedTypes, StringComparer.OrdinalIgnoreCase); + + CodeOriginForSpansEnabled = config.WithKeys(ConfigurationKeys.Debugger.CodeOriginForSpansEnabled).AsBool(false); + + CodeOriginMaxUserFrames = config + .WithKeys(ConfigurationKeys.Debugger.CodeOriginMaxUserFrames) + .AsInt32(DefaultCodeOriginExitSpanFrames, frames => frames > 0) + .Value; } public bool Enabled { get; } @@ -145,6 +153,10 @@ public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry te public HashSet RedactedTypes { get; } + public bool CodeOriginForSpansEnabled { get; } + + public int CodeOriginMaxUserFrames { get; } + public static DebuggerSettings FromSource(IConfigurationSource source, IConfigurationTelemetry telemetry) { return new DebuggerSettings(source, telemetry); diff --git a/tracer/src/Datadog.Trace/Debugger/Expressions/Enums.cs b/tracer/src/Datadog.Trace/Debugger/Expressions/Enums.cs index 86da631325b6..67c137446564 100644 --- a/tracer/src/Datadog.Trace/Debugger/Expressions/Enums.cs +++ b/tracer/src/Datadog.Trace/Debugger/Expressions/Enums.cs @@ -10,7 +10,8 @@ internal enum ProbeType Log = 0, Snapshot = 1, Metric = 2, - SpanDecoration = 3 + SpanDecoration = 3, + SpanOrigin = 4 } internal enum ProbeLocation diff --git a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs index 9b787faec353..a78b9edf3fd8 100644 --- a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs +++ b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs @@ -56,6 +56,7 @@ private void InitializeProbeProcessor(ProbeDefinition probe) LogProbe { CaptureSnapshot: false } => ProbeType.Log, MetricProbe => ProbeType.Metric, SpanDecorationProbe => ProbeType.SpanDecoration, + SpanOriginProbe => ProbeType.SpanOrigin, _ => throw new ArgumentOutOfRangeException(nameof(probe), probe, "Unsupported probe type") }; @@ -342,7 +343,9 @@ private ExpressionEvaluationResult Evaluate(DebuggerSnapshotCreator snapshotCrea return evaluationResult; } - CheckSpanDecoration(snapshotCreator, ref shouldStopCapture, evaluationResult); + SetCodeOrigin(); + + SetSpanDecoration(snapshotCreator, ref shouldStopCapture, evaluationResult); if (evaluationResult.Metric.HasValue) { @@ -370,7 +373,17 @@ private ExpressionEvaluationResult Evaluate(DebuggerSnapshotCreator snapshotCrea return evaluationResult; } - private void CheckSpanDecoration(DebuggerSnapshotCreator snapshotCreator, ref bool shouldStopCapture, ExpressionEvaluationResult evaluationResult) + private void SetCodeOrigin(DebuggerSnapshotCreator snapshotCreator, ref bool shouldStopCapture) + { + var probeTag = Tracer.Instance.ScopeManager.Active.Root.Span.GetTag(ProbeInfo.ProbeId); + if (probeTag == null) + { + snapshotCreator.Dispose(); + shouldStopCapture = true; + } + } + + private void SetSpanDecoration(DebuggerSnapshotCreator snapshotCreator, ref bool shouldStopCapture, ExpressionEvaluationResult evaluationResult) { if (evaluationResult.Decorations == null) { @@ -393,7 +406,7 @@ private void CheckSpanDecoration(DebuggerSnapshotCreator snapshotCreator, ref bo { Tracer.Instance.ScopeManager.Active.Root.Span.SetTag(evaluationErrorTag, string.Join(";", decoration.Errors)); } - else if (Tracer.Instance.ScopeManager.Active.Span.GetTag(evaluationErrorTag) != null) + else if (Tracer.Instance.ScopeManager.Active.Root.Span.GetTag(evaluationErrorTag) != null) { Tracer.Instance.ScopeManager.Active.Root.Span.SetTag(evaluationErrorTag, null); } diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs new file mode 100644 index 000000000000..12a0d59b37a9 --- /dev/null +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -0,0 +1,255 @@ +// +// 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. +// +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; +using Datadog.Trace.Debugger.Configurations.Models; +using Datadog.Trace.Debugger.Symbols; +using Datadog.Trace.Logging; +using Datadog.Trace.Util; +using Datadog.Trace.VendoredMicrosoftCode.System.Buffers; + +namespace Datadog.Trace.Debugger.SpanCodeOrigin +{ + internal class SpanCodeOriginManager + { + private const string CodeOriginTag = "_dd.code_origin"; + private static readonly DebuggerSettings Settings = LiveDebugger.Instance?.Settings ?? DebuggerSettings.FromDefaultSource(); + private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(SpanCodeOriginManager)); + + private static readonly HashSet ExitSpanTypes = + [ + SpanTypes.Http, + SpanTypes.Sql, + SpanTypes.Redis, + SpanTypes.MongoDb, + SpanTypes.DynamoDb, + SpanTypes.Db + ]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsEntrySpan(ISpan? span) + { + if (span == null) + { + return false; + } + + if (span.Type == SpanTypes.Web) + { + return true; + } + + if (IsEntryOperation(span.OperationName)) + { + return true; + } + + var kind = span?.GetTag(Tags.SpanKind); + return kind is SpanKinds.Server or SpanKinds.Consumer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsExitSpan(ISpan span) + { + if (span.Type != null && ExitSpanTypes.Contains(span.Type)) + { + return true; + } + + if (IsExitOperation(span.OperationName)) + { + return true; + } + + var kind = span?.GetTag(Tags.SpanKind); + return kind is SpanKinds.Client or SpanKinds.Producer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsEntryOperation(string? operationName) + { + if (string.IsNullOrEmpty(operationName)) + { + return false; + } + + return operationName?.StartsWith("aspnet.", StringComparison.OrdinalIgnoreCase) == true || + operationName?.StartsWith("aspnetcore.", StringComparison.OrdinalIgnoreCase) == true || + operationName?.StartsWith("grpc.server.", StringComparison.OrdinalIgnoreCase) == true || + operationName?.StartsWith("webapi.", StringComparison.OrdinalIgnoreCase) == true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsExitOperation(string? operationName) + { + if (string.IsNullOrEmpty(operationName)) + { + return false; + } + + return operationName?.StartsWith("http.client.", StringComparison.OrdinalIgnoreCase) == true || + operationName?.StartsWith("sql.", StringComparison.OrdinalIgnoreCase) == true || + operationName?.StartsWith("redis.", StringComparison.OrdinalIgnoreCase) == true || + operationName?.StartsWith("mongodb.", StringComparison.OrdinalIgnoreCase) == true || + operationName?.StartsWith("rabbitmq.client.", StringComparison.OrdinalIgnoreCase) == true; + } + + internal static void SetCodeOrigin(ISpan? span, ISpan rootSpan) + { + if (span == null || !Settings.CodeOriginForSpansEnabled) + { + return; + } + + if (IsExitSpan(span)) + { + CaptureExitSpanLocation(span, rootSpan); + } + } + + private static void CaptureExitSpanLocation(ISpan span, ISpan rootSpan) + { + var frames = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); + var probes = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); + var sb = StringBuilderCache.Acquire(StringBuilderCache.MaxBuilderSize); + try + { + CaptureUserFrames(frames); + sb.Append("{\"type\":\"").Append("exit").Append("\",\"frames\":["); + + for (int i = 0; i < frames.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + AppendFrame(sb, frames[i]); + var probe = CreateSpanOriginProbe(frames[i]); + if (probe != null) + { + probes[i] = probe; + rootSpan.SetTag(probe.Id, string.Empty); + } + } + + sb.Append("]}"); + + span.SetTag(CodeOriginTag, sb.ToString()); + + LiveDebugger.Instance.UpdateAddedProbeInstrumentations(probes); + } + catch (Exception ex) + { + Log.Error(ex, ""); + } + finally + { + if (sb != null) + { + StringBuilderCache.Release(sb); + } + + if (frames != null) + { + ArrayPool.Shared.Return(frames, true); + } + + if (probes != null) + { + ArrayPool.Shared.Return(probes, true); + } + } + } + + private static SpanOriginProbe? CreateSpanOriginProbe(StackFrame frame) + { + var method = frame.GetMethod(); + var type = method.DeclaringType; + if (type == null) + { + return null; + } + + return new SpanOriginProbe + { + Id = Guid.NewGuid().ToString(), + EvaluateAt = EvaluateAt.Exit, + Where = new Where + { + MethodName = method.Name, + TypeName = type.FullName ?? type.Name, + }, + }; + } + + private static void CaptureUserFrames(StackFrame[] frames) + { + var stackTrace = new StackTrace(true); + var stackFrames = stackTrace.GetFrames(); + + if (stackFrames == null) + { + return; + } + + int count = 0; + for (int i = 3; i < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; i++) + { + var frame = stackFrames[i]; + + var assembly = frame.GetMethod()?.DeclaringType?.Module.Assembly; + if (assembly == null) + { + continue; + } + + if (AssemblyFilter.ShouldSkipAssembly(assembly, LiveDebugger.Instance.Settings.ThirdPartyDetectionExcludes, LiveDebugger.Instance.Settings.ThirdPartyDetectionIncludes)) + { + // use cache when this will be merged: https://github.com/DataDog/dd-trace-dotnet/pull/6093 + continue; + } + + frames[count++] = frame; + } + } + + private static void AppendFrame(StringBuilder sb, StackFrame frame) + { + sb.Append('{'); + + var fileName = frame.GetFileName(); + if (!string.IsNullOrEmpty(fileName)) + { + sb.Append("\"file\":\"").Append(fileName.Replace("\\", "\\\\")).Append("\","); + sb.Append("\"line\":").Append(frame.GetFileLineNumber()); + + int column = frame.GetFileColumnNumber(); + if (column > 0) + { + sb.Append(",\"column\":").Append(column); + } + } + + var method = frame.GetMethod(); + if (method != null) + { + sb.Append(",\"method\":\"").Append(method.Name).Append("\""); + + if (method.DeclaringType != null) + { + sb.Append(",\"type\":\"").Append(method.DeclaringType.FullName).Append("\""); + } + } + + sb.Append('}'); + } + } +} diff --git a/tracer/src/Datadog.Trace/TraceContext.cs b/tracer/src/Datadog.Trace/TraceContext.cs index 3a6bccbee2ae..4700f2935083 100644 --- a/tracer/src/Datadog.Trace/TraceContext.cs +++ b/tracer/src/Datadog.Trace/TraceContext.cs @@ -15,6 +15,7 @@ using Datadog.Trace.ClrProfiler; using Datadog.Trace.Configuration; using Datadog.Trace.ContinuousProfiler; +using Datadog.Trace.Debugger.SpanCodeOrigin; using Datadog.Trace.Iast; using Datadog.Trace.Logging; using Datadog.Trace.Sampling; @@ -136,6 +137,10 @@ public void AddSpan(Span span) { span.MarkSpanForExceptionDebugging(); } + else + { + SpanCodeOriginManager.SetCodeOrigin(span, _rootSpan); + } lock (_rootSpan) { From 1729fc705cc92376c969117d57e7e790c185f1c7 Mon Sep 17 00:00:00 2001 From: dudik Date: Fri, 25 Oct 2024 19:24:44 +0200 Subject: [PATCH 02/16] wip --- .../Debugger/Expressions/ProbeProcessor.cs | 2 +- .../SpanCodeOrigin/SpanCodeOriginManager.cs | 187 +++++++++++------- .../Datadog.Trace/Util/StringBuilderCache.cs | 2 +- 3 files changed, 113 insertions(+), 78 deletions(-) diff --git a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs index a78b9edf3fd8..54d507c05e50 100644 --- a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs +++ b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs @@ -343,7 +343,7 @@ private ExpressionEvaluationResult Evaluate(DebuggerSnapshotCreator snapshotCrea return evaluationResult; } - SetCodeOrigin(); + SetCodeOrigin(snapshotCreator, ref shouldStopCapture); SetSpanDecoration(snapshotCreator, ref shouldStopCapture, evaluationResult); diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 12a0d59b37a9..5578e990cba1 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -101,6 +101,7 @@ private static bool IsExitOperation(string? operationName) operationName?.StartsWith("rabbitmq.client.", StringComparison.OrdinalIgnoreCase) == true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void SetCodeOrigin(ISpan? span, ISpan rootSpan) { if (span == null || !Settings.CodeOriginForSpansEnabled) @@ -110,98 +111,56 @@ internal static void SetCodeOrigin(ISpan? span, ISpan rootSpan) if (IsExitSpan(span)) { - CaptureExitSpanLocation(span, rootSpan); + AddExitSpanTag(span); } } - private static void CaptureExitSpanLocation(ISpan span, ISpan rootSpan) + private static void AddExitSpanTag(ISpan span) { var frames = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); - var probes = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); - var sb = StringBuilderCache.Acquire(StringBuilderCache.MaxBuilderSize); try { - CaptureUserFrames(frames); - sb.Append("{\"type\":\"").Append("exit").Append("\",\"frames\":["); - - for (int i = 0; i < frames.Length; i++) + var framesLength = PopulateUserFrames(frames); + if (framesLength == 0) { - if (i > 0) - { - sb.Append(','); - } - - AppendFrame(sb, frames[i]); - var probe = CreateSpanOriginProbe(frames[i]); - if (probe != null) - { - probes[i] = probe; - rootSpan.SetTag(probe.Id, string.Empty); - } + Log.Warning("No user frames has founded"); + return; } - sb.Append("]}"); - - span.SetTag(CodeOriginTag, sb.ToString()); + var tagValue = CreateExitTagValue(frames); + if (string.IsNullOrEmpty(tagValue)) + { + Log.Error("Failed to create tag for {CodeOriginTag}", CodeOriginTag); + return; + } - LiveDebugger.Instance.UpdateAddedProbeInstrumentations(probes); + span.SetTag(CodeOriginTag, tagValue); } catch (Exception ex) { - Log.Error(ex, ""); + Log.Error(ex, "Failed to create exit span tag for {Span}", span.SpanId); } finally { - if (sb != null) - { - StringBuilderCache.Release(sb); - } - if (frames != null) { ArrayPool.Shared.Return(frames, true); } - - if (probes != null) - { - ArrayPool.Shared.Return(probes, true); - } } } - private static SpanOriginProbe? CreateSpanOriginProbe(StackFrame frame) - { - var method = frame.GetMethod(); - var type = method.DeclaringType; - if (type == null) - { - return null; - } - - return new SpanOriginProbe - { - Id = Guid.NewGuid().ToString(), - EvaluateAt = EvaluateAt.Exit, - Where = new Where - { - MethodName = method.Name, - TypeName = type.FullName ?? type.Name, - }, - }; - } - - private static void CaptureUserFrames(StackFrame[] frames) + private static int PopulateUserFrames(StackFrame[] frames) { var stackTrace = new StackTrace(true); var stackFrames = stackTrace.GetFrames(); if (stackFrames == null) { - return; + return 0; } - int count = 0; - for (int i = 3; i < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; i++) + var count = 0; + for (var i = 3; i < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; i++) { var frame = stackFrames[i]; @@ -219,37 +178,113 @@ private static void CaptureUserFrames(StackFrame[] frames) frames[count++] = frame; } + + return count; } - private static void AppendFrame(StringBuilder sb, StackFrame frame) + private static string CreateExitTagValue(StackFrame[] frames) { - sb.Append('{'); + var sb = StringBuilderCache.Acquire(StringBuilderCache.MaxBuilderSize); + try + { + sb.Append("{\"type\":\"").Append("exit").Append("\",\"frames\":["); + for (var i = 0; i < frames.Length; i++) + { + if (i > 0) + { + sb.Append(','); + } + + AppendFrame(frames[i]); + } + sb.Append("]}"); + } + catch (Exception) + { + StringBuilderCache.Release(sb); + } + + return StringBuilderCache.GetStringAndRelease(sb); - var fileName = frame.GetFileName(); - if (!string.IsNullOrEmpty(fileName)) + void AppendFrame(StackFrame frame) { - sb.Append("\"file\":\"").Append(fileName.Replace("\\", "\\\\")).Append("\","); - sb.Append("\"line\":").Append(frame.GetFileLineNumber()); + sb.Append('{'); - int column = frame.GetFileColumnNumber(); - if (column > 0) + var fileName = frame.GetFileName(); + if (!string.IsNullOrEmpty(fileName)) { - sb.Append(",\"column\":").Append(column); + sb.Append("\"file\":\"").Append(fileName.Replace("\\", "\\\\")).Append("\","); + sb.Append("\"line\":").Append(frame.GetFileLineNumber()); + + int column = frame.GetFileColumnNumber(); + if (column > 0) + { + sb.Append(",\"column\":").Append(column); + } + } + + var method = frame.GetMethod(); + if (method != null) + { + sb.Append(",\"method\":\"").Append(method.Name).Append("\""); + + if (method.DeclaringType != null) + { + sb.Append(",\"type\":\"").Append(method.DeclaringType.FullName).Append("\""); + } } + + sb.Append(",\"snapshot_id\":\"").Append("snapshot_id_placeholder").Append("\""); + + sb.Append('}'); } + } - var method = frame.GetMethod(); - if (method != null) + private static void InstrumentSpanOriginProbes(StackFrame[] frames, ISpan rootSpan) + { + var probes = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); + try { - sb.Append(",\"method\":\"").Append(method.Name).Append("\""); - - if (method.DeclaringType != null) + for (var i = 0; i < frames.Length; i++) { - sb.Append(",\"type\":\"").Append(method.DeclaringType.FullName).Append("\""); + var probe = CreateSpanOriginProbe(frames[i]); + if (probe != null) + { + probes[i] = probe; + rootSpan.SetTag(probe.Id, string.Empty); + } + } + } + finally + { + if (probes != null) + { + ArrayPool.Shared.Return(probes, true); } } - sb.Append('}'); + LiveDebugger.Instance.UpdateAddedProbeInstrumentations(probes); + } + + private static SpanOriginProbe? CreateSpanOriginProbe(StackFrame frame) + { + var method = frame.GetMethod(); + var type = method.DeclaringType; + if (type == null) + { + return null; + } + + return new SpanOriginProbe + { + Id = Guid.NewGuid().ToString(), + EvaluateAt = EvaluateAt.Exit, + Where = new Where + { + MethodName = method.Name, + TypeName = type.FullName ?? type.Name, + }, + }; } } } diff --git a/tracer/src/Datadog.Trace/Util/StringBuilderCache.cs b/tracer/src/Datadog.Trace/Util/StringBuilderCache.cs index caaecf84e4de..7d3d4fa664f1 100644 --- a/tracer/src/Datadog.Trace/Util/StringBuilderCache.cs +++ b/tracer/src/Datadog.Trace/Util/StringBuilderCache.cs @@ -52,7 +52,7 @@ public static string GetStringAndRelease(StringBuilder sb) return result; } - public static void Release(StringBuilder sb) + public static void Release(StringBuilder? sb) { if (sb?.Capacity <= MaxBuilderSize) { From e3a29a2f628207c4b162663aae6ac726f5b8a5f0 Mon Sep 17 00:00:00 2001 From: dudik Date: Mon, 28 Oct 2024 12:24:12 +0100 Subject: [PATCH 03/16] Add tags for each span and fix tag format --- .../SpanCodeOrigin/SpanCodeOriginManager.cs | 187 ++++-------------- tracer/src/Datadog.Trace/TraceContext.cs | 4 - tracer/src/Datadog.Trace/Tracer.cs | 3 + 3 files changed, 37 insertions(+), 157 deletions(-) diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 5578e990cba1..933e6fb0a3ab 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -5,114 +5,30 @@ #nullable enable using System; -using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Text; using Datadog.Trace.Debugger.Configurations.Models; using Datadog.Trace.Debugger.Symbols; using Datadog.Trace.Logging; -using Datadog.Trace.Util; using Datadog.Trace.VendoredMicrosoftCode.System.Buffers; namespace Datadog.Trace.Debugger.SpanCodeOrigin { internal class SpanCodeOriginManager { - private const string CodeOriginTag = "_dd.code_origin"; + private const string CodeOriginTag = "_dd.code_origin_"; private static readonly DebuggerSettings Settings = LiveDebugger.Instance?.Settings ?? DebuggerSettings.FromDefaultSource(); private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(SpanCodeOriginManager)); - private static readonly HashSet ExitSpanTypes = - [ - SpanTypes.Http, - SpanTypes.Sql, - SpanTypes.Redis, - SpanTypes.MongoDb, - SpanTypes.DynamoDb, - SpanTypes.Db - ]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsEntrySpan(ISpan? span) - { - if (span == null) - { - return false; - } - - if (span.Type == SpanTypes.Web) - { - return true; - } - - if (IsEntryOperation(span.OperationName)) - { - return true; - } - - var kind = span?.GetTag(Tags.SpanKind); - return kind is SpanKinds.Server or SpanKinds.Consumer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsExitSpan(ISpan span) - { - if (span.Type != null && ExitSpanTypes.Contains(span.Type)) - { - return true; - } - - if (IsExitOperation(span.OperationName)) - { - return true; - } - - var kind = span?.GetTag(Tags.SpanKind); - return kind is SpanKinds.Client or SpanKinds.Producer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsEntryOperation(string? operationName) - { - if (string.IsNullOrEmpty(operationName)) - { - return false; - } - - return operationName?.StartsWith("aspnet.", StringComparison.OrdinalIgnoreCase) == true || - operationName?.StartsWith("aspnetcore.", StringComparison.OrdinalIgnoreCase) == true || - operationName?.StartsWith("grpc.server.", StringComparison.OrdinalIgnoreCase) == true || - operationName?.StartsWith("webapi.", StringComparison.OrdinalIgnoreCase) == true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsExitOperation(string? operationName) - { - if (string.IsNullOrEmpty(operationName)) - { - return false; - } - - return operationName?.StartsWith("http.client.", StringComparison.OrdinalIgnoreCase) == true || - operationName?.StartsWith("sql.", StringComparison.OrdinalIgnoreCase) == true || - operationName?.StartsWith("redis.", StringComparison.OrdinalIgnoreCase) == true || - operationName?.StartsWith("mongodb.", StringComparison.OrdinalIgnoreCase) == true || - operationName?.StartsWith("rabbitmq.client.", StringComparison.OrdinalIgnoreCase) == true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void SetCodeOrigin(ISpan? span, ISpan rootSpan) + internal static void SetCodeOrigin(ISpan? span) { if (span == null || !Settings.CodeOriginForSpansEnabled) { return; } - if (IsExitSpan(span)) - { - AddExitSpanTag(span); - } + AddExitSpanTag(span); } private static void AddExitSpanTag(ISpan span) @@ -127,14 +43,37 @@ private static void AddExitSpanTag(ISpan span) return; } - var tagValue = CreateExitTagValue(frames); - if (string.IsNullOrEmpty(tagValue)) + span.SetTag($"{CodeOriginTag}.Type", "exit"); + + for (int i = 0; i < framesLength; i++) { - Log.Error("Failed to create tag for {CodeOriginTag}", CodeOriginTag); - return; - } + var frame = frames[i]; + var fileName = frame.GetFileName(); // todo: should we normalize? + if (!string.IsNullOrEmpty(fileName)) + { + span.SetTag($"{CodeOriginTag}.frame.{i}.file", fileName); + } - span.SetTag(CodeOriginTag, tagValue); + var line = frame.GetFileLineNumber(); + if (line > 0) + { + span.SetTag($"{CodeOriginTag}.frame.{i}.line", line); + } + + int column = frame.GetFileColumnNumber(); + if (column > 0) + { + span.SetTag($"{CodeOriginTag}.frame.{i}.column", column); + } + + var method = frame.GetMethod(); + var type = method.DeclaringType?.FullName ?? method.DeclaringType?.Name; + if (!string.IsNullOrEmpty(type)) + { + span.SetTag($"{CodeOriginTag}.frame.{i}.method", method.Name); + span.SetTag($"{CodeOriginTag}.frame.{i}.type", type); + } + } } catch (Exception ex) { @@ -160,9 +99,9 @@ private static int PopulateUserFrames(StackFrame[] frames) } var count = 0; - for (var i = 3; i < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; i++) + for (int walkIndex = 2; walkIndex < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; walkIndex++) { - var frame = stackFrames[i]; + var frame = stackFrames[walkIndex]; var assembly = frame.GetMethod()?.DeclaringType?.Module.Assembly; if (assembly == null) @@ -182,64 +121,6 @@ private static int PopulateUserFrames(StackFrame[] frames) return count; } - private static string CreateExitTagValue(StackFrame[] frames) - { - var sb = StringBuilderCache.Acquire(StringBuilderCache.MaxBuilderSize); - try - { - sb.Append("{\"type\":\"").Append("exit").Append("\",\"frames\":["); - for (var i = 0; i < frames.Length; i++) - { - if (i > 0) - { - sb.Append(','); - } - - AppendFrame(frames[i]); - } - sb.Append("]}"); - } - catch (Exception) - { - StringBuilderCache.Release(sb); - } - - return StringBuilderCache.GetStringAndRelease(sb); - - void AppendFrame(StackFrame frame) - { - sb.Append('{'); - - var fileName = frame.GetFileName(); - if (!string.IsNullOrEmpty(fileName)) - { - sb.Append("\"file\":\"").Append(fileName.Replace("\\", "\\\\")).Append("\","); - sb.Append("\"line\":").Append(frame.GetFileLineNumber()); - - int column = frame.GetFileColumnNumber(); - if (column > 0) - { - sb.Append(",\"column\":").Append(column); - } - } - - var method = frame.GetMethod(); - if (method != null) - { - sb.Append(",\"method\":\"").Append(method.Name).Append("\""); - - if (method.DeclaringType != null) - { - sb.Append(",\"type\":\"").Append(method.DeclaringType.FullName).Append("\""); - } - } - - sb.Append(",\"snapshot_id\":\"").Append("snapshot_id_placeholder").Append("\""); - - sb.Append('}'); - } - } - private static void InstrumentSpanOriginProbes(StackFrame[] frames, ISpan rootSpan) { var probes = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); diff --git a/tracer/src/Datadog.Trace/TraceContext.cs b/tracer/src/Datadog.Trace/TraceContext.cs index 4700f2935083..8c1f9875f313 100644 --- a/tracer/src/Datadog.Trace/TraceContext.cs +++ b/tracer/src/Datadog.Trace/TraceContext.cs @@ -137,10 +137,6 @@ public void AddSpan(Span span) { span.MarkSpanForExceptionDebugging(); } - else - { - SpanCodeOriginManager.SetCodeOrigin(span, _rootSpan); - } lock (_rootSpan) { diff --git a/tracer/src/Datadog.Trace/Tracer.cs b/tracer/src/Datadog.Trace/Tracer.cs index e71634d7a3df..7175c9d6a57c 100644 --- a/tracer/src/Datadog.Trace/Tracer.cs +++ b/tracer/src/Datadog.Trace/Tracer.cs @@ -12,6 +12,7 @@ using Datadog.Trace.Agent.DiscoveryService; using Datadog.Trace.ClrProfiler; using Datadog.Trace.Configuration; +using Datadog.Trace.Debugger.SpanCodeOrigin; using Datadog.Trace.Logging.TracerFlare; using Datadog.Trace.Sampling; using Datadog.Trace.SourceGenerators; @@ -553,6 +554,8 @@ internal Span StartSpan(string operationName, ITags tags = null, ISpanContext pa // write them directly to the . TracerManager.GitMetadataTagsProvider.TryExtractGitMetadata(out _); + SpanCodeOriginManager.SetCodeOrigin(span); + return span; } From 6ff1d29789f0efa25e5960033fb2d48ee7683d1d Mon Sep 17 00:00:00 2001 From: dudik Date: Mon, 28 Oct 2024 14:08:15 +0100 Subject: [PATCH 04/16] Fix errors --- .../ConfigurationKeys.Debugger.cs | 2 +- .../Configurations/Models/SpanOriginProbe.cs | 2 +- .../SpanCodeOrigin/SpanCodeOriginManager.cs | 34 +++++++++++-------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs index e8e88525ca51..8656dd9bb3e2 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs @@ -153,7 +153,7 @@ internal static class Debugger /// /// public const string CodeOriginForSpansEnabled = "DD_CODE_ORIGIN_FOR_SPANS_ENABLED"; - + /// /// Configuration key for setting the number of frames to be tagged in exit span code origin. /// Default value is 8. diff --git a/tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs b/tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs index cc5fcc4ab686..9efc766ceba3 100644 --- a/tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs +++ b/tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs @@ -19,7 +19,7 @@ internal class SpanOriginProbe : ProbeDefinition, IEquatable { private SpanOriginKind Kind { get; set; } - public bool Equals(SpanOriginProbe other) + public bool Equals(SpanOriginProbe? other) { if (ReferenceEquals(null, other)) { diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 933e6fb0a3ab..15d54c45a3c4 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -6,6 +6,7 @@ using System; using System.Diagnostics; +using System.Reflection; using System.Runtime.CompilerServices; using Datadog.Trace.Debugger.Configurations.Models; using Datadog.Trace.Debugger.Symbols; @@ -21,7 +22,7 @@ internal class SpanCodeOriginManager private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(SpanCodeOriginManager)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void SetCodeOrigin(ISpan? span) + internal static void SetCodeOrigin(Span? span) { if (span == null || !Settings.CodeOriginForSpansEnabled) { @@ -31,7 +32,7 @@ internal static void SetCodeOrigin(ISpan? span) AddExitSpanTag(span); } - private static void AddExitSpanTag(ISpan span) + private static void AddExitSpanTag(Span span) { var frames = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); try @@ -43,7 +44,7 @@ private static void AddExitSpanTag(ISpan span) return; } - span.SetTag($"{CodeOriginTag}.Type", "exit"); + span.Tags.SetTag($"{CodeOriginTag}.Type", "exit"); for (int i = 0; i < framesLength; i++) { @@ -51,27 +52,28 @@ private static void AddExitSpanTag(ISpan span) var fileName = frame.GetFileName(); // todo: should we normalize? if (!string.IsNullOrEmpty(fileName)) { - span.SetTag($"{CodeOriginTag}.frame.{i}.file", fileName); + span.Tags.SetTag($"{CodeOriginTag}.frame.{i}.file", fileName); } var line = frame.GetFileLineNumber(); if (line > 0) { - span.SetTag($"{CodeOriginTag}.frame.{i}.line", line); + span.Tags.SetTag($"{CodeOriginTag}.frame.{i}.line", line.ToString()); } int column = frame.GetFileColumnNumber(); if (column > 0) { - span.SetTag($"{CodeOriginTag}.frame.{i}.column", column); + span.Tags.SetTag($"{CodeOriginTag}.frame.{i}.column", column.ToString()); } - var method = frame.GetMethod(); + // PopulateUserFrames returns only frames that have method + var method = frame.GetMethod()!; var type = method.DeclaringType?.FullName ?? method.DeclaringType?.Name; if (!string.IsNullOrEmpty(type)) { - span.SetTag($"{CodeOriginTag}.frame.{i}.method", method.Name); - span.SetTag($"{CodeOriginTag}.frame.{i}.type", type); + span.Tags.SetTag($"{CodeOriginTag}.frame.{i}.method", method.Name); + span.Tags.SetTag($"{CodeOriginTag}.frame.{i}.type", type); } } } @@ -103,7 +105,7 @@ private static int PopulateUserFrames(StackFrame[] frames) { var frame = stackFrames[walkIndex]; - var assembly = frame.GetMethod()?.DeclaringType?.Module.Assembly; + var assembly = frame?.GetMethod()?.DeclaringType?.Module.Assembly; if (assembly == null) { continue; @@ -115,7 +117,7 @@ private static int PopulateUserFrames(StackFrame[] frames) continue; } - frames[count++] = frame; + frames[count++] = frame!; } return count; @@ -128,7 +130,7 @@ private static void InstrumentSpanOriginProbes(StackFrame[] frames, ISpan rootSp { for (var i = 0; i < frames.Length; i++) { - var probe = CreateSpanOriginProbe(frames[i]); + var probe = CreateSpanOriginProbe(frames[i].GetMethod()); if (probe != null) { probes[i] = probe; @@ -147,9 +149,13 @@ private static void InstrumentSpanOriginProbes(StackFrame[] frames, ISpan rootSp LiveDebugger.Instance.UpdateAddedProbeInstrumentations(probes); } - private static SpanOriginProbe? CreateSpanOriginProbe(StackFrame frame) + private static SpanOriginProbe? CreateSpanOriginProbe(MethodBase? method) { - var method = frame.GetMethod(); + if (method == null) + { + return null; + } + var type = method.DeclaringType; if (type == null) { From c0134d46f0fbbab521d8298db96a911079c0e38c Mon Sep 17 00:00:00 2001 From: dudik Date: Mon, 28 Oct 2024 17:35:09 +0100 Subject: [PATCH 05/16] change tag name --- .../Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 15d54c45a3c4..d4f383dd0531 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -17,7 +17,7 @@ namespace Datadog.Trace.Debugger.SpanCodeOrigin { internal class SpanCodeOriginManager { - private const string CodeOriginTag = "_dd.code_origin_"; + private const string CodeOriginTag = "_dd.code_origin"; private static readonly DebuggerSettings Settings = LiveDebugger.Instance?.Settings ?? DebuggerSettings.FromDefaultSource(); private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(SpanCodeOriginManager)); @@ -44,7 +44,7 @@ private static void AddExitSpanTag(Span span) return; } - span.Tags.SetTag($"{CodeOriginTag}.Type", "exit"); + span.Tags.SetTag($"{CodeOriginTag}.type", "exit"); for (int i = 0; i < framesLength; i++) { From cf698f342abd0aed511afb9eeca64a338c3de0fa Mon Sep 17 00:00:00 2001 From: dudik Date: Wed, 30 Oct 2024 08:47:22 +0100 Subject: [PATCH 06/16] change frame to frames --- .../Configuration/ConfigurationKeys.Debugger.cs | 2 +- .../SpanCodeOrigin/SpanCodeOriginManager.cs | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs index 8656dd9bb3e2..4124f5f0e2ca 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs @@ -62,7 +62,7 @@ internal static class Debugger public const string ThirdPartyDetectionIncludes = "DD_THIRD_PARTY_DETECTION_INCLUDES"; /// - /// Configuration key for a separated comma list of libraries to include in the 3rd party detection + /// Configuration key for a separated comma list of libraries to exclude for the 3rd party detection /// Default value is empty. /// public const string ThirdPartyDetectionExcludes = "DD_THIRD_PARTY_DETECTION_EXCLUDES"; diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index d4f383dd0531..58de7c8357be 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -5,6 +5,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; @@ -12,12 +13,14 @@ 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)); @@ -52,19 +55,19 @@ private static void AddExitSpanTag(Span span) var fileName = frame.GetFileName(); // todo: should we normalize? if (!string.IsNullOrEmpty(fileName)) { - span.Tags.SetTag($"{CodeOriginTag}.frame.{i}.file", fileName); + span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.file", fileName); } var line = frame.GetFileLineNumber(); if (line > 0) { - span.Tags.SetTag($"{CodeOriginTag}.frame.{i}.line", line.ToString()); + span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.line", line.ToString()); } int column = frame.GetFileColumnNumber(); if (column > 0) { - span.Tags.SetTag($"{CodeOriginTag}.frame.{i}.column", column.ToString()); + span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.column", column.ToString()); } // PopulateUserFrames returns only frames that have method @@ -72,8 +75,8 @@ private static void AddExitSpanTag(Span span) var type = method.DeclaringType?.FullName ?? method.DeclaringType?.Name; if (!string.IsNullOrEmpty(type)) { - span.Tags.SetTag($"{CodeOriginTag}.frame.{i}.method", method.Name); - span.Tags.SetTag($"{CodeOriginTag}.frame.{i}.type", type); + span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.method", method.Name); + span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.type", type); } } } From bc47a22b4d87458736032d041ee8f9acb994d13e Mon Sep 17 00:00:00 2001 From: dudik Date: Wed, 30 Oct 2024 10:44:02 +0100 Subject: [PATCH 07/16] Add frame index and clean code --- .../Debugger/Expressions/ProbeProcessor.cs | 12 --- .../SpanCodeOrigin/SpanCodeOriginManager.cs | 101 ++++++------------ 2 files changed, 32 insertions(+), 81 deletions(-) diff --git a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs index 54d507c05e50..ecddea90de8c 100644 --- a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs +++ b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs @@ -343,8 +343,6 @@ private ExpressionEvaluationResult Evaluate(DebuggerSnapshotCreator snapshotCrea return evaluationResult; } - SetCodeOrigin(snapshotCreator, ref shouldStopCapture); - SetSpanDecoration(snapshotCreator, ref shouldStopCapture, evaluationResult); if (evaluationResult.Metric.HasValue) @@ -373,16 +371,6 @@ private ExpressionEvaluationResult Evaluate(DebuggerSnapshotCreator snapshotCrea return evaluationResult; } - private void SetCodeOrigin(DebuggerSnapshotCreator snapshotCreator, ref bool shouldStopCapture) - { - var probeTag = Tracer.Instance.ScopeManager.Active.Root.Span.GetTag(ProbeInfo.ProbeId); - if (probeTag == null) - { - snapshotCreator.Dispose(); - shouldStopCapture = true; - } - } - private void SetSpanDecoration(DebuggerSnapshotCreator snapshotCreator, ref bool shouldStopCapture, ExpressionEvaluationResult evaluationResult) { if (evaluationResult.Decorations == null) diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 58de7c8357be..48b961927030 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -5,15 +5,11 @@ #nullable enable using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Reflection; using System.Runtime.CompilerServices; -using Datadog.Trace.Debugger.Configurations.Models; 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 { @@ -37,7 +33,7 @@ internal static void SetCodeOrigin(Span? span) private static void AddExitSpanTag(Span span) { - var frames = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); + var frames = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); try { var framesLength = PopulateUserFrames(frames); @@ -48,36 +44,40 @@ private static void AddExitSpanTag(Span span) } span.Tags.SetTag($"{CodeOriginTag}.type", "exit"); - - for (int i = 0; i < framesLength; i++) + for (var i = 0; i < framesLength; i++) { - var frame = frames[i]; - var fileName = frame.GetFileName(); // todo: should we normalize? + ref var info = ref frames[i]; + + // PopulateUserFrames returns only frames that have method + var method = info.Frame.GetMethod()!; + var type = method.DeclaringType?.FullName ?? method.DeclaringType?.Name; + if (string.IsNullOrEmpty(type)) + { + continue; + } + + span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.index", info.FrameIndex.ToString()); + span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.method", method.Name); + span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.type", type); + + var fileName = info.Frame.GetFileName(); if (!string.IsNullOrEmpty(fileName)) { + // todo: should we normalize? span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.file", fileName); } - var line = frame.GetFileLineNumber(); + var line = info.Frame.GetFileLineNumber(); if (line > 0) { span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.line", line.ToString()); } - int column = frame.GetFileColumnNumber(); + var column = info.Frame.GetFileColumnNumber(); if (column > 0) { span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.column", column.ToString()); } - - // PopulateUserFrames returns only frames that have method - var method = frame.GetMethod()!; - var type = method.DeclaringType?.FullName ?? method.DeclaringType?.Name; - if (!string.IsNullOrEmpty(type)) - { - span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.method", method.Name); - span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.type", type); - } } } catch (Exception ex) @@ -86,25 +86,27 @@ private static void AddExitSpanTag(Span span) } finally { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (frames != null) { - ArrayPool.Shared.Return(frames, true); + ArrayPool.Shared.Return(frames, true); } } } - private static int PopulateUserFrames(StackFrame[] frames) + private static int PopulateUserFrames(FrameInfo[] frames) { var stackTrace = new StackTrace(true); var stackFrames = stackTrace.GetFrames(); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (stackFrames == null) { return 0; } var count = 0; - for (int walkIndex = 2; walkIndex < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; walkIndex++) + for (var walkIndex = 2; walkIndex < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; walkIndex++) { var frame = stackFrames[walkIndex]; @@ -120,61 +122,22 @@ private static int PopulateUserFrames(StackFrame[] frames) continue; } - frames[count++] = frame!; + frames[count++] = new FrameInfo(walkIndex, frame!); } return count; } - private static void InstrumentSpanOriginProbes(StackFrame[] frames, ISpan rootSpan) - { - var probes = ArrayPool.Shared.Rent(Settings.CodeOriginMaxUserFrames); - try - { - for (var i = 0; i < frames.Length; i++) - { - var probe = CreateSpanOriginProbe(frames[i].GetMethod()); - if (probe != null) - { - probes[i] = probe; - rootSpan.SetTag(probe.Id, string.Empty); - } - } - } - finally - { - if (probes != null) - { - ArrayPool.Shared.Return(probes, true); - } - } - - LiveDebugger.Instance.UpdateAddedProbeInstrumentations(probes); - } - - private static SpanOriginProbe? CreateSpanOriginProbe(MethodBase? method) + private struct FrameInfo { - if (method == null) - { - return null; - } + internal readonly int FrameIndex; + internal readonly StackFrame Frame; - var type = method.DeclaringType; - if (type == null) + internal FrameInfo(int frameIndex, StackFrame frame) { - return null; + FrameIndex = frameIndex; + Frame = frame; } - - return new SpanOriginProbe - { - Id = Guid.NewGuid().ToString(), - EvaluateAt = EvaluateAt.Exit, - Where = new Where - { - MethodName = method.Name, - TypeName = type.FullName ?? type.Name, - }, - }; } } } From 20da15c487dc62441d47065169f97fc5ec3d32ff Mon Sep 17 00:00:00 2001 From: dudik Date: Wed, 30 Oct 2024 12:32:56 +0100 Subject: [PATCH 08/16] remove code that not directly related to this pr --- .../Configurations/Models/SpanOriginProbe.cs | 62 ------------------- .../Debugger/Expressions/Enums.cs | 3 +- .../Debugger/Expressions/ProbeProcessor.cs | 1 - tracer/src/Datadog.Trace/TraceContext.cs | 1 - .../Datadog.Trace/Util/StringBuilderCache.cs | 2 +- 5 files changed, 2 insertions(+), 67 deletions(-) delete mode 100644 tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs diff --git a/tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs b/tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs deleted file mode 100644 index 9efc766ceba3..000000000000 --- a/tracer/src/Datadog.Trace/Debugger/Configurations/Models/SpanOriginProbe.cs +++ /dev/null @@ -1,62 +0,0 @@ -// -// 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; - -#nullable enable - -namespace Datadog.Trace.Debugger.Configurations.Models -{ - internal enum SpanOriginKind - { - Entry, - Exit - } - - internal class SpanOriginProbe : ProbeDefinition, IEquatable - { - private SpanOriginKind Kind { get; set; } - - public bool Equals(SpanOriginProbe? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && Kind == other.Kind; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != this.GetType()) - { - return false; - } - - return Equals((SpanOriginProbe)obj); - } - - public override int GetHashCode() - { - return HashCode.Combine(base.GetHashCode(), (int)Kind); - } - } -} diff --git a/tracer/src/Datadog.Trace/Debugger/Expressions/Enums.cs b/tracer/src/Datadog.Trace/Debugger/Expressions/Enums.cs index 67c137446564..86da631325b6 100644 --- a/tracer/src/Datadog.Trace/Debugger/Expressions/Enums.cs +++ b/tracer/src/Datadog.Trace/Debugger/Expressions/Enums.cs @@ -10,8 +10,7 @@ internal enum ProbeType Log = 0, Snapshot = 1, Metric = 2, - SpanDecoration = 3, - SpanOrigin = 4 + SpanDecoration = 3 } internal enum ProbeLocation diff --git a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs index ecddea90de8c..0909277701b7 100644 --- a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs +++ b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs @@ -56,7 +56,6 @@ private void InitializeProbeProcessor(ProbeDefinition probe) LogProbe { CaptureSnapshot: false } => ProbeType.Log, MetricProbe => ProbeType.Metric, SpanDecorationProbe => ProbeType.SpanDecoration, - SpanOriginProbe => ProbeType.SpanOrigin, _ => throw new ArgumentOutOfRangeException(nameof(probe), probe, "Unsupported probe type") }; diff --git a/tracer/src/Datadog.Trace/TraceContext.cs b/tracer/src/Datadog.Trace/TraceContext.cs index 8c1f9875f313..3a6bccbee2ae 100644 --- a/tracer/src/Datadog.Trace/TraceContext.cs +++ b/tracer/src/Datadog.Trace/TraceContext.cs @@ -15,7 +15,6 @@ using Datadog.Trace.ClrProfiler; using Datadog.Trace.Configuration; using Datadog.Trace.ContinuousProfiler; -using Datadog.Trace.Debugger.SpanCodeOrigin; using Datadog.Trace.Iast; using Datadog.Trace.Logging; using Datadog.Trace.Sampling; diff --git a/tracer/src/Datadog.Trace/Util/StringBuilderCache.cs b/tracer/src/Datadog.Trace/Util/StringBuilderCache.cs index 7d3d4fa664f1..caaecf84e4de 100644 --- a/tracer/src/Datadog.Trace/Util/StringBuilderCache.cs +++ b/tracer/src/Datadog.Trace/Util/StringBuilderCache.cs @@ -52,7 +52,7 @@ public static string GetStringAndRelease(StringBuilder sb) return result; } - public static void Release(StringBuilder? sb) + public static void Release(StringBuilder sb) { if (sb?.Capacity <= MaxBuilderSize) { From 63dc7038c5cfbbb77db1e90d75c282d40a0dcb21 Mon Sep 17 00:00:00 2001 From: dudik Date: Wed, 30 Oct 2024 14:08:55 +0100 Subject: [PATCH 09/16] pr comments --- .../Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 48b961927030..3d99efbbe372 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -89,7 +89,7 @@ private static void AddExitSpanTag(Span span) // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (frames != null) { - ArrayPool.Shared.Return(frames, true); + ArrayPool.Shared.Return(frames); } } } @@ -106,7 +106,7 @@ private static int PopulateUserFrames(FrameInfo[] frames) } var count = 0; - for (var walkIndex = 2; walkIndex < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; walkIndex++) + for (var walkIndex = 0; walkIndex < stackFrames.Length && count < Settings.CodeOriginMaxUserFrames; walkIndex++) { var frame = stackFrames[walkIndex]; @@ -128,7 +128,7 @@ private static int PopulateUserFrames(FrameInfo[] frames) return count; } - private struct FrameInfo + private record struct FrameInfo { internal readonly int FrameIndex; internal readonly StackFrame Frame; From a56abc44dbf00a8a969f23bfac31001277f9e2c5 Mon Sep 17 00:00:00 2001 From: dudik Date: Wed, 30 Oct 2024 14:13:51 +0100 Subject: [PATCH 10/16] nit --- .../Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 3d99efbbe372..7f50fd09281f 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -128,16 +128,6 @@ private static int PopulateUserFrames(FrameInfo[] frames) return count; } - private record struct FrameInfo - { - internal readonly int FrameIndex; - internal readonly StackFrame Frame; - - internal FrameInfo(int frameIndex, StackFrame frame) - { - FrameIndex = frameIndex; - Frame = frame; - } - } + private readonly record struct FrameInfo(int FrameIndex, StackFrame Frame); } } From e3898f1516e7776f940df7a728788385f12eea0c Mon Sep 17 00:00:00 2001 From: dudik Date: Wed, 30 Oct 2024 21:14:58 +0100 Subject: [PATCH 11/16] add tests --- .../ConfigurationKeys.Debugger.cs | 2 +- .../SpanCodeOrigin/SpanCodeOriginManager.cs | 37 ++++-- tracer/src/Datadog.Trace/Tracer.cs | 2 +- .../Debugger/DebuggerSettingsTests.cs | 56 +++++++++ .../Debugger/SpanCodeOriginTests.cs | 118 ++++++++++++++++++ 5 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs 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 7175c9d6a57c..c7d03b08d5fa 100644 --- a/tracer/src/Datadog.Trace/Tracer.cs +++ b/tracer/src/Datadog.Trace/Tracer.cs @@ -554,7 +554,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); + } + } +} From 4c53836aee7aee67fc0467172561111e38ce70cf Mon Sep 17 00:00:00 2001 From: dudik Date: Thu, 31 Oct 2024 17:11:32 +0100 Subject: [PATCH 12/16] PR comments --- .../Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 0e6362bcb30c..167ca1ca80ba 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -47,7 +47,7 @@ internal void SetCodeOrigin(Span? span) return; } - Instance.AddExitSpanTag(span); + AddExitSpanTag(span); } private void AddExitSpanTag(Span span) @@ -58,7 +58,7 @@ private void AddExitSpanTag(Span span) var framesLength = PopulateUserFrames(frames); if (framesLength == 0) { - Log.Warning("No user frames has founded"); + Log.Warning("No user frames were found"); return; } @@ -105,8 +105,7 @@ private void AddExitSpanTag(Span span) } finally { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (frames != null) + if (frames != null!) { ArrayPool.Shared.Return(frames); } @@ -118,8 +117,7 @@ private int PopulateUserFrames(FrameInfo[] frames) var stackTrace = new StackTrace(true); var stackFrames = stackTrace.GetFrames(); - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (stackFrames == null) + if (stackFrames == null!) { return 0; } From 48932b86090533e09a2a75ae9efbb4e9b9814aa0 Mon Sep 17 00:00:00 2001 From: dudik Date: Tue, 5 Nov 2024 17:14:54 +0100 Subject: [PATCH 13/16] PR comments and fix tests --- .../Debugger/Expressions/ProbeProcessor.cs | 2 +- .../SpanCodeOrigin/SpanCodeOriginManager.cs | 20 +------- .../Debugger/SpanCodeOriginTests.cs | 48 ++++++++++++------- 3 files changed, 33 insertions(+), 37 deletions(-) diff --git a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs index 0909277701b7..96aacaae9aad 100644 --- a/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs +++ b/tracer/src/Datadog.Trace/Debugger/Expressions/ProbeProcessor.cs @@ -393,7 +393,7 @@ private void SetSpanDecoration(DebuggerSnapshotCreator snapshotCreator, ref bool { Tracer.Instance.ScopeManager.Active.Root.Span.SetTag(evaluationErrorTag, string.Join(";", decoration.Errors)); } - else if (Tracer.Instance.ScopeManager.Active.Root.Span.GetTag(evaluationErrorTag) != null) + else if (Tracer.Instance.ScopeManager.Active.Span.GetTag(evaluationErrorTag) != null) { Tracer.Instance.ScopeManager.Active.Root.Span.SetTag(evaluationErrorTag, null); } diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 167ca1ca80ba..1a2fd7ccb24f 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -6,12 +6,9 @@ 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 { @@ -23,26 +20,13 @@ internal class SpanCodeOriginManager 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); + internal static SpanCodeOriginManager Instance { get; } = new(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetCodeOrigin(Span? span) { - if (span == null || !this._settings.CodeOriginForSpansEnabled) + if (span == null || !_settings.CodeOriginForSpansEnabled) { return; } diff --git a/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs b/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs index 4c59ebf6055b..8237c11fc11f 100644 --- a/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs @@ -5,13 +5,15 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; 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 FluentAssertions; using Xunit; namespace Datadog.Trace.Tests.Debugger @@ -39,7 +41,7 @@ public void SetCodeOrigin_WhenDisabled_DoesNotSetTags() SpanCodeOriginManager.Instance.SetCodeOrigin(span); // Assert - Assert.Null(span.Tags.GetTag(CodeOriginTag + ".type")); + span.Tags.GetTag(CodeOriginTag + ".type").Should().BeNull(); } [Fact] @@ -54,13 +56,18 @@ public void SetCodeOrigin_WhenEnabled_SetsCorrectTags() 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")); + var codeOriginType = span.Tags.GetTag($"{CodeOriginTag}.type"); + codeOriginType.Should().Be("exit"); + var frame0Method = span.Tags.GetTag($"{CodeOriginTag}.frames.0.method"); + frame0Method.Should().Be(nameof(TestMethod)); + var frame0Type = span.Tags.GetTag($"{CodeOriginTag}.frames.0.type"); + frame0Type.Should().Be(GetType().FullName); + var file = span.Tags.GetTag($"{CodeOriginTag}.frames.0.file"); + file.Should().EndWith($"{nameof(SpanCodeOriginTests)}.cs"); + var line = span.Tags.GetTag($"{CodeOriginTag}.frames.0.line"); + line.Should().NotBeNullOrEmpty(); + var column = span.Tags.GetTag($"{CodeOriginTag}.frames.0.column"); + column.Should().NotBeNullOrEmpty(); } [Fact] @@ -76,40 +83,45 @@ public void SetCodeOrigin_WithMaxFramesLimit_RespectsLimit() // 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")); + tags.Should().Contain(s => s.StartsWith($"{CodeOriginTag}.frames.0")); + tags.Should().Contain(s => s.StartsWith($"{CodeOriginTag}.frames.1")); + tags.Should().NotContain(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 } - }), + new NameValueConfigurationSource( + new NameValueCollection + { + { 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); } + [MethodImpl(MethodImplOptions.NoInlining)] private void TestMethod(Span span) { SpanCodeOriginManager.Instance.SetCodeOrigin(span); } + [MethodImpl(MethodImplOptions.NoInlining)] private void DeepTestMethod1(Span span) { DeepTestMethod2(span); } + [MethodImpl(MethodImplOptions.NoInlining)] private void DeepTestMethod2(Span span) { DeepTestMethod3(span); } + [MethodImpl(MethodImplOptions.NoInlining)] private void DeepTestMethod3(Span span) { SpanCodeOriginManager.Instance.SetCodeOrigin(span); From 52a1a40018895870281e3fff0f84cbaea77d09df Mon Sep 17 00:00:00 2001 From: dudik Date: Wed, 6 Nov 2024 19:28:18 +0100 Subject: [PATCH 14/16] nit --- .../Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 1a2fd7ccb24f..2f43cebe0940 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -107,7 +107,7 @@ private int PopulateUserFrames(FrameInfo[] frames) } var count = 0; - for (var walkIndex = 0; walkIndex < stackFrames.Length && count < this._settings.CodeOriginMaxUserFrames; walkIndex++) + for (var walkIndex = 0; walkIndex < stackFrames.Length && count < _settings.CodeOriginMaxUserFrames; walkIndex++) { var frame = stackFrames[walkIndex]; From 23a86f08460381bdae09d703b680be1735903128 Mon Sep 17 00:00:00 2001 From: dudik Date: Wed, 6 Nov 2024 23:27:39 +0100 Subject: [PATCH 15/16] fix unit test --- .../Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs | 2 +- .../test/Datadog.Trace.Tests/Telemetry/config_norm_rules.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 2f43cebe0940..459af0a06e84 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -98,7 +98,7 @@ private void AddExitSpanTag(Span span) private int PopulateUserFrames(FrameInfo[] frames) { - var stackTrace = new StackTrace(true); + var stackTrace = new StackTrace(skipFrames: 3, fNeedFileInfo: true); var stackFrames = stackTrace.GetFrames(); if (stackFrames == null!) diff --git a/tracer/test/Datadog.Trace.Tests/Telemetry/config_norm_rules.json b/tracer/test/Datadog.Trace.Tests/Telemetry/config_norm_rules.json index 9a7d9ea899b0..ba8157d4d759 100644 --- a/tracer/test/Datadog.Trace.Tests/Telemetry/config_norm_rules.json +++ b/tracer/test/Datadog.Trace.Tests/Telemetry/config_norm_rules.json @@ -485,6 +485,8 @@ "DD_SYMBOL_DATABASE_THIRD_PARTY_DETECTION_EXCLUDES": "symbol_database_third_party_detection_excludes", "DD_THIRD_PARTY_DETECTION_INCLUDES": "third_party_detection_includes", "DD_THIRD_PARTY_DETECTION_EXCLUDES": "third_party_detection_excludes", + "DD_CODE_ORIGIN_FOR_SPANS_ENABLED": "code_origin_for_spans_enabled", + "DD_CODE_ORIGIN_FOR_SPANS_MAX_USER_FRAMES": "code_origin_for_spans_max_user_frames", "DD_LOGS_DIRECT_SUBMISSION_INTEGRATIONS": "logs_direct_submission_integrations", "DD_LOGS_DIRECT_SUBMISSION_HOST": "logs_direct_submission_host", "DD_LOGS_DIRECT_SUBMISSION_SOURCE": "logs_direct_submission_source", From f363d6898474232935416e6a228c95b6ff200b59 Mon Sep 17 00:00:00 2001 From: dudik Date: Thu, 7 Nov 2024 10:14:29 +0100 Subject: [PATCH 16/16] Fix tests in CI --- .../SpanCodeOrigin/SpanCodeOriginManager.cs | 4 +- .../Debugger/SpanCodeOriginTests.cs | 51 +++++++++++++------ 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs index 459af0a06e84..1b5329eb2739 100644 --- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs +++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/SpanCodeOriginManager.cs @@ -36,7 +36,7 @@ internal void SetCodeOrigin(Span? span) private void AddExitSpanTag(Span span) { - var frames = ArrayPool.Shared.Rent(this._settings.CodeOriginMaxUserFrames); + var frames = ArrayPool.Shared.Rent(_settings.CodeOriginMaxUserFrames); try { var framesLength = PopulateUserFrames(frames); @@ -98,7 +98,7 @@ private void AddExitSpanTag(Span span) private int PopulateUserFrames(FrameInfo[] frames) { - var stackTrace = new StackTrace(skipFrames: 3, fNeedFileInfo: true); + var stackTrace = new StackTrace(fNeedFileInfo: true); var stackFrames = stackTrace.GetFrames(); if (stackFrames == null!) diff --git a/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs b/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs index 8237c11fc11f..550ab8f5851a 100644 --- a/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Debugger/SpanCodeOriginTests.cs @@ -33,7 +33,7 @@ public void SetCodeOrigin_WhenSpanIsNull_DoesNotThrow() public void SetCodeOrigin_WhenDisabled_DoesNotSetTags() { // Arrange - CreateCodeOriginManager(); + using var settingsSetter = SetCodeOriginManagerSettings(); var span = new Span(new SpanContext(1, 2, SamplingPriority.UserKeep), DateTimeOffset.UtcNow); @@ -48,7 +48,7 @@ public void SetCodeOrigin_WhenDisabled_DoesNotSetTags() public void SetCodeOrigin_WhenEnabled_SetsCorrectTags() { // Arrange - CreateCodeOriginManager(true); + using var settingsSetter = SetCodeOriginManagerSettings(true); var span = new Span(new SpanContext(1, 2, SamplingPriority.UserKeep), DateTimeOffset.UtcNow); @@ -74,7 +74,7 @@ public void SetCodeOrigin_WhenEnabled_SetsCorrectTags() public void SetCodeOrigin_WithMaxFramesLimit_RespectsLimit() { // Arrange - CreateCodeOriginManager(true, 2); + using var settingsSetter = SetCodeOriginManagerSettings(true, 2); var span = new Span(new SpanContext(1, 2, SamplingPriority.UserKeep), DateTimeOffset.UtcNow); @@ -88,19 +88,11 @@ public void SetCodeOrigin_WithMaxFramesLimit_RespectsLimit() tags.Should().NotContain(s => s.StartsWith($"{CodeOriginTag}.frames.2")); } - private static void CreateCodeOriginManager(bool isEnable = false, int numberOfFrames = 8, string excludeFromFilter = "Datadog.Trace.Tests") + private static IDisposable SetCodeOriginManagerSettings(bool isEnable = false, int numberOfFrames = 8, string excludeFromFilter = "Datadog.Trace.Tests") { - var overrideSettings = DebuggerSettings.FromSource( - new NameValueConfigurationSource( - new NameValueCollection - { - { 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); + var setter = new CodeOriginSettingsSetter(); + setter.Set(isEnable, numberOfFrames, excludeFromFilter); + return setter; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -126,5 +118,34 @@ private void DeepTestMethod3(Span span) { SpanCodeOriginManager.Instance.SetCodeOrigin(span); } + + internal class CodeOriginSettingsSetter : IDisposable + { + private DebuggerSettings _original; + + public void Dispose() + { + var instance = SpanCodeOriginManager.Instance; + instance.GetType().GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(instance, _original); + } + + internal void Set(bool isEnable, int numberOfFrames, string excludeFromFilter) + { + var instance = SpanCodeOriginManager.Instance; + _original = (DebuggerSettings)instance.GetType().GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(instance); + + var overrideSettings = DebuggerSettings.FromSource( + new NameValueConfigurationSource( + new NameValueCollection + { + { ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, isEnable.ToString() }, + { ConfigurationKeys.Debugger.CodeOriginMaxUserFrames, numberOfFrames.ToString() }, + { ConfigurationKeys.Debugger.ThirdPartyDetectionExcludes, excludeFromFilter } + }), + NullConfigurationTelemetry.Instance); + + instance.GetType().GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(instance, overrideSettings); + } + } } }