Skip to content

Commit ec3722f

Browse files
authored
Rename hosting, routing and error handling metrics to be consistent with OpenTelemetry (#49743)
1 parent 4fe394c commit ec3722f

12 files changed

+218
-137
lines changed

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,11 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp
148148
httpContext.Request.Host,
149149
route,
150150
httpContext.Response.StatusCode,
151+
reachedPipelineEnd,
151152
exception,
152153
customTags,
153154
startTimestamp,
154155
currentTimestamp);
155-
156-
if (reachedPipelineEnd)
157-
{
158-
_metrics.UnhandledRequest();
159-
}
160156
}
161157

162158
if (reachedPipelineEnd)

src/Hosting/Hosting/src/Internal/HostingMetrics.cs

Lines changed: 92 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Frozen;
55
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Diagnostics.Metrics;
78
using Microsoft.AspNetCore.Http;
89

@@ -13,26 +14,22 @@ internal sealed class HostingMetrics : IDisposable
1314
public const string MeterName = "Microsoft.AspNetCore.Hosting";
1415

1516
private readonly Meter _meter;
16-
private readonly UpDownCounter<long> _currentRequestsCounter;
17+
private readonly UpDownCounter<long> _activeRequestsCounter;
1718
private readonly Histogram<double> _requestDuration;
18-
private readonly Counter<long> _unhandledRequestsCounter;
1919

2020
public HostingMetrics(IMeterFactory meterFactory)
2121
{
2222
_meter = meterFactory.Create(MeterName);
2323

24-
_currentRequestsCounter = _meter.CreateUpDownCounter<long>(
25-
"http-server-current-requests",
24+
_activeRequestsCounter = _meter.CreateUpDownCounter<long>(
25+
"http.server.active_requests",
26+
unit: "{request}",
2627
description: "Number of HTTP requests that are currently active on the server.");
2728

2829
_requestDuration = _meter.CreateHistogram<double>(
29-
"http-server-request-duration",
30+
"http.server.request.duration",
3031
unit: "s",
31-
description: "The duration of HTTP requests on the server.");
32-
33-
_unhandledRequestsCounter = _meter.CreateCounter<long>(
34-
"http-server-unhandled-requests",
35-
description: "Number of HTTP requests that reached the end of the middleware pipeline without being handled by application code.");
32+
description: "Measures the duration of inbound HTTP requests.");
3633
}
3734

3835
// Note: Calling code checks whether counter is enabled.
@@ -41,35 +38,43 @@ public void RequestStart(bool isHttps, string scheme, string method, HostString
4138
// Tags must match request end.
4239
var tags = new TagList();
4340
InitializeRequestTags(ref tags, isHttps, scheme, method, host);
44-
_currentRequestsCounter.Add(1, tags);
41+
_activeRequestsCounter.Add(1, tags);
4542
}
4643

47-
public void RequestEnd(string protocol, bool isHttps, string scheme, string method, HostString host, string? route, int statusCode, Exception? exception, List<KeyValuePair<string, object?>>? customTags, long startTimestamp, long currentTimestamp)
44+
public void RequestEnd(string protocol, bool isHttps, string scheme, string method, HostString host, string? route, int statusCode, bool unhandledRequest, Exception? exception, List<KeyValuePair<string, object?>>? customTags, long startTimestamp, long currentTimestamp)
4845
{
4946
var tags = new TagList();
5047
InitializeRequestTags(ref tags, isHttps, scheme, method, host);
5148

5249
// Tags must match request start.
53-
if (_currentRequestsCounter.Enabled)
50+
if (_activeRequestsCounter.Enabled)
5451
{
55-
_currentRequestsCounter.Add(-1, tags);
52+
_activeRequestsCounter.Add(-1, tags);
5653
}
5754

5855
if (_requestDuration.Enabled)
5956
{
60-
tags.Add("protocol", protocol);
57+
tags.Add("network.protocol.name", "http");
58+
if (TryGetHttpVersion(protocol, out var httpVersion))
59+
{
60+
tags.Add("network.protocol.version", httpVersion);
61+
}
62+
if (unhandledRequest)
63+
{
64+
tags.Add("aspnetcore.request.is_unhandled", true);
65+
}
6166

6267
// Add information gathered during request.
63-
tags.Add("status-code", GetBoxedStatusCode(statusCode));
68+
tags.Add("http.response.status_code", GetBoxedStatusCode(statusCode));
6469
if (route != null)
6570
{
66-
tags.Add("route", route);
71+
tags.Add("http.route", route);
6772
}
6873
// This exception is only present if there is an unhandled exception.
69-
// An exception caught by ExceptionHandlerMiddleware and DeveloperExceptionMiddleware isn't thrown to here. Instead, those middleware add exception-name to custom tags.
74+
// An exception caught by ExceptionHandlerMiddleware and DeveloperExceptionMiddleware isn't thrown to here. Instead, those middleware add exception.type to custom tags.
7075
if (exception != null)
7176
{
72-
tags.Add("exception-name", exception.GetType().FullName);
77+
tags.Add("exception.type", exception.GetType().FullName);
7378
}
7479
if (customTags != null)
7580
{
@@ -84,36 +89,37 @@ public void RequestEnd(string protocol, bool isHttps, string scheme, string meth
8489
}
8590
}
8691

87-
public void UnhandledRequest()
88-
{
89-
_unhandledRequestsCounter.Add(1);
90-
}
91-
9292
public void Dispose()
9393
{
9494
_meter.Dispose();
9595
}
9696

97-
public bool IsEnabled() => _currentRequestsCounter.Enabled || _requestDuration.Enabled || _unhandledRequestsCounter.Enabled;
97+
public bool IsEnabled() => _activeRequestsCounter.Enabled || _requestDuration.Enabled;
9898

9999
private static void InitializeRequestTags(ref TagList tags, bool isHttps, string scheme, string method, HostString host)
100100
{
101-
tags.Add("scheme", scheme);
102-
tags.Add("method", method);
101+
tags.Add("url.scheme", scheme);
102+
tags.Add("http.request.method", ResolveHttpMethod(method));
103+
104+
_ = isHttps;
105+
_ = host;
106+
// TODO: Support configuration for enabling host header annotations
107+
/*
103108
if (host.HasValue)
104109
{
105-
tags.Add("host", host.Host);
110+
tags.Add("server.address", host.Host);
106111
107112
// Port is parsed each time it's accessed. Store part in local variable.
108113
if (host.Port is { } port)
109114
{
110115
// Add port tag when not the default value for the current scheme
111116
if ((isHttps && port != 443) || (!isHttps && port != 80))
112117
{
113-
tags.Add("port", port);
118+
tags.Add("server.port", port);
114119
}
115120
}
116121
}
122+
*/
117123
}
118124

119125
// Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
@@ -197,4 +203,61 @@ private static object GetBoxedStatusCode(int statusCode)
197203

198204
return statusCode;
199205
}
206+
207+
private static readonly FrozenDictionary<string, string> KnownMethods = FrozenDictionary.ToFrozenDictionary(new[]
208+
{
209+
KeyValuePair.Create(HttpMethods.Connect, HttpMethods.Connect),
210+
KeyValuePair.Create(HttpMethods.Delete, HttpMethods.Delete),
211+
KeyValuePair.Create(HttpMethods.Get, HttpMethods.Get),
212+
KeyValuePair.Create(HttpMethods.Head, HttpMethods.Head),
213+
KeyValuePair.Create(HttpMethods.Options, HttpMethods.Options),
214+
KeyValuePair.Create(HttpMethods.Patch, HttpMethods.Patch),
215+
KeyValuePair.Create(HttpMethods.Post, HttpMethods.Post),
216+
KeyValuePair.Create(HttpMethods.Put, HttpMethods.Put),
217+
KeyValuePair.Create(HttpMethods.Trace, HttpMethods.Trace)
218+
}, StringComparer.OrdinalIgnoreCase);
219+
220+
private static string ResolveHttpMethod(string method)
221+
{
222+
// TODO: Support configuration for configuring known methods
223+
if (KnownMethods.TryGetValue(method, out var result))
224+
{
225+
// KnownMethods ignores case. Use the value returned by the dictionary to have a consistent case.
226+
return result;
227+
}
228+
return "_OTHER";
229+
}
230+
231+
private static bool TryGetHttpVersion(string protocol, [NotNullWhen(true)] out string? version)
232+
{
233+
if (HttpProtocol.IsHttp11(protocol))
234+
{
235+
version = "1.1";
236+
return true;
237+
}
238+
if (HttpProtocol.IsHttp2(protocol))
239+
{
240+
// HTTP/2 only has one version.
241+
version = "2";
242+
return true;
243+
}
244+
if (HttpProtocol.IsHttp3(protocol))
245+
{
246+
// HTTP/3 only has one version.
247+
version = "3";
248+
return true;
249+
}
250+
if (HttpProtocol.IsHttp10(protocol))
251+
{
252+
version = "1.0";
253+
return true;
254+
}
255+
if (HttpProtocol.IsHttp09(protocol))
256+
{
257+
version = "0.9";
258+
return true;
259+
}
260+
version = null;
261+
return false;
262+
}
200263
}

src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ public async Task EventCountersAndMetricsValues()
5353
var hostingApplication1 = CreateApplication(out var features1, eventSource: hostingEventSource, meterFactory: testMeterFactory1);
5454
var hostingApplication2 = CreateApplication(out var features2, eventSource: hostingEventSource, meterFactory: testMeterFactory2);
5555

56-
using var currentRequestsRecorder1 = new MetricCollector<long>(testMeterFactory1, HostingMetrics.MeterName, "http-server-current-requests");
57-
using var currentRequestsRecorder2 = new MetricCollector<long>(testMeterFactory2, HostingMetrics.MeterName, "http-server-current-requests");
58-
using var requestDurationRecorder1 = new MetricCollector<double>(testMeterFactory1, HostingMetrics.MeterName, "http-server-request-duration");
59-
using var requestDurationRecorder2 = new MetricCollector<double>(testMeterFactory2, HostingMetrics.MeterName, "http-server-request-duration");
56+
using var activeRequestsCollector1 = new MetricCollector<long>(testMeterFactory1, HostingMetrics.MeterName, "http.server.active_requests");
57+
using var activeRequestsCollector2 = new MetricCollector<long>(testMeterFactory2, HostingMetrics.MeterName, "http.server.active_requests");
58+
using var requestDurationCollector1 = new MetricCollector<double>(testMeterFactory1, HostingMetrics.MeterName, "http.server.request.duration");
59+
using var requestDurationCollector2 = new MetricCollector<double>(testMeterFactory2, HostingMetrics.MeterName, "http.server.request.duration");
6060

6161
// Act/Assert 1
6262
var context1 = hostingApplication1.CreateContext(features1);
@@ -75,15 +75,15 @@ public async Task EventCountersAndMetricsValues()
7575
Assert.Equal(0, await currentRequestValues.FirstOrDefault(v => v == 0));
7676
Assert.Equal(0, await failedRequestValues.FirstOrDefault(v => v == 0));
7777

78-
Assert.Collection(currentRequestsRecorder1.GetMeasurementSnapshot(),
78+
Assert.Collection(activeRequestsCollector1.GetMeasurementSnapshot(),
7979
m => Assert.Equal(1, m.Value),
8080
m => Assert.Equal(-1, m.Value));
81-
Assert.Collection(currentRequestsRecorder2.GetMeasurementSnapshot(),
81+
Assert.Collection(activeRequestsCollector2.GetMeasurementSnapshot(),
8282
m => Assert.Equal(1, m.Value),
8383
m => Assert.Equal(-1, m.Value));
84-
Assert.Collection(requestDurationRecorder1.GetMeasurementSnapshot(),
84+
Assert.Collection(requestDurationCollector1.GetMeasurementSnapshot(),
8585
m => Assert.True(m.Value > 0));
86-
Assert.Collection(requestDurationRecorder2.GetMeasurementSnapshot(),
86+
Assert.Collection(requestDurationCollector2.GetMeasurementSnapshot(),
8787
m => Assert.True(m.Value > 0));
8888

8989
// Act/Assert 2
@@ -106,20 +106,20 @@ public async Task EventCountersAndMetricsValues()
106106
Assert.Equal(0, await currentRequestValues.FirstOrDefault(v => v == 0));
107107
Assert.Equal(2, await failedRequestValues.FirstOrDefault(v => v == 2));
108108

109-
Assert.Collection(currentRequestsRecorder1.GetMeasurementSnapshot(),
109+
Assert.Collection(activeRequestsCollector1.GetMeasurementSnapshot(),
110110
m => Assert.Equal(1, m.Value),
111111
m => Assert.Equal(-1, m.Value),
112112
m => Assert.Equal(1, m.Value),
113113
m => Assert.Equal(-1, m.Value));
114-
Assert.Collection(currentRequestsRecorder2.GetMeasurementSnapshot(),
114+
Assert.Collection(activeRequestsCollector2.GetMeasurementSnapshot(),
115115
m => Assert.Equal(1, m.Value),
116116
m => Assert.Equal(-1, m.Value),
117117
m => Assert.Equal(1, m.Value),
118118
m => Assert.Equal(-1, m.Value));
119-
Assert.Collection(requestDurationRecorder1.GetMeasurementSnapshot(),
119+
Assert.Collection(requestDurationCollector1.GetMeasurementSnapshot(),
120120
m => Assert.True(m.Value > 0),
121121
m => Assert.True(m.Value > 0));
122-
Assert.Collection(requestDurationRecorder2.GetMeasurementSnapshot(),
122+
Assert.Collection(requestDurationCollector2.GetMeasurementSnapshot(),
123123
m => Assert.True(m.Value > 0),
124124
m => Assert.True(m.Value > 0));
125125
}

0 commit comments

Comments
 (0)