From 267c84b118eeede6cce7cb17520d97c982bc5785 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 23 Dec 2025 08:53:16 +0800 Subject: [PATCH 1/7] Add OTEL telemetry tags to activity --- src/Hosting/Hosting.sln | 151 ++++++++++ .../Internal/HostingApplicationDiagnostics.cs | 44 ++- .../src/Internal/HostingTelemetryHelpers.cs | 6 +- .../HostingApplicationDiagnosticsTests.cs | 268 ++++++++++++++++++ .../Diagnostics/RouteDiagnosticsHelpers.cs | 2 +- 5 files changed, 466 insertions(+), 5 deletions(-) create mode 100644 src/Hosting/Hosting.sln diff --git a/src/Hosting/Hosting.sln b/src/Hosting/Hosting.sln new file mode 100644 index 000000000000..3d121eed6b0b --- /dev/null +++ b/src/Hosting/Hosting.sln @@ -0,0 +1,151 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Abstractions", "Abstractions", "{67BFE618-E232-7DE5-34A1-02EE902F7C19}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosting", "Hosting", "{916B61B3-312D-841D-1457-818AC0DB32BA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server.Abstractions", "Server.Abstractions", "{CE483CB1-90CB-EB79-62C9-23A75A6C4A3B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server.IntegrationTesting", "Server.IntegrationTesting", "{F8F4454E-C40F-02A5-7CAB-41383D2B66CE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0C88DD14-F956-CE84-757C-A364CCF449FC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestHost", "TestHost", "{51F7520B-358C-1EAF-AC35-3E2A28DCC9EF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowsServices", "WindowsServices", "{1905AEDB-A5C8-16A6-9852-790772961605}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.Abstractions", "Abstractions\src\Microsoft.AspNetCore.Hosting.Abstractions.csproj", "{852A9DB3-52A1-A435-CA6B-87EE73599850}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting", "Hosting\src\Microsoft.AspNetCore.Hosting.csproj", "{7AA70CDA-ECD8-6E57-0E02-A101F2817885}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.Tests", "Hosting\test\Microsoft.AspNetCore.Hosting.Tests.csproj", "{88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenericWebHost", "samples\GenericWebHost\GenericWebHost.csproj", "{C4388D9D-798F-479D-E51E-85114D2892D5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleStartups", "samples\SampleStartups\SampleStartups.csproj", "{64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.Server.Abstractions", "Server.Abstractions\src\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", "{C9D2F0E5-C257-E9C5-93E6-0902677F26D1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Server.IntegrationTesting", "Server.IntegrationTesting\src\Microsoft.AspNetCore.Server.IntegrationTesting.csproj", "{55A1D5F0-1175-D0C0-3710-F48709BD41F5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.FunctionalTests", "test\FunctionalTests\Microsoft.AspNetCore.Hosting.FunctionalTests.csproj", "{76C2B280-8473-4356-5864-4188BB454743}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.TestHost", "TestHost\src\Microsoft.AspNetCore.TestHost.csproj", "{2F703095-EC1B-575A-7CF1-7853B4669091}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.TestHost.Tests", "TestHost\test\Microsoft.AspNetCore.TestHost.Tests.csproj", "{B84B68AD-9BA8-C53F-529E-80397D24BF87}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.WindowsServices", "WindowsServices\src\Microsoft.AspNetCore.Hosting.WindowsServices.csproj", "{D092D56B-04E4-EEED-7039-AE09E9875DD2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.WindowsServices.Tests", "WindowsServices\test\Microsoft.AspNetCore.Hosting.WindowsServices.Tests.csproj", "{085BFDB1-E383-79DF-96A9-57B4A953E2B3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicLinkedApp", "test\testassets\BasicLinkedApp\BasicLinkedApp.csproj", "{A3BF017E-6643-4EDA-399D-6FDBB464D174}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IStartupInjectionAssemblyName", "test\testassets\IStartupInjectionAssemblyName\IStartupInjectionAssemblyName.csproj", "{8E0C8C36-6432-15B3-0820-8B73DA2DFBB6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.TestSites", "test\testassets\Microsoft.AspNetCore.Hosting.TestSites\Microsoft.AspNetCore.Hosting.TestSites.csproj", "{6E49A448-7894-5386-FB13-69B348BBDBC0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestStartupAssembly1", "test\testassets\TestStartupAssembly1\TestStartupAssembly1.csproj", "{72EFE64E-5B24-C84C-6667-1674D729D327}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {852A9DB3-52A1-A435-CA6B-87EE73599850}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {852A9DB3-52A1-A435-CA6B-87EE73599850}.Debug|Any CPU.Build.0 = Debug|Any CPU + {852A9DB3-52A1-A435-CA6B-87EE73599850}.Release|Any CPU.ActiveCfg = Release|Any CPU + {852A9DB3-52A1-A435-CA6B-87EE73599850}.Release|Any CPU.Build.0 = Release|Any CPU + {7AA70CDA-ECD8-6E57-0E02-A101F2817885}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AA70CDA-ECD8-6E57-0E02-A101F2817885}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AA70CDA-ECD8-6E57-0E02-A101F2817885}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AA70CDA-ECD8-6E57-0E02-A101F2817885}.Release|Any CPU.Build.0 = Release|Any CPU + {88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6}.Release|Any CPU.Build.0 = Release|Any CPU + {C4388D9D-798F-479D-E51E-85114D2892D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4388D9D-798F-479D-E51E-85114D2892D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4388D9D-798F-479D-E51E-85114D2892D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4388D9D-798F-479D-E51E-85114D2892D5}.Release|Any CPU.Build.0 = Release|Any CPU + {64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA}.Release|Any CPU.Build.0 = Release|Any CPU + {C9D2F0E5-C257-E9C5-93E6-0902677F26D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9D2F0E5-C257-E9C5-93E6-0902677F26D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9D2F0E5-C257-E9C5-93E6-0902677F26D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9D2F0E5-C257-E9C5-93E6-0902677F26D1}.Release|Any CPU.Build.0 = Release|Any CPU + {55A1D5F0-1175-D0C0-3710-F48709BD41F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55A1D5F0-1175-D0C0-3710-F48709BD41F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55A1D5F0-1175-D0C0-3710-F48709BD41F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55A1D5F0-1175-D0C0-3710-F48709BD41F5}.Release|Any CPU.Build.0 = Release|Any CPU + {76C2B280-8473-4356-5864-4188BB454743}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76C2B280-8473-4356-5864-4188BB454743}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76C2B280-8473-4356-5864-4188BB454743}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76C2B280-8473-4356-5864-4188BB454743}.Release|Any CPU.Build.0 = Release|Any CPU + {2F703095-EC1B-575A-7CF1-7853B4669091}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F703095-EC1B-575A-7CF1-7853B4669091}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F703095-EC1B-575A-7CF1-7853B4669091}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F703095-EC1B-575A-7CF1-7853B4669091}.Release|Any CPU.Build.0 = Release|Any CPU + {B84B68AD-9BA8-C53F-529E-80397D24BF87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B84B68AD-9BA8-C53F-529E-80397D24BF87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B84B68AD-9BA8-C53F-529E-80397D24BF87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B84B68AD-9BA8-C53F-529E-80397D24BF87}.Release|Any CPU.Build.0 = Release|Any CPU + {D092D56B-04E4-EEED-7039-AE09E9875DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D092D56B-04E4-EEED-7039-AE09E9875DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D092D56B-04E4-EEED-7039-AE09E9875DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D092D56B-04E4-EEED-7039-AE09E9875DD2}.Release|Any CPU.Build.0 = Release|Any CPU + {085BFDB1-E383-79DF-96A9-57B4A953E2B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {085BFDB1-E383-79DF-96A9-57B4A953E2B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {085BFDB1-E383-79DF-96A9-57B4A953E2B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {085BFDB1-E383-79DF-96A9-57B4A953E2B3}.Release|Any CPU.Build.0 = Release|Any CPU + {A3BF017E-6643-4EDA-399D-6FDBB464D174}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3BF017E-6643-4EDA-399D-6FDBB464D174}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3BF017E-6643-4EDA-399D-6FDBB464D174}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3BF017E-6643-4EDA-399D-6FDBB464D174}.Release|Any CPU.Build.0 = Release|Any CPU + {8E0C8C36-6432-15B3-0820-8B73DA2DFBB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E0C8C36-6432-15B3-0820-8B73DA2DFBB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E0C8C36-6432-15B3-0820-8B73DA2DFBB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E0C8C36-6432-15B3-0820-8B73DA2DFBB6}.Release|Any CPU.Build.0 = Release|Any CPU + {6E49A448-7894-5386-FB13-69B348BBDBC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E49A448-7894-5386-FB13-69B348BBDBC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E49A448-7894-5386-FB13-69B348BBDBC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E49A448-7894-5386-FB13-69B348BBDBC0}.Release|Any CPU.Build.0 = Release|Any CPU + {72EFE64E-5B24-C84C-6667-1674D729D327}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72EFE64E-5B24-C84C-6667-1674D729D327}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72EFE64E-5B24-C84C-6667-1674D729D327}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72EFE64E-5B24-C84C-6667-1674D729D327}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {852A9DB3-52A1-A435-CA6B-87EE73599850} = {67BFE618-E232-7DE5-34A1-02EE902F7C19} + {7AA70CDA-ECD8-6E57-0E02-A101F2817885} = {916B61B3-312D-841D-1457-818AC0DB32BA} + {88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6} = {916B61B3-312D-841D-1457-818AC0DB32BA} + {C4388D9D-798F-479D-E51E-85114D2892D5} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} + {64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} + {C9D2F0E5-C257-E9C5-93E6-0902677F26D1} = {CE483CB1-90CB-EB79-62C9-23A75A6C4A3B} + {55A1D5F0-1175-D0C0-3710-F48709BD41F5} = {F8F4454E-C40F-02A5-7CAB-41383D2B66CE} + {76C2B280-8473-4356-5864-4188BB454743} = {0C88DD14-F956-CE84-757C-A364CCF449FC} + {2F703095-EC1B-575A-7CF1-7853B4669091} = {51F7520B-358C-1EAF-AC35-3E2A28DCC9EF} + {B84B68AD-9BA8-C53F-529E-80397D24BF87} = {51F7520B-358C-1EAF-AC35-3E2A28DCC9EF} + {D092D56B-04E4-EEED-7039-AE09E9875DD2} = {1905AEDB-A5C8-16A6-9852-790772961605} + {085BFDB1-E383-79DF-96A9-57B4A953E2B3} = {1905AEDB-A5C8-16A6-9852-790772961605} + {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} = {0C88DD14-F956-CE84-757C-A364CCF449FC} + {A3BF017E-6643-4EDA-399D-6FDBB464D174} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {8E0C8C36-6432-15B3-0820-8B73DA2DFBB6} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {6E49A448-7894-5386-FB13-69B348BBDBC0} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + {72EFE64E-5B24-C84C-6667-1674D729D327} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {649ED017-B5C5-467C-B67D-E6C87FF4A065} + EndGlobalSection +EndGlobal diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 67f584462e6e..21177b44a697 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.Shared; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Hosting; @@ -233,7 +234,7 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp // can capture the activity as a metric exemplar. if (activity is not null) { - StopActivity(httpContext, activity, context.HasDiagnosticListener); + StopActivity(httpContext, activity, exception, context.HasDiagnosticListener); } if (context.EventLogEnabled) @@ -473,7 +474,7 @@ private static TagList CreateInitializeActivityTags(HttpContext httpContext) HostingTelemetryHelpers.SetActivityHttpMethodTags(ref creationTags, request.Method); - if (request.Headers.TryGetValue("User-Agent", out var values)) + if (request.Headers.TryGetValue(HeaderNames.UserAgent, out var values)) { var userAgent = values.Count > 0 ? values[0] : null; if (!string.IsNullOrEmpty(userAgent)) @@ -491,8 +492,13 @@ private static TagList CreateInitializeActivityTags(HttpContext httpContext) } [MethodImpl(MethodImplOptions.NoInlining)] - private void StopActivity(HttpContext httpContext, Activity activity, bool hasDiagnosticListener) + private void StopActivity(HttpContext httpContext, Activity activity, Exception? exception, bool hasDiagnosticListener) { + if (!SuppressActivityOpenTelemetryData) + { + SetActivityEndTags(httpContext, activity, exception); + } + if (hasDiagnosticListener) { StopActivity(activity, httpContext); @@ -503,6 +509,38 @@ private void StopActivity(HttpContext httpContext, Activity activity, bool hasDi } } + private static void SetActivityEndTags(HttpContext httpContext, Activity activity, Exception? exception) + { + var response = httpContext.Response; + + activity.SetTag(HostingTelemetryHelpers.AttributeHttpResponseStatusCode, HostingTelemetryHelpers.GetBoxedStatusCode(response.StatusCode)); + + if (HostingTelemetryHelpers.TryGetHttpVersion(httpContext.Request.Protocol, out var httpVersion)) + { + activity.SetTag(HostingTelemetryHelpers.AttributeNetworkProtocolVersion, httpVersion); + } + + var endpoint = HttpExtensions.GetOriginalEndpoint(httpContext); + var route = endpoint?.Metadata.GetMetadata()?.Route; + if (route != null) + { + activity.SetTag(HostingTelemetryHelpers.AttributeHttpRoute, RouteDiagnosticsHelpers.ResolveHttpRoute(route)); + } + + if (exception != null) + { + activity.SetTag(HostingTelemetryHelpers.AttributeErrorType, exception.GetType().FullName); + activity.SetStatus(ActivityStatusCode.Error, exception.Message); + } + else if (IsErrorStatusCode(response.StatusCode)) + { + activity.SetTag(HostingTelemetryHelpers.AttributeErrorType, response.StatusCode.ToString(System.Globalization.CultureInfo.InvariantCulture)); + activity.SetStatus(ActivityStatusCode.Error); + } + } + + private static bool IsErrorStatusCode(int statusCode) => statusCode >= 400; + // These are versions of DiagnosticSource.Start/StopActivity that don't allocate strings per call (see https://github.com/dotnet/corefx/issues/37055) // DynamicDependency matches the properties selected in: // https://github.com/dotnet/diagnostics/blob/7cc6fbef613cdfe5ff64393120d59d7a15e98bd6/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/HttpRequestSourceConfiguration.cs#L20-L33 diff --git a/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs b/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs index e415869e9cff..b8ebad0599ce 100644 --- a/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs +++ b/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs @@ -14,11 +14,15 @@ internal static class HostingTelemetryHelpers // Note: Not all telemetry code is using these const attribute names yet. public const string AttributeHttpRequestMethod = "http.request.method"; public const string AttributeHttpRequestMethodOriginal = "http.request.method_original"; + public const string AttributeHttpResponseStatusCode = "http.response.status_code"; + public const string AttributeHttpRoute = "http.route"; public const string AttributeUrlScheme = "url.scheme"; public const string AttributeUrlPath = "url.path"; public const string AttributeServerAddress = "server.address"; public const string AttributeServerPort = "server.port"; public const string AttributeUserAgentOriginal = "user_agent.original"; + public const string AttributeNetworkProtocolVersion = "network.protocol.version"; + public const string AttributeErrorType = "error.type"; // The value "_OTHER" is used for non-standard HTTP methods. // https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes @@ -69,7 +73,7 @@ public static bool TryGetServerPort(HostString host, string scheme, [NotNullWhen public static object GetBoxedStatusCode(int statusCode) { - object[] boxes = BoxedStatusCodes; + var boxes = BoxedStatusCodes; return (uint)statusCode < (uint)boxes.Length ? boxes[statusCode] ??= statusCode : statusCode; diff --git a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs index 032eab752397..1a68cdf1abf7 100644 --- a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.Metrics; using System.Diagnostics.Tracing; +using System.Globalization; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -1214,6 +1215,273 @@ public void ActivityListeners_DefaultPorts(string scheme, int? expectedPort) Assert.Equal(expectedPort, (int?)actualPort); } + [Fact] + public void ActivityListeners_DontSuppressActivityTags_EndTagsAdded() + { + var testSource = new ActivitySource(Path.GetRandomFileName()); + var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => + { + c.Request.Protocol = "HTTP/1.1"; + c.Request.Scheme = "http"; + c.Request.Method = "GET"; + c.Request.Path = "/hello"; + c.Response.StatusCode = 200; + }); + + Activity stoppedActivity = null; + using var listener = new ActivityListener + { + ShouldListenTo = activitySource => ReferenceEquals(activitySource, testSource), + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStopped = activity => stoppedActivity = activity + }; + + ActivitySource.AddActivityListener(listener); + + var context = hostingApplication.CreateContext(features); + context.HttpContext.SetEndpoint(new Endpoint( + c => Task.CompletedTask, + new EndpointMetadataCollection(new TestRouteDiagnosticsMetadata()), + "Test endpoint")); + hostingApplication.DisposeContext(context, null); + + Assert.NotNull(stoppedActivity); + var tags = stoppedActivity.TagObjects.ToDictionary(); + + Assert.Equal(200, tags["http.response.status_code"]); + Assert.Equal("1.1", tags["network.protocol.version"]); + Assert.Equal("hello/{name}", tags["http.route"]); + Assert.False(tags.ContainsKey("error.type")); + Assert.Equal(ActivityStatusCode.Unset, stoppedActivity.Status); + } + + [Fact] + public void ActivityListeners_DontSuppressActivityTags_ErrorStatusCodeSetsErrorType() + { + var testSource = new ActivitySource(Path.GetRandomFileName()); + var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => + { + c.Request.Protocol = "HTTP/2"; + c.Request.Scheme = "https"; + c.Request.Method = "POST"; + c.Request.Path = "/api/test"; + c.Response.StatusCode = 500; + }); + + Activity stoppedActivity = null; + using var listener = new ActivityListener + { + ShouldListenTo = activitySource => ReferenceEquals(activitySource, testSource), + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStopped = activity => stoppedActivity = activity + }; + + ActivitySource.AddActivityListener(listener); + + var context = hostingApplication.CreateContext(features); + hostingApplication.DisposeContext(context, null); + + Assert.NotNull(stoppedActivity); + var tags = stoppedActivity.TagObjects.ToDictionary(); + + Assert.Equal(500, tags["http.response.status_code"]); + Assert.Equal("2", tags["network.protocol.version"]); + Assert.Equal("500", tags["error.type"]); + Assert.Equal(ActivityStatusCode.Error, stoppedActivity.Status); + } + + [Fact] + public void ActivityListeners_DontSuppressActivityTags_ExceptionSetsErrorType() + { + var testSource = new ActivitySource(Path.GetRandomFileName()); + var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => + { + c.Request.Protocol = "HTTP/1.1"; + c.Request.Scheme = "http"; + c.Request.Method = "GET"; + c.Request.Path = "/error"; + c.Response.StatusCode = 500; + }); + + Activity stoppedActivity = null; + using var listener = new ActivityListener + { + ShouldListenTo = activitySource => ReferenceEquals(activitySource, testSource), + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStopped = activity => stoppedActivity = activity + }; + + ActivitySource.AddActivityListener(listener); + + var context = hostingApplication.CreateContext(features); + var exception = new InvalidOperationException("Test exception"); + hostingApplication.DisposeContext(context, exception); + + Assert.NotNull(stoppedActivity); + var tags = stoppedActivity.TagObjects.ToDictionary(); + + Assert.Equal(500, tags["http.response.status_code"]); + Assert.Equal("1.1", tags["network.protocol.version"]); + Assert.Equal("System.InvalidOperationException", tags["error.type"]); + Assert.Equal(ActivityStatusCode.Error, stoppedActivity.Status); + Assert.Equal("Test exception", stoppedActivity.StatusDescription); + } + + [Theory] + [InlineData("HTTP/1.0", "1.0")] + [InlineData("HTTP/1.1", "1.1")] + [InlineData("HTTP/2", "2")] + [InlineData("HTTP/3", "3")] + public void ActivityListeners_DontSuppressActivityTags_HttpVersionMapped(string protocol, string expectedVersion) + { + var testSource = new ActivitySource(Path.GetRandomFileName()); + var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => + { + c.Request.Protocol = protocol; + c.Request.Scheme = "http"; + c.Request.Method = "GET"; + c.Request.Path = "/"; + c.Response.StatusCode = 200; + }); + + Activity stoppedActivity = null; + using var listener = new ActivityListener + { + ShouldListenTo = activitySource => ReferenceEquals(activitySource, testSource), + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStopped = activity => stoppedActivity = activity + }; + + ActivitySource.AddActivityListener(listener); + + var context = hostingApplication.CreateContext(features); + hostingApplication.DisposeContext(context, null); + + Assert.NotNull(stoppedActivity); + var tags = stoppedActivity.TagObjects.ToDictionary(); + + Assert.Equal(expectedVersion, tags["network.protocol.version"]); + } + + [Fact] + public void ActivityListeners_SuppressActivityTags_NoEndTagsAdded() + { + var testSource = new ActivitySource(Path.GetRandomFileName()); + // Default is to suppress OTel data + var hostingApplication = CreateApplication(out var features, activitySource: testSource, configure: c => + { + c.Request.Protocol = "HTTP/1.1"; + c.Request.Scheme = "http"; + c.Request.Method = "GET"; + c.Request.Path = "/hello"; + c.Response.StatusCode = 200; + }); + + Activity stoppedActivity = null; + using var listener = new ActivityListener + { + ShouldListenTo = activitySource => ReferenceEquals(activitySource, testSource), + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStopped = activity => stoppedActivity = activity + }; + + ActivitySource.AddActivityListener(listener); + + var context = hostingApplication.CreateContext(features); + context.HttpContext.SetEndpoint(new Endpoint( + c => Task.CompletedTask, + new EndpointMetadataCollection(new TestRouteDiagnosticsMetadata()), + "Test endpoint")); + hostingApplication.DisposeContext(context, null); + + Assert.NotNull(stoppedActivity); + var tags = stoppedActivity.TagObjects.ToDictionary(); + + // No end tags should be added when suppressed + Assert.False(tags.ContainsKey("http.response.status_code")); + Assert.False(tags.ContainsKey("network.protocol.version")); + Assert.False(tags.ContainsKey("http.route")); + Assert.False(tags.ContainsKey("error.type")); + } + + [Theory] + [InlineData(400)] + [InlineData(404)] + [InlineData(499)] + [InlineData(500)] + [InlineData(503)] + public void ActivityListeners_DontSuppressActivityTags_ErrorStatusCodesSetErrorType(int statusCode) + { + var testSource = new ActivitySource(Path.GetRandomFileName()); + var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => + { + c.Request.Protocol = "HTTP/1.1"; + c.Request.Scheme = "http"; + c.Request.Method = "GET"; + c.Request.Path = "/"; + c.Response.StatusCode = statusCode; + }); + + Activity stoppedActivity = null; + using var listener = new ActivityListener + { + ShouldListenTo = activitySource => ReferenceEquals(activitySource, testSource), + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStopped = activity => stoppedActivity = activity + }; + + ActivitySource.AddActivityListener(listener); + + var context = hostingApplication.CreateContext(features); + hostingApplication.DisposeContext(context, null); + + Assert.NotNull(stoppedActivity); + var tags = stoppedActivity.TagObjects.ToDictionary(); + + Assert.Equal(statusCode, tags["http.response.status_code"]); + Assert.Equal(statusCode.ToString(CultureInfo.InvariantCulture), tags["error.type"]); + Assert.Equal(ActivityStatusCode.Error, stoppedActivity.Status); + } + + [Theory] + [InlineData(200)] + [InlineData(201)] + [InlineData(204)] + [InlineData(301)] + [InlineData(399)] + public void ActivityListeners_DontSuppressActivityTags_SuccessStatusCodesNoErrorType(int statusCode) + { + var testSource = new ActivitySource(Path.GetRandomFileName()); + var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => + { + c.Request.Protocol = "HTTP/1.1"; + c.Request.Scheme = "http"; + c.Request.Method = "GET"; + c.Request.Path = "/"; + c.Response.StatusCode = statusCode; + }); + + Activity stoppedActivity = null; + using var listener = new ActivityListener + { + ShouldListenTo = activitySource => ReferenceEquals(activitySource, testSource), + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStopped = activity => stoppedActivity = activity + }; + + ActivitySource.AddActivityListener(listener); + + var context = hostingApplication.CreateContext(features); + hostingApplication.DisposeContext(context, null); + + Assert.NotNull(stoppedActivity); + var tags = stoppedActivity.TagObjects.ToDictionary(); + + Assert.Equal(statusCode, tags["http.response.status_code"]); + Assert.False(tags.ContainsKey("error.type")); + Assert.Equal(ActivityStatusCode.Unset, stoppedActivity.Status); + } + [Fact] public void RequestLogs() { diff --git a/src/Shared/Diagnostics/RouteDiagnosticsHelpers.cs b/src/Shared/Diagnostics/RouteDiagnosticsHelpers.cs index 3084f3caf57d..3b97bcba2d4a 100644 --- a/src/Shared/Diagnostics/RouteDiagnosticsHelpers.cs +++ b/src/Shared/Diagnostics/RouteDiagnosticsHelpers.cs @@ -11,7 +11,7 @@ public static string ResolveHttpRoute(string route) // 1. It is potentially confusing, "What does empty string mean?" // 2. Some telemetry tools have problems with empty string values, e.g. https://github.com/dotnet/aspnetcore/pull/62432 // - // The fix is to resolve empty string route to "/" in metrics. + // The fix is to resolve empty string route to "/" in telemetry attributes. return string.IsNullOrEmpty(route) ? "/" : route; } } From 9e8301ba9b6e7540d396c2ec53ee9c9e881910d8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 23 Dec 2025 09:02:45 +0800 Subject: [PATCH 2/7] Use OTEL attribute name constants everywhere --- .../Hosting/src/Internal/HostingMetrics.cs | 12 +-- .../src/Internal/HostingTelemetryHelpers.cs | 1 - .../HostingApplicationDiagnosticsTests.cs | 102 +++++++++--------- .../Hosting/test/HostingMetricsTests.cs | 8 +- 4 files changed, 61 insertions(+), 62 deletions(-) diff --git a/src/Hosting/Hosting/src/Internal/HostingMetrics.cs b/src/Hosting/Hosting/src/Internal/HostingMetrics.cs index 2615bf5608f7..47b25a792fd2 100644 --- a/src/Hosting/Hosting/src/Internal/HostingMetrics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingMetrics.cs @@ -56,7 +56,7 @@ public void RequestEnd(string protocol, string scheme, string method, string? ro { if (HostingTelemetryHelpers.TryGetHttpVersion(protocol, out var httpVersion)) { - tags.Add("network.protocol.version", httpVersion); + tags.Add(HostingTelemetryHelpers.AttributeNetworkProtocolVersion, httpVersion); } if (unhandledRequest) { @@ -64,10 +64,10 @@ public void RequestEnd(string protocol, string scheme, string method, string? ro } // Add information gathered during request. - tags.Add("http.response.status_code", HostingTelemetryHelpers.GetBoxedStatusCode(statusCode)); + tags.Add(HostingTelemetryHelpers.AttributeHttpResponseStatusCode, HostingTelemetryHelpers.GetBoxedStatusCode(statusCode)); if (route != null) { - tags.Add("http.route", RouteDiagnosticsHelpers.ResolveHttpRoute(route)); + tags.Add(HostingTelemetryHelpers.AttributeHttpRoute, RouteDiagnosticsHelpers.ResolveHttpRoute(route)); } // Add before some built in tags so custom tags are prioritized when dealing with duplicates. @@ -85,7 +85,7 @@ public void RequestEnd(string protocol, string scheme, string method, string? ro { // Exception tag could have been added by middleware. If an exception is later thrown in request pipeline // then we don't want to add a duplicate tag here because that breaks some metrics systems. - tags.TryAddTag("error.type", exception.GetType().FullName); + tags.TryAddTag(HostingTelemetryHelpers.AttributeErrorType, exception.GetType().FullName); } var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp); @@ -102,7 +102,7 @@ public void Dispose() private static void InitializeRequestTags(ref TagList tags, string scheme, string method) { - tags.Add("url.scheme", scheme); - tags.Add("http.request.method", HostingTelemetryHelpers.GetNormalizedHttpMethod(method)); + tags.Add(HostingTelemetryHelpers.AttributeUrlScheme, scheme); + tags.Add(HostingTelemetryHelpers.AttributeHttpRequestMethod, HostingTelemetryHelpers.GetNormalizedHttpMethod(method)); } } diff --git a/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs b/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs index b8ebad0599ce..b61401527f3a 100644 --- a/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs +++ b/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs @@ -11,7 +11,6 @@ namespace Microsoft.AspNetCore.Hosting; internal static class HostingTelemetryHelpers { // Semantic Conventions for HTTP. - // Note: Not all telemetry code is using these const attribute names yet. public const string AttributeHttpRequestMethod = "http.request.method"; public const string AttributeHttpRequestMethodOriginal = "http.request.method_original"; public const string AttributeHttpResponseStatusCode = "http.response.status_code"; diff --git a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs index 1a68cdf1abf7..daa5565d2dda 100644 --- a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs @@ -261,8 +261,8 @@ public void Metrics_RequestChanges_OriginalValuesUsed() m => { Assert.Equal(1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }); context.HttpContext.Request.Protocol = "HTTP/2"; @@ -277,14 +277,14 @@ public void Metrics_RequestChanges_OriginalValuesUsed() m => { Assert.Equal(1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }, m => { Assert.Equal(-1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }); Assert.Empty(context.MetricsTagsFeature.TagsList); @@ -320,8 +320,8 @@ public void Metrics_Route_RouteTagReported() m => { Assert.Equal(1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }); context.HttpContext.SetEndpoint(new Endpoint( @@ -336,20 +336,20 @@ public void Metrics_Route_RouteTagReported() m => { Assert.Equal(1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }, m => { Assert.Equal(-1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }); Assert.Collection(requestDurationCollector.GetMeasurementSnapshot(), m => { Assert.True(m.Value > 0); - Assert.Equal("hello/{name}", m.Tags["http.route"]); + Assert.Equal("hello/{name}", m.Tags[HostingTelemetryHelpers.AttributeHttpRoute]); }); } @@ -385,8 +385,8 @@ public void Metrics_Route_RouteTagIsRootWhenEmpty() m => { Assert.Equal(1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }); context.HttpContext.SetEndpoint(new Endpoint( @@ -401,20 +401,20 @@ public void Metrics_Route_RouteTagIsRootWhenEmpty() m => { Assert.Equal(1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }, m => { Assert.Equal(-1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }); Assert.Collection(requestDurationCollector.GetMeasurementSnapshot(), m => { Assert.True(m.Value > 0); - Assert.Equal("/", m.Tags["http.route"]); + Assert.Equal("/", m.Tags[HostingTelemetryHelpers.AttributeHttpRoute]); }); } @@ -445,8 +445,8 @@ public void Metrics_DisableHttpMetricsWithMetadata_NoMetrics() m => { Assert.Equal(1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }); context.HttpContext.SetEndpoint(new Endpoint( @@ -461,14 +461,14 @@ public void Metrics_DisableHttpMetricsWithMetadata_NoMetrics() m => { Assert.Equal(1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }, m => { Assert.Equal(-1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }); Assert.Empty(requestDurationCollector.GetMeasurementSnapshot()); } @@ -500,8 +500,8 @@ public void Metrics_DisableHttpMetricsWithFeature_NoMetrics() m => { Assert.Equal(1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }); context.HttpContext.Features.Get().MetricsDisabled = true; @@ -516,14 +516,14 @@ public void Metrics_DisableHttpMetricsWithFeature_NoMetrics() m => { Assert.Equal(1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }, m => { Assert.Equal(-1, m.Value); - Assert.Equal("http", m.Tags["url.scheme"]); - Assert.Equal("POST", m.Tags["http.request.method"]); + Assert.Equal("http", m.Tags[HostingTelemetryHelpers.AttributeUrlScheme]); + Assert.Equal("POST", m.Tags[HostingTelemetryHelpers.AttributeHttpRequestMethod]); }); Assert.Empty(requestDurationCollector.GetMeasurementSnapshot()); Assert.False(context.MetricsTagsFeature.MetricsDisabled); @@ -1248,10 +1248,10 @@ public void ActivityListeners_DontSuppressActivityTags_EndTagsAdded() Assert.NotNull(stoppedActivity); var tags = stoppedActivity.TagObjects.ToDictionary(); - Assert.Equal(200, tags["http.response.status_code"]); - Assert.Equal("1.1", tags["network.protocol.version"]); - Assert.Equal("hello/{name}", tags["http.route"]); - Assert.False(tags.ContainsKey("error.type")); + Assert.Equal(200, tags[HostingTelemetryHelpers.AttributeHttpResponseStatusCode]); + Assert.Equal("1.1", tags[HostingTelemetryHelpers.AttributeNetworkProtocolVersion]); + Assert.Equal("hello/{name}", tags[HostingTelemetryHelpers.AttributeHttpRoute]); + Assert.False(tags.ContainsKey(HostingTelemetryHelpers.AttributeErrorType)); Assert.Equal(ActivityStatusCode.Unset, stoppedActivity.Status); } @@ -1284,9 +1284,9 @@ public void ActivityListeners_DontSuppressActivityTags_ErrorStatusCodeSetsErrorT Assert.NotNull(stoppedActivity); var tags = stoppedActivity.TagObjects.ToDictionary(); - Assert.Equal(500, tags["http.response.status_code"]); - Assert.Equal("2", tags["network.protocol.version"]); - Assert.Equal("500", tags["error.type"]); + Assert.Equal(500, tags[HostingTelemetryHelpers.AttributeHttpResponseStatusCode]); + Assert.Equal("2", tags[HostingTelemetryHelpers.AttributeNetworkProtocolVersion]); + Assert.Equal("500", tags[HostingTelemetryHelpers.AttributeErrorType]); Assert.Equal(ActivityStatusCode.Error, stoppedActivity.Status); } @@ -1320,9 +1320,9 @@ public void ActivityListeners_DontSuppressActivityTags_ExceptionSetsErrorType() Assert.NotNull(stoppedActivity); var tags = stoppedActivity.TagObjects.ToDictionary(); - Assert.Equal(500, tags["http.response.status_code"]); - Assert.Equal("1.1", tags["network.protocol.version"]); - Assert.Equal("System.InvalidOperationException", tags["error.type"]); + Assert.Equal(500, tags[HostingTelemetryHelpers.AttributeHttpResponseStatusCode]); + Assert.Equal("1.1", tags[HostingTelemetryHelpers.AttributeNetworkProtocolVersion]); + Assert.Equal("System.InvalidOperationException", tags[HostingTelemetryHelpers.AttributeErrorType]); Assert.Equal(ActivityStatusCode.Error, stoppedActivity.Status); Assert.Equal("Test exception", stoppedActivity.StatusDescription); } @@ -1360,7 +1360,7 @@ public void ActivityListeners_DontSuppressActivityTags_HttpVersionMapped(string Assert.NotNull(stoppedActivity); var tags = stoppedActivity.TagObjects.ToDictionary(); - Assert.Equal(expectedVersion, tags["network.protocol.version"]); + Assert.Equal(expectedVersion, tags[HostingTelemetryHelpers.AttributeNetworkProtocolVersion]); } [Fact] @@ -1398,10 +1398,10 @@ public void ActivityListeners_SuppressActivityTags_NoEndTagsAdded() var tags = stoppedActivity.TagObjects.ToDictionary(); // No end tags should be added when suppressed - Assert.False(tags.ContainsKey("http.response.status_code")); - Assert.False(tags.ContainsKey("network.protocol.version")); - Assert.False(tags.ContainsKey("http.route")); - Assert.False(tags.ContainsKey("error.type")); + Assert.False(tags.ContainsKey(HostingTelemetryHelpers.AttributeHttpResponseStatusCode)); + Assert.False(tags.ContainsKey(HostingTelemetryHelpers.AttributeNetworkProtocolVersion)); + Assert.False(tags.ContainsKey(HostingTelemetryHelpers.AttributeHttpRoute)); + Assert.False(tags.ContainsKey(HostingTelemetryHelpers.AttributeErrorType)); } [Theory] @@ -1438,8 +1438,8 @@ public void ActivityListeners_DontSuppressActivityTags_ErrorStatusCodesSetErrorT Assert.NotNull(stoppedActivity); var tags = stoppedActivity.TagObjects.ToDictionary(); - Assert.Equal(statusCode, tags["http.response.status_code"]); - Assert.Equal(statusCode.ToString(CultureInfo.InvariantCulture), tags["error.type"]); + Assert.Equal(statusCode, tags[HostingTelemetryHelpers.AttributeHttpResponseStatusCode]); + Assert.Equal(statusCode.ToString(CultureInfo.InvariantCulture), tags[HostingTelemetryHelpers.AttributeErrorType]); Assert.Equal(ActivityStatusCode.Error, stoppedActivity.Status); } @@ -1477,8 +1477,8 @@ public void ActivityListeners_DontSuppressActivityTags_SuccessStatusCodesNoError Assert.NotNull(stoppedActivity); var tags = stoppedActivity.TagObjects.ToDictionary(); - Assert.Equal(statusCode, tags["http.response.status_code"]); - Assert.False(tags.ContainsKey("error.type")); + Assert.Equal(statusCode, tags[HostingTelemetryHelpers.AttributeHttpResponseStatusCode]); + Assert.False(tags.ContainsKey(HostingTelemetryHelpers.AttributeErrorType)); Assert.Equal(ActivityStatusCode.Unset, stoppedActivity.Status); } diff --git a/src/Hosting/Hosting/test/HostingMetricsTests.cs b/src/Hosting/Hosting/test/HostingMetricsTests.cs index 77547bb1e462..a3a50761d170 100644 --- a/src/Hosting/Hosting/test/HostingMetricsTests.cs +++ b/src/Hosting/Hosting/test/HostingMetricsTests.cs @@ -92,15 +92,15 @@ public void MultipleRequests() static void AssertRequestDuration(CollectedMeasurement measurement, string httpVersion, int statusCode, string exceptionName = null, bool? unhandledRequest = null) { Assert.True(measurement.Value > 0); - Assert.Equal(httpVersion, (string)measurement.Tags["network.protocol.version"]); - Assert.Equal(statusCode, (int)measurement.Tags["http.response.status_code"]); + Assert.Equal(httpVersion, (string)measurement.Tags[HostingTelemetryHelpers.AttributeNetworkProtocolVersion]); + Assert.Equal(statusCode, (int)measurement.Tags[HostingTelemetryHelpers.AttributeHttpResponseStatusCode]); if (exceptionName == null) { - Assert.False(measurement.Tags.ContainsKey("error.type")); + Assert.False(measurement.Tags.ContainsKey(HostingTelemetryHelpers.AttributeErrorType)); } else { - Assert.Equal(exceptionName, (string)measurement.Tags["error.type"]); + Assert.Equal(exceptionName, (string)measurement.Tags[HostingTelemetryHelpers.AttributeErrorType]); } if (unhandledRequest ?? false) { From ac1487d5e41f54046ec23c683531052c02b1c7c9 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 23 Dec 2025 09:14:10 +0800 Subject: [PATCH 3/7] Default including telemetry to true --- .../Internal/HostingApplicationDiagnostics.cs | 4 ++-- .../HostingApplicationDiagnosticsTests.cs | 21 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 21177b44a697..201b62962c99 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -58,10 +58,10 @@ public HostingApplicationDiagnostics( private static bool GetSuppressActivityOpenTelemetryData() { - // Default to true if the switch isn't set. + // Default to false if the switch isn't set. if (!AppContext.TryGetSwitch("Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData", out var enabled)) { - return true; + return false; } return enabled; diff --git a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs index daa5565d2dda..2680facdd5b5 100644 --- a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs @@ -1082,10 +1082,10 @@ public void ActivityOnImportHookIsCalled() } [Fact] - public void ActivityListenersAreCalled() + public void ActivityListeners_SuppressActivityTags_NoTagsAdded() { var testSource = new ActivitySource(Path.GetRandomFileName()); - var hostingApplication = CreateApplication(out var features, activitySource: testSource); + var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: true); var parentSpanId = ""; var tags = new List>(); using var listener = new ActivityListener @@ -1124,7 +1124,7 @@ public void ActivityListenersAreCalled() } [Fact] - public void ActivityListeners_DontSuppressActivityTags_TagsAdded() + public void ActivityListeners_TagsAdded() { var testSource = new ActivitySource(Path.GetRandomFileName()); var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false); @@ -1216,7 +1216,7 @@ public void ActivityListeners_DefaultPorts(string scheme, int? expectedPort) } [Fact] - public void ActivityListeners_DontSuppressActivityTags_EndTagsAdded() + public void ActivityListeners_EndTagsAdded() { var testSource = new ActivitySource(Path.GetRandomFileName()); var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => @@ -1256,7 +1256,7 @@ public void ActivityListeners_DontSuppressActivityTags_EndTagsAdded() } [Fact] - public void ActivityListeners_DontSuppressActivityTags_ErrorStatusCodeSetsErrorType() + public void ActivityListeners_ErrorStatusCodeSetsErrorType() { var testSource = new ActivitySource(Path.GetRandomFileName()); var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => @@ -1291,7 +1291,7 @@ public void ActivityListeners_DontSuppressActivityTags_ErrorStatusCodeSetsErrorT } [Fact] - public void ActivityListeners_DontSuppressActivityTags_ExceptionSetsErrorType() + public void ActivityListeners_ExceptionSetsErrorType() { var testSource = new ActivitySource(Path.GetRandomFileName()); var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => @@ -1332,7 +1332,7 @@ public void ActivityListeners_DontSuppressActivityTags_ExceptionSetsErrorType() [InlineData("HTTP/1.1", "1.1")] [InlineData("HTTP/2", "2")] [InlineData("HTTP/3", "3")] - public void ActivityListeners_DontSuppressActivityTags_HttpVersionMapped(string protocol, string expectedVersion) + public void ActivityListeners_HttpVersionMapped(string protocol, string expectedVersion) { var testSource = new ActivitySource(Path.GetRandomFileName()); var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => @@ -1367,8 +1367,7 @@ public void ActivityListeners_DontSuppressActivityTags_HttpVersionMapped(string public void ActivityListeners_SuppressActivityTags_NoEndTagsAdded() { var testSource = new ActivitySource(Path.GetRandomFileName()); - // Default is to suppress OTel data - var hostingApplication = CreateApplication(out var features, activitySource: testSource, configure: c => + var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: true, configure: c => { c.Request.Protocol = "HTTP/1.1"; c.Request.Scheme = "http"; @@ -1410,7 +1409,7 @@ public void ActivityListeners_SuppressActivityTags_NoEndTagsAdded() [InlineData(499)] [InlineData(500)] [InlineData(503)] - public void ActivityListeners_DontSuppressActivityTags_ErrorStatusCodesSetErrorType(int statusCode) + public void ActivityListeners_ErrorStatusCodesSetErrorType(int statusCode) { var testSource = new ActivitySource(Path.GetRandomFileName()); var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => @@ -1449,7 +1448,7 @@ public void ActivityListeners_DontSuppressActivityTags_ErrorStatusCodesSetErrorT [InlineData(204)] [InlineData(301)] [InlineData(399)] - public void ActivityListeners_DontSuppressActivityTags_SuccessStatusCodesNoErrorType(int statusCode) + public void ActivityListeners_SuccessStatusCodesNoErrorType(int statusCode) { var testSource = new ActivitySource(Path.GetRandomFileName()); var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => From 3b385dda0471b40f7d252bc0807d68eac315bd34 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 23 Dec 2025 09:15:25 +0800 Subject: [PATCH 4/7] Clean up --- src/Hosting/Hosting.sln | 151 ---------------------------------------- 1 file changed, 151 deletions(-) delete mode 100644 src/Hosting/Hosting.sln diff --git a/src/Hosting/Hosting.sln b/src/Hosting/Hosting.sln deleted file mode 100644 index 3d121eed6b0b..000000000000 --- a/src/Hosting/Hosting.sln +++ /dev/null @@ -1,151 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Abstractions", "Abstractions", "{67BFE618-E232-7DE5-34A1-02EE902F7C19}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosting", "Hosting", "{916B61B3-312D-841D-1457-818AC0DB32BA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server.Abstractions", "Server.Abstractions", "{CE483CB1-90CB-EB79-62C9-23A75A6C4A3B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server.IntegrationTesting", "Server.IntegrationTesting", "{F8F4454E-C40F-02A5-7CAB-41383D2B66CE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0C88DD14-F956-CE84-757C-A364CCF449FC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestHost", "TestHost", "{51F7520B-358C-1EAF-AC35-3E2A28DCC9EF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowsServices", "WindowsServices", "{1905AEDB-A5C8-16A6-9852-790772961605}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.Abstractions", "Abstractions\src\Microsoft.AspNetCore.Hosting.Abstractions.csproj", "{852A9DB3-52A1-A435-CA6B-87EE73599850}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting", "Hosting\src\Microsoft.AspNetCore.Hosting.csproj", "{7AA70CDA-ECD8-6E57-0E02-A101F2817885}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.Tests", "Hosting\test\Microsoft.AspNetCore.Hosting.Tests.csproj", "{88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenericWebHost", "samples\GenericWebHost\GenericWebHost.csproj", "{C4388D9D-798F-479D-E51E-85114D2892D5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleStartups", "samples\SampleStartups\SampleStartups.csproj", "{64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.Server.Abstractions", "Server.Abstractions\src\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", "{C9D2F0E5-C257-E9C5-93E6-0902677F26D1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Server.IntegrationTesting", "Server.IntegrationTesting\src\Microsoft.AspNetCore.Server.IntegrationTesting.csproj", "{55A1D5F0-1175-D0C0-3710-F48709BD41F5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.FunctionalTests", "test\FunctionalTests\Microsoft.AspNetCore.Hosting.FunctionalTests.csproj", "{76C2B280-8473-4356-5864-4188BB454743}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.TestHost", "TestHost\src\Microsoft.AspNetCore.TestHost.csproj", "{2F703095-EC1B-575A-7CF1-7853B4669091}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.TestHost.Tests", "TestHost\test\Microsoft.AspNetCore.TestHost.Tests.csproj", "{B84B68AD-9BA8-C53F-529E-80397D24BF87}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.WindowsServices", "WindowsServices\src\Microsoft.AspNetCore.Hosting.WindowsServices.csproj", "{D092D56B-04E4-EEED-7039-AE09E9875DD2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.WindowsServices.Tests", "WindowsServices\test\Microsoft.AspNetCore.Hosting.WindowsServices.Tests.csproj", "{085BFDB1-E383-79DF-96A9-57B4A953E2B3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicLinkedApp", "test\testassets\BasicLinkedApp\BasicLinkedApp.csproj", "{A3BF017E-6643-4EDA-399D-6FDBB464D174}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IStartupInjectionAssemblyName", "test\testassets\IStartupInjectionAssemblyName\IStartupInjectionAssemblyName.csproj", "{8E0C8C36-6432-15B3-0820-8B73DA2DFBB6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.TestSites", "test\testassets\Microsoft.AspNetCore.Hosting.TestSites\Microsoft.AspNetCore.Hosting.TestSites.csproj", "{6E49A448-7894-5386-FB13-69B348BBDBC0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestStartupAssembly1", "test\testassets\TestStartupAssembly1\TestStartupAssembly1.csproj", "{72EFE64E-5B24-C84C-6667-1674D729D327}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {852A9DB3-52A1-A435-CA6B-87EE73599850}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {852A9DB3-52A1-A435-CA6B-87EE73599850}.Debug|Any CPU.Build.0 = Debug|Any CPU - {852A9DB3-52A1-A435-CA6B-87EE73599850}.Release|Any CPU.ActiveCfg = Release|Any CPU - {852A9DB3-52A1-A435-CA6B-87EE73599850}.Release|Any CPU.Build.0 = Release|Any CPU - {7AA70CDA-ECD8-6E57-0E02-A101F2817885}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7AA70CDA-ECD8-6E57-0E02-A101F2817885}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7AA70CDA-ECD8-6E57-0E02-A101F2817885}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7AA70CDA-ECD8-6E57-0E02-A101F2817885}.Release|Any CPU.Build.0 = Release|Any CPU - {88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6}.Release|Any CPU.Build.0 = Release|Any CPU - {C4388D9D-798F-479D-E51E-85114D2892D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4388D9D-798F-479D-E51E-85114D2892D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4388D9D-798F-479D-E51E-85114D2892D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4388D9D-798F-479D-E51E-85114D2892D5}.Release|Any CPU.Build.0 = Release|Any CPU - {64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA}.Release|Any CPU.Build.0 = Release|Any CPU - {C9D2F0E5-C257-E9C5-93E6-0902677F26D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C9D2F0E5-C257-E9C5-93E6-0902677F26D1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C9D2F0E5-C257-E9C5-93E6-0902677F26D1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C9D2F0E5-C257-E9C5-93E6-0902677F26D1}.Release|Any CPU.Build.0 = Release|Any CPU - {55A1D5F0-1175-D0C0-3710-F48709BD41F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {55A1D5F0-1175-D0C0-3710-F48709BD41F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {55A1D5F0-1175-D0C0-3710-F48709BD41F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {55A1D5F0-1175-D0C0-3710-F48709BD41F5}.Release|Any CPU.Build.0 = Release|Any CPU - {76C2B280-8473-4356-5864-4188BB454743}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76C2B280-8473-4356-5864-4188BB454743}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76C2B280-8473-4356-5864-4188BB454743}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76C2B280-8473-4356-5864-4188BB454743}.Release|Any CPU.Build.0 = Release|Any CPU - {2F703095-EC1B-575A-7CF1-7853B4669091}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F703095-EC1B-575A-7CF1-7853B4669091}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F703095-EC1B-575A-7CF1-7853B4669091}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F703095-EC1B-575A-7CF1-7853B4669091}.Release|Any CPU.Build.0 = Release|Any CPU - {B84B68AD-9BA8-C53F-529E-80397D24BF87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B84B68AD-9BA8-C53F-529E-80397D24BF87}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B84B68AD-9BA8-C53F-529E-80397D24BF87}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B84B68AD-9BA8-C53F-529E-80397D24BF87}.Release|Any CPU.Build.0 = Release|Any CPU - {D092D56B-04E4-EEED-7039-AE09E9875DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D092D56B-04E4-EEED-7039-AE09E9875DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D092D56B-04E4-EEED-7039-AE09E9875DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D092D56B-04E4-EEED-7039-AE09E9875DD2}.Release|Any CPU.Build.0 = Release|Any CPU - {085BFDB1-E383-79DF-96A9-57B4A953E2B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {085BFDB1-E383-79DF-96A9-57B4A953E2B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {085BFDB1-E383-79DF-96A9-57B4A953E2B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {085BFDB1-E383-79DF-96A9-57B4A953E2B3}.Release|Any CPU.Build.0 = Release|Any CPU - {A3BF017E-6643-4EDA-399D-6FDBB464D174}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A3BF017E-6643-4EDA-399D-6FDBB464D174}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A3BF017E-6643-4EDA-399D-6FDBB464D174}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A3BF017E-6643-4EDA-399D-6FDBB464D174}.Release|Any CPU.Build.0 = Release|Any CPU - {8E0C8C36-6432-15B3-0820-8B73DA2DFBB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E0C8C36-6432-15B3-0820-8B73DA2DFBB6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E0C8C36-6432-15B3-0820-8B73DA2DFBB6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E0C8C36-6432-15B3-0820-8B73DA2DFBB6}.Release|Any CPU.Build.0 = Release|Any CPU - {6E49A448-7894-5386-FB13-69B348BBDBC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6E49A448-7894-5386-FB13-69B348BBDBC0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6E49A448-7894-5386-FB13-69B348BBDBC0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6E49A448-7894-5386-FB13-69B348BBDBC0}.Release|Any CPU.Build.0 = Release|Any CPU - {72EFE64E-5B24-C84C-6667-1674D729D327}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {72EFE64E-5B24-C84C-6667-1674D729D327}.Debug|Any CPU.Build.0 = Debug|Any CPU - {72EFE64E-5B24-C84C-6667-1674D729D327}.Release|Any CPU.ActiveCfg = Release|Any CPU - {72EFE64E-5B24-C84C-6667-1674D729D327}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {852A9DB3-52A1-A435-CA6B-87EE73599850} = {67BFE618-E232-7DE5-34A1-02EE902F7C19} - {7AA70CDA-ECD8-6E57-0E02-A101F2817885} = {916B61B3-312D-841D-1457-818AC0DB32BA} - {88AFAE40-88C2-BFCA-CA8C-B4491B12A7B6} = {916B61B3-312D-841D-1457-818AC0DB32BA} - {C4388D9D-798F-479D-E51E-85114D2892D5} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} - {64D6FAD8-4110-6761-1E72-D8BBB0E1B3BA} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} - {C9D2F0E5-C257-E9C5-93E6-0902677F26D1} = {CE483CB1-90CB-EB79-62C9-23A75A6C4A3B} - {55A1D5F0-1175-D0C0-3710-F48709BD41F5} = {F8F4454E-C40F-02A5-7CAB-41383D2B66CE} - {76C2B280-8473-4356-5864-4188BB454743} = {0C88DD14-F956-CE84-757C-A364CCF449FC} - {2F703095-EC1B-575A-7CF1-7853B4669091} = {51F7520B-358C-1EAF-AC35-3E2A28DCC9EF} - {B84B68AD-9BA8-C53F-529E-80397D24BF87} = {51F7520B-358C-1EAF-AC35-3E2A28DCC9EF} - {D092D56B-04E4-EEED-7039-AE09E9875DD2} = {1905AEDB-A5C8-16A6-9852-790772961605} - {085BFDB1-E383-79DF-96A9-57B4A953E2B3} = {1905AEDB-A5C8-16A6-9852-790772961605} - {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} = {0C88DD14-F956-CE84-757C-A364CCF449FC} - {A3BF017E-6643-4EDA-399D-6FDBB464D174} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} - {8E0C8C36-6432-15B3-0820-8B73DA2DFBB6} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} - {6E49A448-7894-5386-FB13-69B348BBDBC0} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} - {72EFE64E-5B24-C84C-6667-1674D729D327} = {BEC57E9A-3A13-6F27-EBDA-E7240D8E32C7} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {649ED017-B5C5-467C-B67D-E6C87FF4A065} - EndGlobalSection -EndGlobal From 0ae333ec282349f3420ab473d75c6f82dbaa343f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 23 Dec 2025 09:42:31 +0800 Subject: [PATCH 5/7] Follow spec on reporting status code errors --- .../Internal/HostingApplicationDiagnostics.cs | 8 +- .../Hosting/src/Internal/HostingMetrics.cs | 5 ++ .../src/Internal/HostingTelemetryHelpers.cs | 6 ++ .../HostingApplicationDiagnosticsTests.cs | 59 ++++++++++++- .../Hosting/test/HostingMetricsTests.cs | 84 +++++++++++++++++++ 5 files changed, 155 insertions(+), 7 deletions(-) diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 201b62962c99..703e0c516ab8 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -458,6 +458,8 @@ private static TagList CreateInitializeActivityTags(HttpContext httpContext) // Missing values recommended by the spec are: // - url.query (need configuration around redaction to do properly) // - http.request.header. + // + // Note that these tags are added even if Activity.IsAllDataRequested is false, as they may be used in sampling decisions. var request = httpContext.Request; var creationTags = new TagList(); @@ -494,7 +496,7 @@ private static TagList CreateInitializeActivityTags(HttpContext httpContext) [MethodImpl(MethodImplOptions.NoInlining)] private void StopActivity(HttpContext httpContext, Activity activity, Exception? exception, bool hasDiagnosticListener) { - if (!SuppressActivityOpenTelemetryData) + if (!SuppressActivityOpenTelemetryData && activity.IsAllDataRequested) { SetActivityEndTags(httpContext, activity, exception); } @@ -532,15 +534,13 @@ private static void SetActivityEndTags(HttpContext httpContext, Activity activit activity.SetTag(HostingTelemetryHelpers.AttributeErrorType, exception.GetType().FullName); activity.SetStatus(ActivityStatusCode.Error, exception.Message); } - else if (IsErrorStatusCode(response.StatusCode)) + else if (HostingTelemetryHelpers.IsErrorStatusCode(response.StatusCode)) { activity.SetTag(HostingTelemetryHelpers.AttributeErrorType, response.StatusCode.ToString(System.Globalization.CultureInfo.InvariantCulture)); activity.SetStatus(ActivityStatusCode.Error); } } - private static bool IsErrorStatusCode(int statusCode) => statusCode >= 400; - // These are versions of DiagnosticSource.Start/StopActivity that don't allocate strings per call (see https://github.com/dotnet/corefx/issues/37055) // DynamicDependency matches the properties selected in: // https://github.com/dotnet/diagnostics/blob/7cc6fbef613cdfe5ff64393120d59d7a15e98bd6/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/HttpRequestSourceConfiguration.cs#L20-L33 diff --git a/src/Hosting/Hosting/src/Internal/HostingMetrics.cs b/src/Hosting/Hosting/src/Internal/HostingMetrics.cs index 47b25a792fd2..f9ecf766fb60 100644 --- a/src/Hosting/Hosting/src/Internal/HostingMetrics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingMetrics.cs @@ -87,6 +87,11 @@ public void RequestEnd(string protocol, string scheme, string method, string? ro // then we don't want to add a duplicate tag here because that breaks some metrics systems. tags.TryAddTag(HostingTelemetryHelpers.AttributeErrorType, exception.GetType().FullName); } + else if (HostingTelemetryHelpers.IsErrorStatusCode(statusCode)) + { + // Add error.type for 5xx status codes when there's no exception. + tags.TryAddTag(HostingTelemetryHelpers.AttributeErrorType, statusCode.ToString(System.Globalization.CultureInfo.InvariantCulture)); + } var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp); _requestDuration.Record(duration.TotalSeconds, tags); diff --git a/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs b/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs index b61401527f3a..cbbb54546951 100644 --- a/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs +++ b/src/Hosting/Hosting/src/Internal/HostingTelemetryHelpers.cs @@ -132,4 +132,10 @@ public static void SetActivityHttpMethodTags(ref TagList tags, string originalHt tags.Add(AttributeHttpRequestMethodOriginal, originalHttpMethod); } } + + /// + /// Determines if the status code indicates a server error (5xx). + /// Client errors (4xx) are not considered server errors. + /// + public static bool IsErrorStatusCode(int statusCode) => statusCode >= 500; } diff --git a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs index 2680facdd5b5..7c6ad2617cd6 100644 --- a/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs @@ -1215,6 +1215,58 @@ public void ActivityListeners_DefaultPorts(string scheme, int? expectedPort) Assert.Equal(expectedPort, (int?)actualPort); } + [Fact] + public void ActivityListeners_PropagationDataSample_EndTagsNotAdded() + { + // When Activity.IsAllDataRequested is false (PropagationData sample result), + // end tags should NOT be added as they are only relevant when all data is requested. + var testSource = new ActivitySource(Path.GetRandomFileName()); + var hostingApplication = CreateApplication(out var features, activitySource: testSource, suppressActivityOpenTelemetryData: false, configure: c => + { + c.Request.Protocol = "HTTP/1.1"; + c.Request.Scheme = "http"; + c.Request.Method = "GET"; + c.Request.Path = "/hello"; + c.Response.StatusCode = 500; + }); + + Activity stoppedActivity = null; + using var listener = new ActivityListener + { + ShouldListenTo = activitySource => ReferenceEquals(activitySource, testSource), + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.PropagationData, + ActivityStopped = activity => stoppedActivity = activity + }; + + ActivitySource.AddActivityListener(listener); + + var context = hostingApplication.CreateContext(features); + context.HttpContext.SetEndpoint(new Endpoint( + c => Task.CompletedTask, + new EndpointMetadataCollection(new TestRouteDiagnosticsMetadata()), + "Test endpoint")); + + var exception = new InvalidOperationException("Test exception"); + hostingApplication.DisposeContext(context, exception); + + Assert.NotNull(stoppedActivity); + Assert.False(stoppedActivity.IsAllDataRequested); + + var tags = stoppedActivity.TagObjects.ToDictionary(); + + // Verify sampling tags are still present + Assert.True(tags.ContainsKey(HostingTelemetryHelpers.AttributeHttpRequestMethod)); + Assert.True(tags.ContainsKey(HostingTelemetryHelpers.AttributeUrlScheme)); + Assert.True(tags.ContainsKey(HostingTelemetryHelpers.AttributeUrlPath)); + + // Verify end tags are NOT present since IsAllDataRequested is false + Assert.False(tags.ContainsKey(HostingTelemetryHelpers.AttributeHttpResponseStatusCode)); + Assert.False(tags.ContainsKey(HostingTelemetryHelpers.AttributeNetworkProtocolVersion)); + Assert.False(tags.ContainsKey(HostingTelemetryHelpers.AttributeHttpRoute)); + Assert.False(tags.ContainsKey(HostingTelemetryHelpers.AttributeErrorType)); + Assert.Equal(ActivityStatusCode.Unset, stoppedActivity.Status); + } + [Fact] public void ActivityListeners_EndTagsAdded() { @@ -1404,11 +1456,9 @@ public void ActivityListeners_SuppressActivityTags_NoEndTagsAdded() } [Theory] - [InlineData(400)] - [InlineData(404)] - [InlineData(499)] [InlineData(500)] [InlineData(503)] + [InlineData(599)] public void ActivityListeners_ErrorStatusCodesSetErrorType(int statusCode) { var testSource = new ActivitySource(Path.GetRandomFileName()); @@ -1448,6 +1498,9 @@ public void ActivityListeners_ErrorStatusCodesSetErrorType(int statusCode) [InlineData(204)] [InlineData(301)] [InlineData(399)] + [InlineData(400)] + [InlineData(404)] + [InlineData(499)] public void ActivityListeners_SuccessStatusCodesNoErrorType(int statusCode) { var testSource = new ActivitySource(Path.GetRandomFileName()); diff --git a/src/Hosting/Hosting/test/HostingMetricsTests.cs b/src/Hosting/Hosting/test/HostingMetricsTests.cs index a3a50761d170..e1f8c0d75def 100644 --- a/src/Hosting/Hosting/test/HostingMetricsTests.cs +++ b/src/Hosting/Hosting/test/HostingMetricsTests.cs @@ -177,6 +177,90 @@ public void IHttpMetricsTagsFeatureNotUsedFromFeatureCollection() Assert.NotEqual(overridenFeature, contextFeature); } + [Theory] + [InlineData(500)] + [InlineData(503)] + [InlineData(599)] + public void RequestDuration_ServerErrorStatusCode_ErrorTypeSet(int statusCode) + { + // Arrange + var meterFactory = new TestMeterFactory(); + var hostingApplication = CreateApplication(meterFactory: meterFactory); + var httpContext = new DefaultHttpContext(); + + using var requestDurationCollector = new MetricCollector(meterFactory, HostingMetrics.MeterName, "http.server.request.duration"); + + // Act + httpContext.Request.Protocol = HttpProtocol.Http11; + var context = hostingApplication.CreateContext(httpContext.Features); + context.HttpContext.Response.StatusCode = statusCode; + hostingApplication.DisposeContext(context, null); + + // Assert + var measurements = requestDurationCollector.GetMeasurementSnapshot(); + Assert.Single(measurements); + + var measurement = measurements[0]; + Assert.Equal(statusCode, (int)measurement.Tags[HostingTelemetryHelpers.AttributeHttpResponseStatusCode]); + Assert.Equal(statusCode.ToString(System.Globalization.CultureInfo.InvariantCulture), (string)measurement.Tags[HostingTelemetryHelpers.AttributeErrorType]); + } + + [Theory] + [InlineData(200)] + [InlineData(301)] + [InlineData(400)] + [InlineData(404)] + [InlineData(499)] + public void RequestDuration_NonServerErrorStatusCode_NoErrorType(int statusCode) + { + // Arrange + var meterFactory = new TestMeterFactory(); + var hostingApplication = CreateApplication(meterFactory: meterFactory); + var httpContext = new DefaultHttpContext(); + + using var requestDurationCollector = new MetricCollector(meterFactory, HostingMetrics.MeterName, "http.server.request.duration"); + + // Act + httpContext.Request.Protocol = HttpProtocol.Http11; + var context = hostingApplication.CreateContext(httpContext.Features); + context.HttpContext.Response.StatusCode = statusCode; + hostingApplication.DisposeContext(context, null); + + // Assert + var measurements = requestDurationCollector.GetMeasurementSnapshot(); + Assert.Single(measurements); + + var measurement = measurements[0]; + Assert.Equal(statusCode, (int)measurement.Tags[HostingTelemetryHelpers.AttributeHttpResponseStatusCode]); + Assert.False(measurement.Tags.ContainsKey(HostingTelemetryHelpers.AttributeErrorType)); + } + + [Fact] + public void RequestDuration_ExceptionWithServerErrorStatusCode_ExceptionTakesPrecedence() + { + // Arrange + var meterFactory = new TestMeterFactory(); + var hostingApplication = CreateApplication(meterFactory: meterFactory); + var httpContext = new DefaultHttpContext(); + + using var requestDurationCollector = new MetricCollector(meterFactory, HostingMetrics.MeterName, "http.server.request.duration"); + + // Act + httpContext.Request.Protocol = HttpProtocol.Http11; + var context = hostingApplication.CreateContext(httpContext.Features); + context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; + hostingApplication.DisposeContext(context, new InvalidOperationException("Test error")); + + // Assert + var measurements = requestDurationCollector.GetMeasurementSnapshot(); + Assert.Single(measurements); + + var measurement = measurements[0]; + Assert.Equal(StatusCodes.Status500InternalServerError, (int)measurement.Tags[HostingTelemetryHelpers.AttributeHttpResponseStatusCode]); + // When there's an exception, it should be used as error.type, not the status code + Assert.Equal("System.InvalidOperationException", (string)measurement.Tags[HostingTelemetryHelpers.AttributeErrorType]); + } + private sealed class TestHttpMetricsTagsFeature : IHttpMetricsTagsFeature { public ICollection> Tags { get; } = new Collection>(); From 1d6f092bace3ce53c184a29c344c6b3159e4eff3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 23 Dec 2025 10:44:04 +0800 Subject: [PATCH 6/7] Update src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Hosting/src/Internal/HostingApplicationDiagnostics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 703e0c516ab8..d3af4015d4af 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -536,7 +536,7 @@ private static void SetActivityEndTags(HttpContext httpContext, Activity activit } else if (HostingTelemetryHelpers.IsErrorStatusCode(response.StatusCode)) { - activity.SetTag(HostingTelemetryHelpers.AttributeErrorType, response.StatusCode.ToString(System.Globalization.CultureInfo.InvariantCulture)); + activity.SetTag(HostingTelemetryHelpers.AttributeErrorType, response.StatusCode.ToString(CultureInfo.InvariantCulture)); activity.SetStatus(ActivityStatusCode.Error); } } From 95885cb39a3cb4f85b42fe89adf082060d378230 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 23 Dec 2025 10:45:21 +0800 Subject: [PATCH 7/7] Clean up --- src/Hosting/Hosting/src/Internal/HostingMetrics.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Hosting/Hosting/src/Internal/HostingMetrics.cs b/src/Hosting/Hosting/src/Internal/HostingMetrics.cs index f9ecf766fb60..68387b6fbe5f 100644 --- a/src/Hosting/Hosting/src/Internal/HostingMetrics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingMetrics.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.Metrics; +using System.Globalization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Shared; @@ -90,7 +91,7 @@ public void RequestEnd(string protocol, string scheme, string method, string? ro else if (HostingTelemetryHelpers.IsErrorStatusCode(statusCode)) { // Add error.type for 5xx status codes when there's no exception. - tags.TryAddTag(HostingTelemetryHelpers.AttributeErrorType, statusCode.ToString(System.Globalization.CultureInfo.InvariantCulture)); + tags.TryAddTag(HostingTelemetryHelpers.AttributeErrorType, statusCode.ToString(CultureInfo.InvariantCulture)); } var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp);