Skip to content

Commit 7c18d3b

Browse files
authored
Merge pull request #63362 from dotnet/merge/release/10.0-rc1-to-release/10.0
[automated] Merge branch 'release/10.0-rc1' => 'release/10.0'
2 parents 560cd83 + 76a8cb3 commit 7c18d3b

File tree

185 files changed

+5303
-798
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

185 files changed

+5303
-798
lines changed

.github/workflows/inter-branch-merge-flow.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ permissions:
1010

1111
jobs:
1212
Merge:
13-
uses: dotnet/arcade/.github/workflows/backport-base.yml@fac534d85b77789bd4daf2b4c916117f1ca381e7 # 2024-06-24
13+
uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@fac534d85b77789bd4daf2b4c916117f1ca381e7 # 2024-06-24

src/Hosting/Hosting/src/Internal/HostingApplication.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ internal sealed class HostingApplication : IHttpApplication<HostingApplication.C
1717
private readonly DefaultHttpContextFactory? _defaultHttpContextFactory;
1818
private readonly HostingApplicationDiagnostics _diagnostics;
1919

20+
// Internal for testing purposes only
21+
internal bool SuppressActivityOpenTelemetryData
22+
{
23+
get => _diagnostics.SuppressActivityOpenTelemetryData;
24+
set => _diagnostics.SuppressActivityOpenTelemetryData = value;
25+
}
26+
2027
public HostingApplication(
2128
RequestDelegate application,
2229
ILogger logger,

src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ internal sealed class HostingApplicationDiagnostics
3434
private readonly HostingMetrics _metrics;
3535
private readonly ILogger _logger;
3636

37+
// Internal for testing purposes only
38+
internal bool SuppressActivityOpenTelemetryData { get; set; }
39+
3740
public HostingApplicationDiagnostics(
3841
ILogger logger,
3942
DiagnosticListener diagnosticListener,
@@ -48,6 +51,19 @@ public HostingApplicationDiagnostics(
4851
_propagator = propagator;
4952
_eventSource = eventSource;
5053
_metrics = metrics;
54+
55+
SuppressActivityOpenTelemetryData = GetSuppressActivityOpenTelemetryData();
56+
}
57+
58+
private static bool GetSuppressActivityOpenTelemetryData()
59+
{
60+
// Default to true if the switch isn't set.
61+
if (!AppContext.TryGetSwitch("Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData", out var enabled))
62+
{
63+
return true;
64+
}
65+
66+
return enabled;
5167
}
5268

5369
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -88,9 +104,9 @@ public void BeginRequest(HttpContext httpContext, HostingApplication.Context con
88104
var diagnosticListenerActivityCreationEnabled = (diagnosticListenerEnabled && _diagnosticListener.IsEnabled(ActivityName, httpContext));
89105
var loggingEnabled = _logger.IsEnabled(LogLevel.Critical);
90106

91-
if (loggingEnabled || diagnosticListenerActivityCreationEnabled || _activitySource.HasListeners())
107+
if (ActivityCreator.IsActivityCreated(_activitySource, loggingEnabled || diagnosticListenerActivityCreationEnabled))
92108
{
93-
context.Activity = StartActivity(httpContext, loggingEnabled, diagnosticListenerActivityCreationEnabled, out var hasDiagnosticListener);
109+
context.Activity = StartActivity(httpContext, loggingEnabled || diagnosticListenerActivityCreationEnabled, out var hasDiagnosticListener);
94110
context.HasDiagnosticListener = hasDiagnosticListener;
95111

96112
if (context.Activity != null)
@@ -385,10 +401,18 @@ private void RecordRequestStartMetrics(HttpContext httpContext)
385401
}
386402

387403
[MethodImpl(MethodImplOptions.NoInlining)]
388-
private Activity? StartActivity(HttpContext httpContext, bool loggingEnabled, bool diagnosticListenerActivityCreationEnabled, out bool hasDiagnosticListener)
404+
private Activity? StartActivity(HttpContext httpContext, bool diagnosticsOrLoggingEnabled, out bool hasDiagnosticListener)
389405
{
406+
// StartActivity is only called if an Activity is already verified to be created.
407+
Debug.Assert(ActivityCreator.IsActivityCreated(_activitySource, diagnosticsOrLoggingEnabled),
408+
"Activity should only be created if diagnostics or logging is enabled.");
409+
390410
hasDiagnosticListener = false;
391411

412+
var initializeTags = !SuppressActivityOpenTelemetryData
413+
? CreateInitializeActivityTags(httpContext)
414+
: (TagList?)null;
415+
392416
var headers = httpContext.Request.Headers;
393417
var activity = ActivityCreator.CreateFromRemote(
394418
_activitySource,
@@ -402,9 +426,9 @@ private void RecordRequestStartMetrics(HttpContext httpContext)
402426
},
403427
ActivityName,
404428
ActivityKind.Server,
405-
tags: null,
429+
tags: initializeTags,
406430
links: null,
407-
loggingEnabled || diagnosticListenerActivityCreationEnabled);
431+
diagnosticsOrLoggingEnabled);
408432
if (activity is null)
409433
{
410434
return null;
@@ -425,6 +449,47 @@ private void RecordRequestStartMetrics(HttpContext httpContext)
425449
return activity;
426450
}
427451

452+
private static TagList CreateInitializeActivityTags(HttpContext httpContext)
453+
{
454+
// The tags here are set when the activity is created. They can be used in sampling decisions.
455+
// Most values in semantic conventions that are present at creation are specified:
456+
// https://github.com/open-telemetry/semantic-conventions/blob/27735ccca3746d7bb7fa061dfb73d93bcbae2b6e/docs/http/http-spans.md#L581-L592
457+
// Missing values recommended by the spec are:
458+
// - url.query (need configuration around redaction to do properly)
459+
// - http.request.header.<key>
460+
461+
var request = httpContext.Request;
462+
var creationTags = new TagList();
463+
464+
if (request.Host.HasValue)
465+
{
466+
creationTags.Add(HostingTelemetryHelpers.AttributeServerAddress, request.Host.Host);
467+
468+
if (HostingTelemetryHelpers.TryGetServerPort(request.Host, request.Scheme, out var port))
469+
{
470+
creationTags.Add(HostingTelemetryHelpers.AttributeServerPort, port);
471+
}
472+
}
473+
474+
HostingTelemetryHelpers.SetActivityHttpMethodTags(ref creationTags, request.Method);
475+
476+
if (request.Headers.TryGetValue("User-Agent", out var values))
477+
{
478+
var userAgent = values.Count > 0 ? values[0] : null;
479+
if (!string.IsNullOrEmpty(userAgent))
480+
{
481+
creationTags.Add(HostingTelemetryHelpers.AttributeUserAgentOriginal, userAgent);
482+
}
483+
}
484+
485+
creationTags.Add(HostingTelemetryHelpers.AttributeUrlScheme, request.Scheme);
486+
487+
var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/";
488+
creationTags.Add(HostingTelemetryHelpers.AttributeUrlPath, path);
489+
490+
return creationTags;
491+
}
492+
428493
[MethodImpl(MethodImplOptions.NoInlining)]
429494
private void StopActivity(HttpContext httpContext, Activity activity, bool hasDiagnosticListener)
430495
{
Lines changed: 5 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections.Frozen;
54
using System.Diagnostics;
6-
using System.Diagnostics.CodeAnalysis;
75
using System.Diagnostics.Metrics;
86
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Shared;
98

109
namespace Microsoft.AspNetCore.Hosting;
1110

@@ -55,7 +54,7 @@ public void RequestEnd(string protocol, string scheme, string method, string? ro
5554

5655
if (!disableHttpRequestDurationMetric && _requestDuration.Enabled)
5756
{
58-
if (TryGetHttpVersion(protocol, out var httpVersion))
57+
if (HostingTelemetryHelpers.TryGetHttpVersion(protocol, out var httpVersion))
5958
{
6059
tags.Add("network.protocol.version", httpVersion);
6160
}
@@ -65,10 +64,10 @@ public void RequestEnd(string protocol, string scheme, string method, string? ro
6564
}
6665

6766
// Add information gathered during request.
68-
tags.Add("http.response.status_code", GetBoxedStatusCode(statusCode));
67+
tags.Add("http.response.status_code", HostingTelemetryHelpers.GetBoxedStatusCode(statusCode));
6968
if (route != null)
7069
{
71-
tags.Add("http.route", route);
70+
tags.Add("http.route", RouteDiagnosticsHelpers.ResolveHttpRoute(route));
7271
}
7372

7473
// Add before some built in tags so custom tags are prioritized when dealing with duplicates.
@@ -104,73 +103,6 @@ public void Dispose()
104103
private static void InitializeRequestTags(ref TagList tags, string scheme, string method)
105104
{
106105
tags.Add("url.scheme", scheme);
107-
tags.Add("http.request.method", ResolveHttpMethod(method));
108-
}
109-
110-
private static readonly object[] BoxedStatusCodes = new object[512];
111-
112-
private static object GetBoxedStatusCode(int statusCode)
113-
{
114-
object[] boxes = BoxedStatusCodes;
115-
return (uint)statusCode < (uint)boxes.Length
116-
? boxes[statusCode] ??= statusCode
117-
: statusCode;
118-
}
119-
120-
private static readonly FrozenDictionary<string, string> KnownMethods = FrozenDictionary.ToFrozenDictionary(new[]
121-
{
122-
KeyValuePair.Create(HttpMethods.Connect, HttpMethods.Connect),
123-
KeyValuePair.Create(HttpMethods.Delete, HttpMethods.Delete),
124-
KeyValuePair.Create(HttpMethods.Get, HttpMethods.Get),
125-
KeyValuePair.Create(HttpMethods.Head, HttpMethods.Head),
126-
KeyValuePair.Create(HttpMethods.Options, HttpMethods.Options),
127-
KeyValuePair.Create(HttpMethods.Patch, HttpMethods.Patch),
128-
KeyValuePair.Create(HttpMethods.Post, HttpMethods.Post),
129-
KeyValuePair.Create(HttpMethods.Put, HttpMethods.Put),
130-
KeyValuePair.Create(HttpMethods.Trace, HttpMethods.Trace)
131-
}, StringComparer.OrdinalIgnoreCase);
132-
133-
private static string ResolveHttpMethod(string method)
134-
{
135-
// TODO: Support configuration for configuring known methods
136-
if (KnownMethods.TryGetValue(method, out var result))
137-
{
138-
// KnownMethods ignores case. Use the value returned by the dictionary to have a consistent case.
139-
return result;
140-
}
141-
return "_OTHER";
142-
}
143-
144-
private static bool TryGetHttpVersion(string protocol, [NotNullWhen(true)] out string? version)
145-
{
146-
if (HttpProtocol.IsHttp11(protocol))
147-
{
148-
version = "1.1";
149-
return true;
150-
}
151-
if (HttpProtocol.IsHttp2(protocol))
152-
{
153-
// HTTP/2 only has one version.
154-
version = "2";
155-
return true;
156-
}
157-
if (HttpProtocol.IsHttp3(protocol))
158-
{
159-
// HTTP/3 only has one version.
160-
version = "3";
161-
return true;
162-
}
163-
if (HttpProtocol.IsHttp10(protocol))
164-
{
165-
version = "1.0";
166-
return true;
167-
}
168-
if (HttpProtocol.IsHttp09(protocol))
169-
{
170-
version = "0.9";
171-
return true;
172-
}
173-
version = null;
174-
return false;
106+
tags.Add("http.request.method", HostingTelemetryHelpers.GetNormalizedHttpMethod(method));
175107
}
176108
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Frozen;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
7+
using Microsoft.AspNetCore.Http;
8+
9+
namespace Microsoft.AspNetCore.Hosting;
10+
11+
internal static class HostingTelemetryHelpers
12+
{
13+
// Semantic Conventions for HTTP.
14+
// Note: Not all telemetry code is using these const attribute names yet.
15+
public const string AttributeHttpRequestMethod = "http.request.method";
16+
public const string AttributeHttpRequestMethodOriginal = "http.request.method_original";
17+
public const string AttributeUrlScheme = "url.scheme";
18+
public const string AttributeUrlPath = "url.path";
19+
public const string AttributeServerAddress = "server.address";
20+
public const string AttributeServerPort = "server.port";
21+
public const string AttributeUserAgentOriginal = "user_agent.original";
22+
23+
// The value "_OTHER" is used for non-standard HTTP methods.
24+
// https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes
25+
private const string OtherHttpMethod = "_OTHER";
26+
27+
private static readonly object[] BoxedStatusCodes = new object[512];
28+
29+
private static readonly FrozenDictionary<string, string> KnownHttpMethods = FrozenDictionary.ToFrozenDictionary([
30+
KeyValuePair.Create(HttpMethods.Connect, HttpMethods.Connect),
31+
KeyValuePair.Create(HttpMethods.Delete, HttpMethods.Delete),
32+
KeyValuePair.Create(HttpMethods.Get, HttpMethods.Get),
33+
KeyValuePair.Create(HttpMethods.Head, HttpMethods.Head),
34+
KeyValuePair.Create(HttpMethods.Options, HttpMethods.Options),
35+
KeyValuePair.Create(HttpMethods.Patch, HttpMethods.Patch),
36+
KeyValuePair.Create(HttpMethods.Post, HttpMethods.Post),
37+
KeyValuePair.Create(HttpMethods.Put, HttpMethods.Put),
38+
KeyValuePair.Create(HttpMethods.Trace, HttpMethods.Trace)
39+
], StringComparer.OrdinalIgnoreCase);
40+
41+
// Boxed port values for HTTP and HTTPS.
42+
private static readonly object HttpPort = 80;
43+
private static readonly object HttpsPort = 443;
44+
45+
public static bool TryGetServerPort(HostString host, string scheme, [NotNullWhen(true)] out object? port)
46+
{
47+
if (host.Port.HasValue)
48+
{
49+
port = host.Port.Value;
50+
return true;
51+
}
52+
53+
// If the port is not specified, use the default port for the scheme.
54+
if (string.Equals(scheme, "http", StringComparison.OrdinalIgnoreCase))
55+
{
56+
port = HttpPort;
57+
return true;
58+
}
59+
else if (string.Equals(scheme, "https", StringComparison.OrdinalIgnoreCase))
60+
{
61+
port = HttpsPort;
62+
return true;
63+
}
64+
65+
// Unknown scheme, no default port.
66+
port = null;
67+
return false;
68+
}
69+
70+
public static object GetBoxedStatusCode(int statusCode)
71+
{
72+
object[] boxes = BoxedStatusCodes;
73+
return (uint)statusCode < (uint)boxes.Length
74+
? boxes[statusCode] ??= statusCode
75+
: statusCode;
76+
}
77+
78+
public static string GetNormalizedHttpMethod(string method)
79+
{
80+
// TODO: Support configuration for configuring known methods
81+
if (method != null && KnownHttpMethods.TryGetValue(method, out var result))
82+
{
83+
// KnownHttpMethods ignores case. Use the value returned by the dictionary to have a consistent case.
84+
return result;
85+
}
86+
return OtherHttpMethod;
87+
}
88+
89+
public static bool TryGetHttpVersion(string protocol, [NotNullWhen(true)] out string? version)
90+
{
91+
if (HttpProtocol.IsHttp11(protocol))
92+
{
93+
version = "1.1";
94+
return true;
95+
}
96+
if (HttpProtocol.IsHttp2(protocol))
97+
{
98+
// HTTP/2 only has one version.
99+
version = "2";
100+
return true;
101+
}
102+
if (HttpProtocol.IsHttp3(protocol))
103+
{
104+
// HTTP/3 only has one version.
105+
version = "3";
106+
return true;
107+
}
108+
if (HttpProtocol.IsHttp10(protocol))
109+
{
110+
version = "1.0";
111+
return true;
112+
}
113+
if (HttpProtocol.IsHttp09(protocol))
114+
{
115+
version = "0.9";
116+
return true;
117+
}
118+
version = null;
119+
return false;
120+
}
121+
122+
public static void SetActivityHttpMethodTags(ref TagList tags, string originalHttpMethod)
123+
{
124+
var normalizedHttpMethod = GetNormalizedHttpMethod(originalHttpMethod);
125+
tags.Add(AttributeHttpRequestMethod, normalizedHttpMethod);
126+
127+
if (originalHttpMethod != normalizedHttpMethod)
128+
{
129+
tags.Add(AttributeHttpRequestMethodOriginal, originalHttpMethod);
130+
}
131+
}
132+
}

src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<Compile Include="$(SharedSourceRoot)Metrics\MetricsExtensions.cs" />
2020
<Compile Include="$(SharedSourceRoot)Metrics\MetricsConstants.cs" />
2121
<Compile Include="$(SharedSourceRoot)Diagnostics\ActivityCreator.cs" />
22+
<Compile Include="$(SharedSourceRoot)Diagnostics\RouteDiagnosticsHelpers.cs" />
2223
</ItemGroup>
2324

2425
<ItemGroup>

0 commit comments

Comments
 (0)