From c8edcfab47fe548a22eb5755bd1c308387827ff4 Mon Sep 17 00:00:00 2001
From: dudik <8845578+dudikeleti@users.noreply.github.com>
Date: Wed, 21 Jan 2026 12:58:19 +0100
Subject: [PATCH 1/7] Add code origin support for Net Framework MVC and web api
---
.../AspNet/ActionDescriptorWithMethodInfo.cs | 25 ++++
...rActionInvoker_InvokeAction_Integration.cs | 66 ++++++++++
...tionDescriptor_ExecuteAsync_Integration.cs | 65 ++++++++++
.../SpanCodeOrigin/EndpointDetector.cs | 42 +++++--
.../AspNet/AspNetMvc5CodeOriginTests.cs | 114 ++++++++++++++++++
.../AspNet/AspNetWebApi2CodeOriginTests.cs | 110 +++++++++++++++++
.../Debugger/EndpointDetectorTests.cs | 56 +++++++++
7 files changed, 471 insertions(+), 7 deletions(-)
create mode 100644 tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ActionDescriptorWithMethodInfo.cs
create mode 100644 tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetMvc5CodeOriginTests.cs
create mode 100644 tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetWebApi2CodeOriginTests.cs
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ActionDescriptorWithMethodInfo.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ActionDescriptorWithMethodInfo.cs
new file mode 100644
index 000000000000..7cfe5eac00aa
--- /dev/null
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ActionDescriptorWithMethodInfo.cs
@@ -0,0 +1,25 @@
+//
+// 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.
+//
+
+#if NETFRAMEWORK
+#nullable enable
+
+using System.Reflection;
+using Datadog.Trace.DuckTyping;
+
+namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet
+{
+ ///
+ /// Duck type for MVC/WebApi2 descriptors that expose a MethodInfo.
+ ///
+ [DuckCopy]
+ internal struct ActionDescriptorWithMethodInfo
+ {
+ [Duck]
+ public MethodInfo MethodInfo;
+ }
+}
+#endif
+
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
index 370917f4cd3b..353cdadf35ca 100644
--- a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
@@ -8,14 +8,18 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.Reflection;
using System.Web;
using Datadog.Trace.AppSec;
using Datadog.Trace.AppSec.Coordinator;
using Datadog.Trace.AspNet;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;
+using Datadog.Trace.Debugger;
+using Datadog.Trace.Debugger.SpanCodeOrigin;
using Datadog.Trace.DuckTyping;
using Datadog.Trace.Logging;
+using Datadog.Trace.Vendors.Serilog.Events;
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet
{
@@ -70,9 +74,71 @@ internal static CallTargetState OnMethodBegin(TActionDescriptor actionDescriptor, SpanCodeOrigin codeOrigin)
+ {
+ if (SharedItems.TryPeekScope(HttpContext.Current, AspNetMvcIntegration.HttpContextKey) is not { Root.Span: { } rootSpan })
+ {
+ if (Log.IsEnabled(LogEventLevel.Debug))
+ {
+ Log.Debug("Code origin is enabled for spans but MVC scope was not found in HttpContext.");
+ }
+
+ return;
+ }
+
+ if (actionDescriptor is null)
+ {
+ if (Log.IsEnabled(LogEventLevel.Debug))
+ {
+ Log.Debug("Code origin is enabled for spans but MVC ActionDescriptor instance was null.");
+ }
+
+ return;
+ }
+
+ if (!actionDescriptor.TryDuckCast(out var reflected) || reflected.MethodInfo is not { } actionMethod)
+ {
+ if (Log.IsEnabled(LogEventLevel.Debug))
+ {
+ Log.Debug(
+ "Code origin is enabled for spans but could not extract MVC action MethodInfo from ActionDescriptor type {ActionDescriptorType}.",
+ actionDescriptor.GetType());
+ }
+
+ return;
+ }
+
+ if (actionMethod.DeclaringType is not { } actionType)
+ {
+ if (Log.IsEnabled(LogEventLevel.Debug))
+ {
+ Log.Debug(
+ "Code origin is enabled for spans but extracted MVC action MethodInfo has no DeclaringType. Method: {Method}.",
+ actionMethod);
+ }
+
+ return;
+ }
+
+ codeOrigin.SetCodeOriginForEntrySpan(rootSpan, actionType, actionMethod);
+ }
+
///
/// OnMethodEnd callback
///
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
index 10634ea0386f..1a02b8a5ecab 100644
--- a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
@@ -16,8 +16,11 @@
using Datadog.Trace.AspNet;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;
+using Datadog.Trace.Debugger;
+using Datadog.Trace.Debugger.SpanCodeOrigin;
using Datadog.Trace.DuckTyping;
using Datadog.Trace.Logging;
+using Datadog.Trace.Vendors.Serilog.Events;
namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet
{
@@ -70,9 +73,71 @@ internal static CallTargetState OnMethodBegin(TTarget inst
Log.Error(ex, "Error instrumenting method {MethodName}", "System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync()");
}
+ try
+ {
+ var codeOrigin = DebuggerManager.Instance.CodeOrigin;
+ if (codeOrigin is { Settings.CodeOriginForSpansEnabled: true })
+ {
+ AddSpanCodeOrigin(instance, codeOrigin);
+ }
+ }
+ catch (Exception ex) when (BlockException.GetBlockException(ex) is null)
+ {
+ Log.Error(ex, "Error adding code origin for spans in {MethodName}", "System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync()");
+ }
+
return CallTargetState.GetDefault();
}
+ private static void AddSpanCodeOrigin(TTarget instance, SpanCodeOrigin codeOrigin)
+ {
+ if (SharedItems.TryPeekScope(HttpContext.Current, AspNetWebApi2Integration.HttpContextKey) is not { Root.Span: { } rootSpan })
+ {
+ if (Log.IsEnabled(LogEventLevel.Debug))
+ {
+ Log.Debug("Code origin is enabled for spans but WebApi2 scope was not found in HttpContext.");
+ }
+
+ return;
+ }
+
+ if (instance is null)
+ {
+ if (Log.IsEnabled(LogEventLevel.Debug))
+ {
+ Log.Debug("Code origin is enabled for spans but ReflectedHttpActionDescriptor instance was null.");
+ }
+
+ return;
+ }
+
+ if (!instance.TryDuckCast(out var reflected) || reflected.MethodInfo is not { } actionMethod)
+ {
+ if (Log.IsEnabled(LogEventLevel.Debug))
+ {
+ Log.Debug(
+ "Code origin is enabled for spans but could not extract WebApi2 action MethodInfo from ReflectedHttpActionDescriptor type {ActionDescriptorType}.",
+ instance.GetType());
+ }
+
+ return;
+ }
+
+ if (actionMethod.DeclaringType is not { } actionType)
+ {
+ if (Log.IsEnabled(LogEventLevel.Debug))
+ {
+ Log.Debug(
+ "Code origin is enabled for spans but extracted WebApi2 action MethodInfo has no DeclaringType. Method: {Method}.",
+ actionMethod);
+ }
+
+ return;
+ }
+
+ codeOrigin.SetCodeOriginForEntrySpan(rootSpan, actionType, actionMethod);
+ }
+
internal static TResponse? OnAsyncMethodEnd(TTarget instance, TResponse? response, Exception? exception, in CallTargetState state)
{
var security = Security.Instance;
diff --git a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/EndpointDetector.cs b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/EndpointDetector.cs
index 1dba428e478d..363a5741b09a 100644
--- a/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/EndpointDetector.cs
+++ b/tracer/src/Datadog.Trace/Debugger/SpanCodeOrigin/EndpointDetector.cs
@@ -1,4 +1,4 @@
-//
+//
// 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.
//
@@ -18,20 +18,20 @@ namespace Datadog.Trace.Debugger.SpanCodeOrigin;
internal static class EndpointDetector
{
- private static readonly HashSet ControllerAttributes =
+ private static readonly HashSet AspNetCoreControllerAttributes =
[
"Microsoft.AspNetCore.Mvc.ApiControllerAttribute",
"Microsoft.AspNetCore.Mvc.ControllerAttribute",
"Microsoft.AspNetCore.Mvc.RouteAttribute"
];
- private static readonly HashSet ControllerBaseNames =
+ private static readonly HashSet AspNetCoreControllerBaseNames =
[
"Microsoft.AspNetCore.Mvc.Controller",
"Microsoft.AspNetCore.Mvc.ControllerBase"
];
- private static readonly HashSet ActionAttributes =
+ private static readonly HashSet AspNetCoreActionAttributes =
[
"Microsoft.AspNetCore.Mvc.HttpGetAttribute",
"Microsoft.AspNetCore.Mvc.HttpPostAttribute",
@@ -43,6 +43,25 @@ internal static class EndpointDetector
"Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute"
];
+ private static readonly HashSet NetFrameworkControllerBaseNames =
+ [
+ // MVC 4/5
+ "System.Web.Mvc.Controller",
+ "System.Web.Mvc.ControllerBase",
+
+ // Web API 2
+ "System.Web.Http.ApiController"
+ ];
+
+ private static readonly HashSet NetFrameworkNonActionAttributes =
+ [
+ // MVC 4/5
+ "System.Web.Mvc.NonActionAttribute",
+
+ // Web API 2
+ "System.Web.Http.NonActionAttribute"
+ ];
+
private static readonly HashSet SignalRHubBaseNames =
[
"Microsoft.AspNetCore.SignalR.Hub",
@@ -74,7 +93,9 @@ internal static ImmutableHashSet GetEndpointMethodTokens(DatadogMetadataRea
bool isPageModel = false;
bool isSignalRHub = false;
bool isCompilerGeneratedType = false;
- var isController = IsInheritFromTypesOrHasAttribute(typeDef, metadataReader, ControllerAttributes, ControllerBaseNames);
+ var isAspNetCoreController = IsInheritFromTypesOrHasAttribute(typeDef, metadataReader, AspNetCoreControllerAttributes, AspNetCoreControllerBaseNames);
+ var isNetFrameworkController = !isAspNetCoreController && IsInheritFromTypes(typeDef, metadataReader, NetFrameworkControllerBaseNames);
+ var isController = isAspNetCoreController || isNetFrameworkController;
if (!isController)
{
isPageModel = IsInheritFromTypes(typeDef, metadataReader, PageModelBaseNames);
@@ -101,7 +122,13 @@ internal static ImmutableHashSet GetEndpointMethodTokens(DatadogMetadataRea
continue;
}
- if (isController && HasAttributeFromSet(methodDef.GetCustomAttributes(), metadataReader, ActionAttributes))
+ if (isAspNetCoreController && HasAttributeFromSet(methodDef.GetCustomAttributes(), metadataReader, AspNetCoreActionAttributes))
+ {
+ builder.Add(metadataReader.GetToken(methodHandle));
+ continue;
+ }
+
+ if (isNetFrameworkController && !HasAttributeFromSet(methodDef.GetCustomAttributes(), metadataReader, NetFrameworkNonActionAttributes))
{
builder.Add(metadataReader.GetToken(methodHandle));
continue;
@@ -141,7 +168,8 @@ private static bool IsValidMethod(MethodDefinition methodDef)
{
var attributes = methodDef.Attributes;
return (attributes & MethodAttributes.Public) != 0 &&
- (attributes & MethodAttributes.Static) == 0;
+ (attributes & MethodAttributes.Static) == 0 &&
+ (attributes & MethodAttributes.SpecialName) == 0;
}
private static bool IsInheritFromTypesOrHasAttribute(TypeDefinition typeDef, MetadataReader reader, HashSet attributesNames, HashSet baseTypeNames)
diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetMvc5CodeOriginTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetMvc5CodeOriginTests.cs
new file mode 100644
index 000000000000..59dbb681016c
--- /dev/null
+++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetMvc5CodeOriginTests.cs
@@ -0,0 +1,114 @@
+//
+// 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.
+//
+
+#if NETFRAMEWORK
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Datadog.Trace.Configuration;
+using Datadog.Trace.ExtensionMethods;
+using Datadog.Trace.TestHelpers;
+using VerifyTests;
+using VerifyXunit;
+using Xunit;
+using Xunit.Abstractions;
+#pragma warning disable SA1402
+
+namespace Datadog.Trace.ClrProfiler.IntegrationTests
+{
+ [UsesVerify]
+ public abstract class AspNetMvc5CodeOriginTests : TracingIntegrationTest, IClassFixture, IAsyncLifetime
+ {
+ private readonly IisFixture _iisFixture;
+ private readonly string _testName;
+ private readonly bool _classicMode;
+
+ protected AspNetMvc5CodeOriginTests(IisFixture iisFixture, ITestOutputHelper output, bool classicMode)
+ : base("AspNetMvc5", @"test\test-applications\aspnet", output)
+ {
+ _iisFixture = iisFixture;
+ _iisFixture.ShutdownPath = "/home/shutdown";
+ _classicMode = classicMode;
+ _testName = $"{nameof(AspNetMvc5CodeOriginTests)}.{(classicMode ? "Classic" : "Integrated")}";
+
+ SetServiceVersion("1.0.0");
+ SetEnvironmentVariable(ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, "true");
+ }
+
+ public override Result ValidateIntegrationSpan(MockSpan span, string metadataSchemaVersion) =>
+ span.Name switch
+ {
+ "aspnet.request" => span.IsAspNet(metadataSchemaVersion),
+ "aspnet-mvc.request" => span.IsAspNetMvc(metadataSchemaVersion),
+ _ => Result.DefaultSuccess,
+ };
+
+ [SkippableFact]
+ [Trait("Category", "EndToEnd")]
+ [Trait("RunOnWindows", "True")]
+ [Trait("LoadFromGAC", "True")]
+ public async Task AddsCodeOriginToAspNetRequestSpan()
+ {
+ const string path = "/Home/Index";
+ const int statusCode = 200;
+
+ var spans = await GetWebServerSpans(
+ path: _iisFixture.VirtualApplicationPath + path,
+ agent: _iisFixture.Agent,
+ httpPort: _iisFixture.HttpPort,
+ expectedHttpStatusCode: HttpStatusCode.OK,
+ expectedSpanCount: 2);
+
+ var sanitisedPath = VerifyHelper.SanitisePathsForVerify(path);
+ var settings = VerifyHelper.GetSpanVerifierSettings(sanitisedPath, statusCode);
+
+ AddCodeOriginScrubbers(settings);
+
+ await Verifier.Verify(spans, settings)
+ .UseFileName($"{_testName}.__path={sanitisedPath}_statusCode={statusCode}");
+ }
+
+ public Task InitializeAsync() => _iisFixture.TryStartIis(this, _classicMode ? IisAppType.AspNetClassic : IisAppType.AspNetIntegrated);
+
+ public Task DisposeAsync() => Task.CompletedTask;
+
+ private static void AddCodeOriginScrubbers(VerifySettings settings)
+ {
+ // File paths are machine dependent, and line/column numbers are sensitive to unrelated edits.
+ // We scrub the values but keep the tags present in the snapshot.
+ settings.AddRegexScrubber(
+ new Regex(@"_dd\.code_origin\.frames\.(\d+)\.file: .*", VerifyHelper.RegOptions),
+ "_dd.code_origin.frames.$1.file: ");
+
+ settings.AddRegexScrubber(
+ new Regex(@"_dd\.code_origin\.frames\.(\d+)\.(line|column): \d+", VerifyHelper.RegOptions),
+ "_dd.code_origin.frames.$1.$2: 0");
+ }
+ }
+
+ [Collection("IisTests")]
+ public class AspNetMvc5CodeOriginTestsClassic : AspNetMvc5CodeOriginTests
+ {
+ public AspNetMvc5CodeOriginTestsClassic(IisFixture iisFixture, ITestOutputHelper output)
+ : base(iisFixture, output, classicMode: true)
+ {
+ }
+ }
+
+ [Collection("IisTests")]
+ public class AspNetMvc5CodeOriginTestsIntegrated : AspNetMvc5CodeOriginTests
+ {
+ public AspNetMvc5CodeOriginTestsIntegrated(IisFixture iisFixture, ITestOutputHelper output)
+ : base(iisFixture, output, classicMode: false)
+ {
+ }
+ }
+}
+
+#endif
+
diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetWebApi2CodeOriginTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetWebApi2CodeOriginTests.cs
new file mode 100644
index 000000000000..384bbba2ce45
--- /dev/null
+++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetWebApi2CodeOriginTests.cs
@@ -0,0 +1,110 @@
+//
+// 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.
+//
+
+#if NETFRAMEWORK
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Datadog.Trace.Configuration;
+using Datadog.Trace.ExtensionMethods;
+using Datadog.Trace.TestHelpers;
+using VerifyXunit;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Datadog.Trace.ClrProfiler.IntegrationTests
+{
+ [Collection("IisTests")]
+ public class AspNetWebApi2CodeOriginTestsClassic : AspNetWebApi2CodeOriginTests
+ {
+ public AspNetWebApi2CodeOriginTestsClassic(IisFixture iisFixture, ITestOutputHelper output)
+ : base(iisFixture, output, classicMode: true)
+ {
+ }
+ }
+
+ [Collection("IisTests")]
+ public class AspNetWebApi2CodeOriginTestsIntegrated : AspNetWebApi2CodeOriginTests
+ {
+ public AspNetWebApi2CodeOriginTestsIntegrated(IisFixture iisFixture, ITestOutputHelper output)
+ : base(iisFixture, output, classicMode: false)
+ {
+ }
+ }
+
+ [UsesVerify]
+ public abstract class AspNetWebApi2CodeOriginTests : TracingIntegrationTest, IClassFixture, IAsyncLifetime
+ {
+ private readonly IisFixture _iisFixture;
+ private readonly string _testName;
+ private readonly bool _classicMode;
+
+ protected AspNetWebApi2CodeOriginTests(IisFixture iisFixture, ITestOutputHelper output, bool classicMode)
+ : base("AspNetMvc5", @"test\test-applications\aspnet", output)
+ {
+ _iisFixture = iisFixture;
+ _iisFixture.ShutdownPath = "/home/shutdown";
+ _classicMode = classicMode;
+ _testName = $"{nameof(AspNetWebApi2CodeOriginTests)}.{(classicMode ? "Classic" : "Integrated")}";
+
+ SetServiceVersion("1.0.0");
+ SetEnvironmentVariable(ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, "true");
+ }
+
+ public override Result ValidateIntegrationSpan(MockSpan span, string metadataSchemaVersion) =>
+ span.Name switch
+ {
+ "aspnet.request" => span.IsAspNet(metadataSchemaVersion),
+ "aspnet-webapi.request" => span.IsAspNetWebApi2(metadataSchemaVersion),
+ _ => Result.DefaultSuccess,
+ };
+
+ [SkippableFact]
+ [Trait("Category", "EndToEnd")]
+ [Trait("RunOnWindows", "True")]
+ [Trait("LoadFromGAC", "True")]
+ public async Task AddsCodeOriginToAspNetRequestSpan()
+ {
+ const string path = "/api/environment";
+ const int statusCode = 200;
+
+ var spans = await GetWebServerSpans(
+ path: _iisFixture.VirtualApplicationPath + path,
+ agent: _iisFixture.Agent,
+ httpPort: _iisFixture.HttpPort,
+ expectedHttpStatusCode: HttpStatusCode.OK,
+ expectedSpanCount: 2);
+
+ var sanitisedPath = VerifyHelper.SanitisePathsForVerify(path);
+ var settings = VerifyHelper.GetSpanVerifierSettings(sanitisedPath, statusCode);
+
+ AddCodeOriginScrubbers(settings);
+
+ await Verifier.Verify(spans, settings)
+ .UseFileName($"{_testName}.__path={sanitisedPath}_statusCode={statusCode}");
+ }
+
+ public Task InitializeAsync() => _iisFixture.TryStartIis(this, _classicMode ? IisAppType.AspNetClassic : IisAppType.AspNetIntegrated);
+
+ public Task DisposeAsync() => Task.CompletedTask;
+
+ private static void AddCodeOriginScrubbers(VerifySettings settings)
+ {
+ settings.AddRegexScrubber(
+ new Regex(@"_dd\.code_origin\.frames\.(\d+)\.file: .*", VerifyHelper.RegOptions),
+ "_dd.code_origin.frames.$1.file: ");
+
+ settings.AddRegexScrubber(
+ new Regex(@"_dd\.code_origin\.frames\.(\d+)\.(line|column): \d+", VerifyHelper.RegOptions),
+ "_dd.code_origin.frames.$1.$2: 0");
+ }
+ }
+}
+
+#endif
+
diff --git a/tracer/test/Datadog.Trace.Tests/Debugger/EndpointDetectorTests.cs b/tracer/test/Datadog.Trace.Tests/Debugger/EndpointDetectorTests.cs
index 8a28d0ba6400..eb9122f61726 100644
--- a/tracer/test/Datadog.Trace.Tests/Debugger/EndpointDetectorTests.cs
+++ b/tracer/test/Datadog.Trace.Tests/Debugger/EndpointDetectorTests.cs
@@ -102,6 +102,16 @@ public void GetEndpointMethodTokens_FindsAllEndpoints()
isExpectedEndpoint = true;
}
+ // .NET Framework MVC/WebApi2 controllers (convention-based)
+ else if (type.Name == "NetFxMvcController" && method.Name == "Index")
+ {
+ isExpectedEndpoint = true;
+ }
+ else if (type.Name == "NetFxWebApiController" && method.Name == "Get")
+ {
+ isExpectedEndpoint = true;
+ }
+
// PageModel handlers
else if (type.Name == "TestPageModel" &&
method.Name is "OnGet" or "OnGetAsync" or "OnPost" or "OnPostAsync" or "OnPut" or "OnPutAsync" or "OnDelete" or "OnDeleteAsync" or "OnHead" or "OnHeadAsync" or "OnPatch" or "OnPatchAsync" or "OnOptions" or "OnOptionsAsync" &&
@@ -191,6 +201,12 @@ public void GetEndpointMethodTokens_DoesNotDetectNonEndpoints()
shouldNotBeEndpoint = true;
}
+ // .NET Framework MVC/WebApi2 non-actions
+ else if (type.Name is "NetFxMvcController" or "NetFxWebApiController" && method.Name == "Helper")
+ {
+ shouldNotBeEndpoint = true;
+ }
+
// PageModel methods with NonHandler attribute
else if (type.Name == "TestPageModel" && method.Name == "OnGetWithNonHandlerAttribute")
{
@@ -414,6 +430,24 @@ public static void MapPost(this object app, string pattern, Delegate handler) {
}
}
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Method)]
+ public class NonActionAttribute : Attribute { }
+
+ public class ControllerBase { }
+
+ public class Controller : ControllerBase { }
+}
+
+namespace System.Web.Http
+{
+ [AttributeUsage(AttributeTargets.Method)]
+ public class NonActionAttribute : Attribute { }
+
+ public class ApiController { }
+}
+
namespace EndpointDetectorTestNamespace
{
// Controller with ControllerAttribute
@@ -577,6 +611,28 @@ public static void UseHandlers(object app, RequestHandler handler1, RequestHandl
// This empty method ensures the delegates are used
}
}
+
+ // .NET Framework MVC controller
+ public class NetFxMvcController : System.Web.Mvc.Controller
+ {
+ public object Index() => null;
+
+ [System.Web.Mvc.NonAction]
+ public object Helper() => null;
+
+ private object PrivateMethod() => null;
+
+ public static object StaticMethod() => null;
+ }
+
+ // .NET Framework Web API 2 controller
+ public class NetFxWebApiController : System.Web.Http.ApiController
+ {
+ public object Get() => null;
+
+ [System.Web.Http.NonAction]
+ public object Helper() => null;
+ }
}");
}
}
From 247f65dd7a28ad029a2b5aa2069e29e161949c1f Mon Sep 17 00:00:00 2001
From: dudik <8845578+dudikeleti@users.noreply.github.com>
Date: Tue, 27 Jan 2026 14:57:33 +0100
Subject: [PATCH 2/7] improve debug logs
---
...rActionInvoker_InvokeAction_Integration.cs | 30 +++++++----------
...tionDescriptor_ExecuteAsync_Integration.cs | 32 +++++++------------
2 files changed, 23 insertions(+), 39 deletions(-)
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
index 353cdadf35ca..5c1852c0ea19 100644
--- a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
@@ -92,44 +92,36 @@ internal static CallTargetState OnMethodBegin(TActionDescriptor actionDescriptor, SpanCodeOrigin codeOrigin)
{
- if (SharedItems.TryPeekScope(HttpContext.Current, AspNetMvcIntegration.HttpContextKey) is not { Root.Span: { } rootSpan })
- {
- if (Log.IsEnabled(LogEventLevel.Debug))
- {
- Log.Debug("Code origin is enabled for spans but MVC scope was not found in HttpContext.");
- }
-
- return;
- }
-
if (actionDescriptor is null)
{
- if (Log.IsEnabled(LogEventLevel.Debug))
- {
- Log.Debug("Code origin is enabled for spans but MVC ActionDescriptor instance was null.");
- }
-
return;
}
- if (!actionDescriptor.TryDuckCast(out var reflected) || reflected.MethodInfo is not { } actionMethod)
+ var httpContext = HttpContext.Current;
+ if (SharedItems.TryPeekScope(httpContext, AspNetMvcIntegration.HttpContextKey) is not { Root.Span: { } rootSpan })
{
if (Log.IsEnabled(LogEventLevel.Debug))
{
Log.Debug(
- "Code origin is enabled for spans but could not extract MVC action MethodInfo from ActionDescriptor type {ActionDescriptorType}.",
+ "Code origin is enabled but scope was not found in HttpContext (key: {HttpContextKey}, httpContextNull: {HttpContextIsNull}, itemsCount: {HttpContextItemsCount}, actionDescriptorType: {ActionDescriptorType}).",
+ AspNetMvcIntegration.HttpContextKey,
+ httpContext is null,
+ httpContext?.Items?.Count ?? 0,
actionDescriptor.GetType());
}
return;
}
- if (actionMethod.DeclaringType is not { } actionType)
+ if (!actionDescriptor.TryDuckCast(out var reflected)
+ || reflected.MethodInfo is not { } actionMethod
+ || actionMethod.DeclaringType is not { } actionType)
{
if (Log.IsEnabled(LogEventLevel.Debug))
{
Log.Debug(
- "Code origin is enabled for spans but extracted MVC action MethodInfo has no DeclaringType. Method: {Method}.",
+ "Code origin is enabled but could not extract action from ActionDescriptor type {ActionDescriptorType} or action MethodInfo has no DeclaringType. Method: {Method}.",
+ actionDescriptor.GetType(),
actionMethod);
}
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
index 1a02b8a5ecab..321dcaad9b56 100644
--- a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
@@ -91,44 +91,36 @@ internal static CallTargetState OnMethodBegin(TTarget inst
private static void AddSpanCodeOrigin(TTarget instance, SpanCodeOrigin codeOrigin)
{
- if (SharedItems.TryPeekScope(HttpContext.Current, AspNetWebApi2Integration.HttpContextKey) is not { Root.Span: { } rootSpan })
+ if (instance == null)
{
- if (Log.IsEnabled(LogEventLevel.Debug))
- {
- Log.Debug("Code origin is enabled for spans but WebApi2 scope was not found in HttpContext.");
- }
-
- return;
- }
-
- if (instance is null)
- {
- if (Log.IsEnabled(LogEventLevel.Debug))
- {
- Log.Debug("Code origin is enabled for spans but ReflectedHttpActionDescriptor instance was null.");
- }
-
return;
}
- if (!instance.TryDuckCast(out var reflected) || reflected.MethodInfo is not { } actionMethod)
+ var httpContext = HttpContext.Current;
+ if (SharedItems.TryPeekScope(httpContext, AspNetWebApi2Integration.HttpContextKey) is not { Root.Span: { } rootSpan })
{
if (Log.IsEnabled(LogEventLevel.Debug))
{
Log.Debug(
- "Code origin is enabled for spans but could not extract WebApi2 action MethodInfo from ReflectedHttpActionDescriptor type {ActionDescriptorType}.",
+ "Code origin is enabled but scope was not found in HttpContext (key: {HttpContextKey}, httpContextNull: {HttpContextIsNull}, itemsCount: {HttpContextItemsCount}, actionDescriptorType: {ActionDescriptorType}).",
+ AspNetWebApi2Integration.HttpContextKey,
+ httpContext is null,
+ httpContext?.Items?.Count ?? 0,
instance.GetType());
}
return;
}
- if (actionMethod.DeclaringType is not { } actionType)
+ if (!instance.TryDuckCast(out var reflected)
+ || reflected.MethodInfo is not { } actionMethod
+ || actionMethod.DeclaringType is not { } actionType)
{
if (Log.IsEnabled(LogEventLevel.Debug))
{
Log.Debug(
- "Code origin is enabled for spans but extracted WebApi2 action MethodInfo has no DeclaringType. Method: {Method}.",
+ "Code origin is enabled but could not extract action from HttpActionDescriptor type {ActionDescriptorType} or action has no DeclaringType. Method: {Method}.",
+ instance.GetType(),
actionMethod);
}
From 7a97828034ca36c488296a255839ed1b5bfea913 Mon Sep 17 00:00:00 2001
From: dudik <8845578+dudikeleti@users.noreply.github.com>
Date: Tue, 27 Jan 2026 16:33:43 +0100
Subject: [PATCH 3/7] fix build
---
.../ControllerActionInvoker_InvokeAction_Integration.cs | 5 ++---
...ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs | 5 ++---
.../AspNet/AspNetWebApi2CodeOriginTests.cs | 3 +++
3 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
index 5c1852c0ea19..e7d188c9ede5 100644
--- a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
@@ -120,9 +120,8 @@ private static void AddSpanCodeOrigin(TActionDescriptor actio
if (Log.IsEnabled(LogEventLevel.Debug))
{
Log.Debug(
- "Code origin is enabled but could not extract action from ActionDescriptor type {ActionDescriptorType} or action MethodInfo has no DeclaringType. Method: {Method}.",
- actionDescriptor.GetType(),
- actionMethod);
+ "Code origin is enabled but could not extract action from ActionDescriptor type {ActionDescriptorType} or action MethodInfo has no DeclaringType.",
+ actionDescriptor.GetType());
}
return;
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
index 321dcaad9b56..b753febbd026 100644
--- a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
@@ -119,9 +119,8 @@ private static void AddSpanCodeOrigin(TTarget instance, SpanCodeOrigin
if (Log.IsEnabled(LogEventLevel.Debug))
{
Log.Debug(
- "Code origin is enabled but could not extract action from HttpActionDescriptor type {ActionDescriptorType} or action has no DeclaringType. Method: {Method}.",
- instance.GetType(),
- actionMethod);
+ "Code origin is enabled but could not extract action from HttpActionDescriptor type {ActionDescriptorType} or action has no DeclaringType.",
+ instance.GetType());
}
return;
diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetWebApi2CodeOriginTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetWebApi2CodeOriginTests.cs
index 384bbba2ce45..e9528be983d6 100644
--- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetWebApi2CodeOriginTests.cs
+++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/AspNet/AspNetWebApi2CodeOriginTests.cs
@@ -4,6 +4,8 @@
//
#if NETFRAMEWORK
+#pragma warning disable SA1402 // File may only contain a single class
+#pragma warning disable SA1649 // File name must match first type name
using System;
using System.Collections.Generic;
@@ -13,6 +15,7 @@
using Datadog.Trace.Configuration;
using Datadog.Trace.ExtensionMethods;
using Datadog.Trace.TestHelpers;
+using VerifyTests;
using VerifyXunit;
using Xunit;
using Xunit.Abstractions;
From 1dba03f0b349eb65f2403c8014c342d6689c5c5b Mon Sep 17 00:00:00 2001
From: dudik <8845578+dudikeleti@users.noreply.github.com>
Date: Mon, 9 Feb 2026 23:58:44 +0100
Subject: [PATCH 4/7] Add InstrumentationCategory.Tracing
---
.../AspNet/ControllerActionInvoker_InvokeAction_Integration.cs | 2 +-
.../ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
index e7d188c9ede5..e611bbdc1439 100644
--- a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ControllerActionInvoker_InvokeAction_Integration.cs
@@ -35,7 +35,7 @@ namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet
MinimumVersion = "4",
MaximumVersion = "5",
IntegrationName = IntegrationName,
- InstrumentationCategory = InstrumentationCategory.AppSec | InstrumentationCategory.Iast)]
+ InstrumentationCategory = InstrumentationCategory.Tracing | InstrumentationCategory.AppSec | InstrumentationCategory.Iast)]
// ReSharper disable once InconsistentNaming
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
index b753febbd026..43e3d8d4ba1e 100644
--- a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
+++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/AspNet/ReflectedHttpActionDescriptor_ExecuteAsync_Integration.cs
@@ -36,7 +36,7 @@ namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet
MinimumVersion = "5.1",
MaximumVersion = "5",
IntegrationName = IntegrationName,
- InstrumentationCategory = InstrumentationCategory.AppSec | InstrumentationCategory.Iast)]
+ InstrumentationCategory = InstrumentationCategory.Tracing | InstrumentationCategory.AppSec | InstrumentationCategory.Iast)]
// ReSharper disable once InconsistentNaming
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
From 9ae29fbf235ac26900485df2662d18ed2588462a Mon Sep 17 00:00:00 2001
From: dudik <8845578+dudikeleti@users.noreply.github.com>
Date: Mon, 9 Feb 2026 23:59:50 +0100
Subject: [PATCH 5/7] Add code origin snapshots
---
...th=_Home_Index_statusCode=200.verified.txt | 61 +++++++++++++++++++
...th=_Home_Index_statusCode=200.verified.txt | 61 +++++++++++++++++++
...pi_environment_statusCode=200.verified.txt | 59 ++++++++++++++++++
...pi_environment_statusCode=200.verified.txt | 59 ++++++++++++++++++
4 files changed, 240 insertions(+)
create mode 100644 tracer/test/snapshots/AspNetMvc5CodeOriginTests.Classic.__path=_Home_Index_statusCode=200.verified.txt
create mode 100644 tracer/test/snapshots/AspNetMvc5CodeOriginTests.Integrated.__path=_Home_Index_statusCode=200.verified.txt
create mode 100644 tracer/test/snapshots/AspNetWebApi2CodeOriginTests.Classic.__path=_api_environment_statusCode=200.verified.txt
create mode 100644 tracer/test/snapshots/AspNetWebApi2CodeOriginTests.Integrated.__path=_api_environment_statusCode=200.verified.txt
diff --git a/tracer/test/snapshots/AspNetMvc5CodeOriginTests.Classic.__path=_Home_Index_statusCode=200.verified.txt b/tracer/test/snapshots/AspNetMvc5CodeOriginTests.Classic.__path=_Home_Index_statusCode=200.verified.txt
new file mode 100644
index 000000000000..c8131e6839db
--- /dev/null
+++ b/tracer/test/snapshots/AspNetMvc5CodeOriginTests.Classic.__path=_Home_Index_statusCode=200.verified.txt
@@ -0,0 +1,61 @@
+[
+ {
+ TraceId: Id_1,
+ SpanId: Id_2,
+ Name: aspnet-mvc.request,
+ Resource: GET /home/index,
+ Service: sample,
+ Type: web,
+ ParentId: Id_3,
+ Tags: {
+ aspnet.action: index,
+ aspnet.controller: home,
+ aspnet.route: {controller}/{action}/{id},
+ component: aspnet,
+ env: integration_tests,
+ http.method: GET,
+ http.request.headers.host: localhost:00000,
+ http.status_code: 200,
+ http.url: http://localhost:00000/Home/Index,
+ http.useragent: testhelper,
+ language: dotnet,
+ span.kind: server,
+ version: 1.0.0
+ }
+ },
+ {
+ TraceId: Id_1,
+ SpanId: Id_3,
+ Name: aspnet.request,
+ Resource: GET /home/index,
+ Service: sample,
+ Type: web,
+ Tags: {
+ component: aspnet,
+ env: integration_tests,
+ http.method: GET,
+ http.request.headers.host: localhost:00000,
+ http.route: {controller}/{action}/{id},
+ http.status_code: 200,
+ http.url: http://localhost:00000/Home/Index,
+ http.useragent: testhelper,
+ language: dotnet,
+ runtime-id: Guid_1,
+ span.kind: server,
+ version: 1.0.0,
+ _dd.code_origin.frames.0.column: 0,
+ _dd.code_origin.frames.0.file:
+ _dd.code_origin.frames.0.index: 0,
+ _dd.code_origin.frames.0.line: 0,
+ _dd.code_origin.frames.0.method: Index,
+ _dd.code_origin.frames.0.type: Samples.AspNetMvc5.Controllers.HomeController,
+ _dd.code_origin.type: entry
+ },
+ Metrics: {
+ process_id: 0,
+ _dd.top_level: 1.0,
+ _dd.tracer_kr: 1.0,
+ _sampling_priority_v1: 1.0
+ }
+ }
+]
\ No newline at end of file
diff --git a/tracer/test/snapshots/AspNetMvc5CodeOriginTests.Integrated.__path=_Home_Index_statusCode=200.verified.txt b/tracer/test/snapshots/AspNetMvc5CodeOriginTests.Integrated.__path=_Home_Index_statusCode=200.verified.txt
new file mode 100644
index 000000000000..c8131e6839db
--- /dev/null
+++ b/tracer/test/snapshots/AspNetMvc5CodeOriginTests.Integrated.__path=_Home_Index_statusCode=200.verified.txt
@@ -0,0 +1,61 @@
+[
+ {
+ TraceId: Id_1,
+ SpanId: Id_2,
+ Name: aspnet-mvc.request,
+ Resource: GET /home/index,
+ Service: sample,
+ Type: web,
+ ParentId: Id_3,
+ Tags: {
+ aspnet.action: index,
+ aspnet.controller: home,
+ aspnet.route: {controller}/{action}/{id},
+ component: aspnet,
+ env: integration_tests,
+ http.method: GET,
+ http.request.headers.host: localhost:00000,
+ http.status_code: 200,
+ http.url: http://localhost:00000/Home/Index,
+ http.useragent: testhelper,
+ language: dotnet,
+ span.kind: server,
+ version: 1.0.0
+ }
+ },
+ {
+ TraceId: Id_1,
+ SpanId: Id_3,
+ Name: aspnet.request,
+ Resource: GET /home/index,
+ Service: sample,
+ Type: web,
+ Tags: {
+ component: aspnet,
+ env: integration_tests,
+ http.method: GET,
+ http.request.headers.host: localhost:00000,
+ http.route: {controller}/{action}/{id},
+ http.status_code: 200,
+ http.url: http://localhost:00000/Home/Index,
+ http.useragent: testhelper,
+ language: dotnet,
+ runtime-id: Guid_1,
+ span.kind: server,
+ version: 1.0.0,
+ _dd.code_origin.frames.0.column: 0,
+ _dd.code_origin.frames.0.file:
+ _dd.code_origin.frames.0.index: 0,
+ _dd.code_origin.frames.0.line: 0,
+ _dd.code_origin.frames.0.method: Index,
+ _dd.code_origin.frames.0.type: Samples.AspNetMvc5.Controllers.HomeController,
+ _dd.code_origin.type: entry
+ },
+ Metrics: {
+ process_id: 0,
+ _dd.top_level: 1.0,
+ _dd.tracer_kr: 1.0,
+ _sampling_priority_v1: 1.0
+ }
+ }
+]
\ No newline at end of file
diff --git a/tracer/test/snapshots/AspNetWebApi2CodeOriginTests.Classic.__path=_api_environment_statusCode=200.verified.txt b/tracer/test/snapshots/AspNetWebApi2CodeOriginTests.Classic.__path=_api_environment_statusCode=200.verified.txt
new file mode 100644
index 000000000000..940a619c1ad3
--- /dev/null
+++ b/tracer/test/snapshots/AspNetWebApi2CodeOriginTests.Classic.__path=_api_environment_statusCode=200.verified.txt
@@ -0,0 +1,59 @@
+[
+ {
+ TraceId: Id_1,
+ SpanId: Id_2,
+ Name: aspnet-webapi.request,
+ Resource: GET /api/environment,
+ Service: sample,
+ Type: web,
+ ParentId: Id_3,
+ Tags: {
+ aspnet.route: api/environment,
+ component: aspnet,
+ env: integration_tests,
+ http.method: GET,
+ http.request.headers.host: localhost:00000,
+ http.status_code: 200,
+ http.url: http://localhost:00000/api/environment,
+ http.useragent: testhelper,
+ language: dotnet,
+ span.kind: server,
+ version: 1.0.0
+ }
+ },
+ {
+ TraceId: Id_1,
+ SpanId: Id_3,
+ Name: aspnet.request,
+ Resource: GET /api/environment,
+ Service: sample,
+ Type: web,
+ Tags: {
+ component: aspnet,
+ env: integration_tests,
+ http.method: GET,
+ http.request.headers.host: localhost:00000,
+ http.route: api/environment,
+ http.status_code: 200,
+ http.url: http://localhost:00000/api/environment,
+ http.useragent: testhelper,
+ language: dotnet,
+ runtime-id: Guid_1,
+ span.kind: server,
+ version: 1.0.0,
+ _dd.code_origin.frames.0.column: 0,
+ _dd.code_origin.frames.0.file:
+ _dd.code_origin.frames.0.index: 0,
+ _dd.code_origin.frames.0.line: 0,
+ _dd.code_origin.frames.0.method: Environment,
+ _dd.code_origin.frames.0.type: Samples.AspNetMvc5.Controllers.ApiController,
+ _dd.code_origin.type: entry
+ },
+ Metrics: {
+ process_id: 0,
+ _dd.top_level: 1.0,
+ _dd.tracer_kr: 1.0,
+ _sampling_priority_v1: 1.0
+ }
+ }
+]
\ No newline at end of file
diff --git a/tracer/test/snapshots/AspNetWebApi2CodeOriginTests.Integrated.__path=_api_environment_statusCode=200.verified.txt b/tracer/test/snapshots/AspNetWebApi2CodeOriginTests.Integrated.__path=_api_environment_statusCode=200.verified.txt
new file mode 100644
index 000000000000..940a619c1ad3
--- /dev/null
+++ b/tracer/test/snapshots/AspNetWebApi2CodeOriginTests.Integrated.__path=_api_environment_statusCode=200.verified.txt
@@ -0,0 +1,59 @@
+[
+ {
+ TraceId: Id_1,
+ SpanId: Id_2,
+ Name: aspnet-webapi.request,
+ Resource: GET /api/environment,
+ Service: sample,
+ Type: web,
+ ParentId: Id_3,
+ Tags: {
+ aspnet.route: api/environment,
+ component: aspnet,
+ env: integration_tests,
+ http.method: GET,
+ http.request.headers.host: localhost:00000,
+ http.status_code: 200,
+ http.url: http://localhost:00000/api/environment,
+ http.useragent: testhelper,
+ language: dotnet,
+ span.kind: server,
+ version: 1.0.0
+ }
+ },
+ {
+ TraceId: Id_1,
+ SpanId: Id_3,
+ Name: aspnet.request,
+ Resource: GET /api/environment,
+ Service: sample,
+ Type: web,
+ Tags: {
+ component: aspnet,
+ env: integration_tests,
+ http.method: GET,
+ http.request.headers.host: localhost:00000,
+ http.route: api/environment,
+ http.status_code: 200,
+ http.url: http://localhost:00000/api/environment,
+ http.useragent: testhelper,
+ language: dotnet,
+ runtime-id: Guid_1,
+ span.kind: server,
+ version: 1.0.0,
+ _dd.code_origin.frames.0.column: 0,
+ _dd.code_origin.frames.0.file:
+ _dd.code_origin.frames.0.index: 0,
+ _dd.code_origin.frames.0.line: 0,
+ _dd.code_origin.frames.0.method: Environment,
+ _dd.code_origin.frames.0.type: Samples.AspNetMvc5.Controllers.ApiController,
+ _dd.code_origin.type: entry
+ },
+ Metrics: {
+ process_id: 0,
+ _dd.top_level: 1.0,
+ _dd.tracer_kr: 1.0,
+ _sampling_priority_v1: 1.0
+ }
+ }
+]
\ No newline at end of file
From bd252b8ba4b5c9a2c6b8476957de471204ecd174 Mon Sep 17 00:00:00 2001
From: dudik <8845578+dudikeleti@users.noreply.github.com>
Date: Tue, 10 Feb 2026 18:40:03 +0100
Subject: [PATCH 6/7] fix security integration test
---
.../Datadog.Trace.Security.IntegrationTests/AspNetWebApi.cs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tracer/test/Datadog.Trace.Security.IntegrationTests/AspNetWebApi.cs b/tracer/test/Datadog.Trace.Security.IntegrationTests/AspNetWebApi.cs
index 7aaca72b8f8a..739038c21c17 100644
--- a/tracer/test/Datadog.Trace.Security.IntegrationTests/AspNetWebApi.cs
+++ b/tracer/test/Datadog.Trace.Security.IntegrationTests/AspNetWebApi.cs
@@ -105,7 +105,10 @@ public async Task TestBlockedRequest(string test)
var settings = VerifyHelper.GetSpanVerifierSettings(test);
FilterConnectionHeader(settings);
- await TestAppSecRequestWithVerifyAsync(_iisFixture.Agent, url, null, 5, 1, settings, userAgent: "Hello/V");
+ // When AppSec is enabled, the request is blocked early and only the ASP.NET root span is created.
+ // When AppSec is disabled, the request completes normally and we also get the Web API span.
+ var spansPerRequest = SecurityEnabled ? 1 : 2;
+ await TestAppSecRequestWithVerifyAsync(_iisFixture.Agent, url, null, 5, spansPerRequest, settings, userAgent: "Hello/V");
}
[Trait("Category", "EndToEnd")]
From 22e8dbd040b332c0cae976e0db36241a0bfc9191 Mon Sep 17 00:00:00 2001
From: dudik <8845578+dudikeleti@users.noreply.github.com>
Date: Wed, 11 Feb 2026 12:42:20 +0100
Subject: [PATCH 7/7] update generated source
---
tracer/build/supported_calltargets.g.json | 4 ++--
.../Generated/generated_calltargets.g.cpp | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/tracer/build/supported_calltargets.g.json b/tracer/build/supported_calltargets.g.json
index e88f055d8947..cde0f5b56113 100644
--- a/tracer/build/supported_calltargets.g.json
+++ b/tracer/build/supported_calltargets.g.json
@@ -1569,7 +1569,7 @@
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet.ControllerActionInvoker_InvokeAction_Integration",
"IntegrationKind": 0,
"IsAdoNetIntegration": false,
- "InstrumentationCategory": 6
+ "InstrumentationCategory": 7
},
{
"IntegrationName": "AspNetWebApi2",
@@ -1620,7 +1620,7 @@
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet.ReflectedHttpActionDescriptor_ExecuteAsync_Integration",
"IntegrationKind": 0,
"IsAdoNetIntegration": false,
- "InstrumentationCategory": 6
+ "InstrumentationCategory": 7
},
{
"IntegrationName": "AspNetWebApi2",
diff --git a/tracer/src/Datadog.Tracer.Native/Generated/generated_calltargets.g.cpp b/tracer/src/Datadog.Tracer.Native/Generated/generated_calltargets.g.cpp
index dff661caa48c..6cbb5e6e48fb 100644
--- a/tracer/src/Datadog.Tracer.Native/Generated/generated_calltargets.g.cpp
+++ b/tracer/src/Datadog.Tracer.Native/Generated/generated_calltargets.g.cpp
@@ -483,9 +483,9 @@ std::vector callTargets =
#if _WIN32
{(WCHAR*)WStr("System.Web.Mvc"),(WCHAR*)WStr("System.Web.Mvc.Async.AsyncControllerActionInvoker"),(WCHAR*)WStr("BeginInvokeAction"),sig145,5,4,0,0,5,65535,65535,assemblyName,(WCHAR*)WStr("Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet.AsyncControllerActionInvoker_BeginInvokeAction_Integration"),CallTargetKind::Default,1,1},
{(WCHAR*)WStr("System.Web.Mvc"),(WCHAR*)WStr("System.Web.Mvc.Async.AsyncControllerActionInvoker"),(WCHAR*)WStr("EndInvokeAction"),sig124,2,4,0,0,5,65535,65535,assemblyName,(WCHAR*)WStr("Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet.AsyncControllerActionInvoker_EndInvokeAction_Integration"),CallTargetKind::Default,1,1},
-{(WCHAR*)WStr("System.Web.Mvc"),(WCHAR*)WStr("System.Web.Mvc.ControllerActionInvoker"),(WCHAR*)WStr("InvokeActionMethod"),sig394,4,4,0,0,5,65535,65535,assemblyName,(WCHAR*)WStr("Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet.ControllerActionInvoker_InvokeAction_Integration"),CallTargetKind::Default,6,1},
+{(WCHAR*)WStr("System.Web.Mvc"),(WCHAR*)WStr("System.Web.Mvc.ControllerActionInvoker"),(WCHAR*)WStr("InvokeActionMethod"),sig394,4,4,0,0,5,65535,65535,assemblyName,(WCHAR*)WStr("Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet.ControllerActionInvoker_InvokeAction_Integration"),CallTargetKind::Default,7,1},
{(WCHAR*)WStr("System.Web.Http"),(WCHAR*)WStr("System.Web.Http.ApiController"),(WCHAR*)WStr("ExecuteAsync"),sig299,3,5,1,0,5,65535,65535,assemblyName,(WCHAR*)WStr("Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet.ApiController_ExecuteAsync_Integration"),CallTargetKind::Default,1,1},
-{(WCHAR*)WStr("System.Web.Http"),(WCHAR*)WStr("System.Web.Http.Controllers.ReflectedHttpActionDescriptor"),(WCHAR*)WStr("ExecuteAsync"),sig298,4,5,1,0,5,65535,65535,assemblyName,(WCHAR*)WStr("Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet.ReflectedHttpActionDescriptor_ExecuteAsync_Integration"),CallTargetKind::Default,6,1},
+{(WCHAR*)WStr("System.Web.Http"),(WCHAR*)WStr("System.Web.Http.Controllers.ReflectedHttpActionDescriptor"),(WCHAR*)WStr("ExecuteAsync"),sig298,4,5,1,0,5,65535,65535,assemblyName,(WCHAR*)WStr("Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet.ReflectedHttpActionDescriptor_ExecuteAsync_Integration"),CallTargetKind::Default,7,1},
{(WCHAR*)WStr("System.Web.Http"),(WCHAR*)WStr("System.Web.Http.ExceptionHandling.ExceptionHandlerExtensions"),(WCHAR*)WStr("HandleAsync"),sig300,4,5,1,0,5,65535,65535,assemblyName,(WCHAR*)WStr("Datadog.Trace.ClrProfiler.AutoInstrumentation.AspNet.ExceptionHandlerExtensions_HandleAsync_Integration"),CallTargetKind::Default,1,1},
#endif
{(WCHAR*)WStr("AWSSDK.DynamoDBv2"),(WCHAR*)WStr("Amazon.DynamoDBv2.AmazonDynamoDBClient"),(WCHAR*)WStr("BatchGetItem"),sig008,2,3,0,0,4,65535,65535,assemblyName,(WCHAR*)WStr("Datadog.Trace.ClrProfiler.AutoInstrumentation.AWS.DynamoDb.BatchGetItemIntegration"),CallTargetKind::Default,1,15},