3
3
4
4
using System . Collections . Frozen ;
5
5
using System . Diagnostics ;
6
+ using System . Diagnostics . CodeAnalysis ;
6
7
using System . Diagnostics . Metrics ;
7
8
using Microsoft . AspNetCore . Http ;
8
9
@@ -13,26 +14,22 @@ internal sealed class HostingMetrics : IDisposable
13
14
public const string MeterName = "Microsoft.AspNetCore.Hosting" ;
14
15
15
16
private readonly Meter _meter ;
16
- private readonly UpDownCounter < long > _currentRequestsCounter ;
17
+ private readonly UpDownCounter < long > _activeRequestsCounter ;
17
18
private readonly Histogram < double > _requestDuration ;
18
- private readonly Counter < long > _unhandledRequestsCounter ;
19
19
20
20
public HostingMetrics ( IMeterFactory meterFactory )
21
21
{
22
22
_meter = meterFactory . Create ( MeterName ) ;
23
23
24
- _currentRequestsCounter = _meter . CreateUpDownCounter < long > (
25
- "http-server-current-requests" ,
24
+ _activeRequestsCounter = _meter . CreateUpDownCounter < long > (
25
+ "http.server.active_requests" ,
26
+ unit : "{request}" ,
26
27
description : "Number of HTTP requests that are currently active on the server." ) ;
27
28
28
29
_requestDuration = _meter . CreateHistogram < double > (
29
- "http- server- request- duration" ,
30
+ "http. server. request. duration" ,
30
31
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." ) ;
36
33
}
37
34
38
35
// Note: Calling code checks whether counter is enabled.
@@ -41,35 +38,43 @@ public void RequestStart(bool isHttps, string scheme, string method, HostString
41
38
// Tags must match request end.
42
39
var tags = new TagList ( ) ;
43
40
InitializeRequestTags ( ref tags , isHttps , scheme , method , host ) ;
44
- _currentRequestsCounter . Add ( 1 , tags ) ;
41
+ _activeRequestsCounter . Add ( 1 , tags ) ;
45
42
}
46
43
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 )
48
45
{
49
46
var tags = new TagList ( ) ;
50
47
InitializeRequestTags ( ref tags , isHttps , scheme , method , host ) ;
51
48
52
49
// Tags must match request start.
53
- if ( _currentRequestsCounter . Enabled )
50
+ if ( _activeRequestsCounter . Enabled )
54
51
{
55
- _currentRequestsCounter . Add ( - 1 , tags ) ;
52
+ _activeRequestsCounter . Add ( - 1 , tags ) ;
56
53
}
57
54
58
55
if ( _requestDuration . Enabled )
59
56
{
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
+ }
61
66
62
67
// Add information gathered during request.
63
- tags . Add ( "status-code " , GetBoxedStatusCode ( statusCode ) ) ;
68
+ tags . Add ( "http.response.status_code " , GetBoxedStatusCode ( statusCode ) ) ;
64
69
if ( route != null )
65
70
{
66
- tags . Add ( "route" , route ) ;
71
+ tags . Add ( "http. route" , route ) ;
67
72
}
68
73
// 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.
70
75
if ( exception != null )
71
76
{
72
- tags . Add ( "exception-name " , exception . GetType ( ) . FullName ) ;
77
+ tags . Add ( "exception.type " , exception . GetType ( ) . FullName ) ;
73
78
}
74
79
if ( customTags != null )
75
80
{
@@ -84,36 +89,37 @@ public void RequestEnd(string protocol, bool isHttps, string scheme, string meth
84
89
}
85
90
}
86
91
87
- public void UnhandledRequest ( )
88
- {
89
- _unhandledRequestsCounter . Add ( 1 ) ;
90
- }
91
-
92
92
public void Dispose ( )
93
93
{
94
94
_meter . Dispose ( ) ;
95
95
}
96
96
97
- public bool IsEnabled ( ) => _currentRequestsCounter . Enabled || _requestDuration . Enabled || _unhandledRequestsCounter . Enabled ;
97
+ public bool IsEnabled ( ) => _activeRequestsCounter . Enabled || _requestDuration . Enabled ;
98
98
99
99
private static void InitializeRequestTags ( ref TagList tags , bool isHttps , string scheme , string method , HostString host )
100
100
{
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
+ /*
103
108
if (host.HasValue)
104
109
{
105
- tags . Add ( "host " , host . Host ) ;
110
+ tags.Add("server.address ", host.Host);
106
111
107
112
// Port is parsed each time it's accessed. Store part in local variable.
108
113
if (host.Port is { } port)
109
114
{
110
115
// Add port tag when not the default value for the current scheme
111
116
if ((isHttps && port != 443) || (!isHttps && port != 80))
112
117
{
113
- tags . Add ( "port" , port ) ;
118
+ tags.Add("server. port", port);
114
119
}
115
120
}
116
121
}
122
+ */
117
123
}
118
124
119
125
// 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)
197
203
198
204
return statusCode ;
199
205
}
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
+ }
200
263
}
0 commit comments