3
3
4
4
using System ;
5
5
using System . Diagnostics ;
6
+ using System . Runtime . CompilerServices ;
6
7
using System . Threading . Tasks ;
7
8
using Microsoft . AspNetCore . Hosting . Server ;
8
9
using Microsoft . AspNetCore . Http ;
@@ -13,6 +14,12 @@ namespace Microsoft.AspNetCore.Hosting.Internal
13
14
{
14
15
public class HostingApplication : IHttpApplication < HostingApplication . Context >
15
16
{
17
+ private const string DiagnosticsBeginRequestKey = "Microsoft.AspNetCore.Hosting.BeginRequest" ;
18
+ private const string DiagnosticsEndRequestKey = "Microsoft.AspNetCore.Hosting.EndRequest" ;
19
+ private const string DiagnosticsUnhandledExceptionKey = "Microsoft.AspNetCore.Hosting.UnhandledException" ;
20
+
21
+ private static readonly double TimestampToTicks = TimeSpan . TicksPerSecond / ( double ) Stopwatch . Frequency ;
22
+
16
23
private readonly RequestDelegate _application ;
17
24
private readonly ILogger _logger ;
18
25
private readonly DiagnosticSource _diagnosticSource ;
@@ -30,25 +37,41 @@ public HostingApplication(
30
37
_httpContextFactory = httpContextFactory ;
31
38
}
32
39
40
+ // Set up the request
33
41
public Context CreateContext ( IFeatureCollection contextFeatures )
34
42
{
35
43
var httpContext = _httpContextFactory . Create ( contextFeatures ) ;
36
- var diagnoticsEnabled = _diagnosticSource . IsEnabled ( "Microsoft.AspNetCore.Hosting.BeginRequest" ) ;
37
- var startTimestamp = ( diagnoticsEnabled || _logger . IsEnabled ( LogLevel . Information ) ) ? Stopwatch . GetTimestamp ( ) : 0 ;
38
44
45
+ // These enabled checks are virtual dispatch and used twice and so cache to locals
46
+ var diagnoticsEnabled = _diagnosticSource . IsEnabled ( DiagnosticsBeginRequestKey ) ;
47
+ var loggingEnabled = _logger . IsEnabled ( LogLevel . Information ) ;
48
+
49
+ if ( HostingEventSource . Log . IsEnabled ( ) )
50
+ {
51
+ // To keep the hot path short we defer logging in this function to non-inlines
52
+ RecordRequestStartEventLog ( httpContext ) ;
53
+ }
54
+
55
+ // Only make call GetTimestamp if its value will be used, i.e. of the listenters is enabled
56
+ var startTimestamp = ( diagnoticsEnabled || loggingEnabled ) ? Stopwatch . GetTimestamp ( ) : 0 ;
57
+
58
+ // Scope may be relevant for a different level of logging, so we always create it
59
+ // see: https://github.com/aspnet/Hosting/pull/944
39
60
var scope = _logger . RequestScope ( httpContext ) ;
40
- _logger . RequestStarting ( httpContext ) ;
41
- if ( diagnoticsEnabled )
61
+
62
+ if ( loggingEnabled )
42
63
{
43
- _diagnosticSource . Write ( "Microsoft.AspNetCore.Hosting.BeginRequest" , new { httpContext = httpContext , timestamp = startTimestamp } ) ;
64
+ // Non-inline
65
+ LogRequestStarting ( httpContext ) ;
44
66
}
45
67
46
- var hostingLog = HostingEventSource . Log ;
47
- if ( hostingLog . IsEnabled ( ) )
68
+ if ( diagnoticsEnabled )
48
69
{
49
- hostingLog . RequestStart ( httpContext . Request . Method , httpContext . Request . Path ) ;
70
+ // Non-inline
71
+ RecordBeginRequestDiagnostics ( httpContext , startTimestamp ) ;
50
72
}
51
73
74
+ // Create and return the request Context
52
75
return new Context
53
76
{
54
77
HttpContext = httpContext ,
@@ -57,53 +80,147 @@ public Context CreateContext(IFeatureCollection contextFeatures)
57
80
} ;
58
81
}
59
82
83
+ // Execute the request
84
+ public Task ProcessRequestAsync ( Context context )
85
+ {
86
+ return _application ( context . HttpContext ) ;
87
+ }
88
+
89
+ // Clean up the request
60
90
public void DisposeContext ( Context context , Exception exception )
61
91
{
92
+ // Local cache items resolved multiple items, in order of use so they are primed in cpu pipeline when used
93
+ var hostingEventLog = HostingEventSource . Log ;
94
+ var startTimestamp = context . StartTimestamp ;
62
95
var httpContext = context . HttpContext ;
63
- var hostingLog = HostingEventSource . Log ;
64
- if ( exception == null )
65
- {
66
- var diagnoticsEnabled = _diagnosticSource . IsEnabled ( "Microsoft.AspNetCore.Hosting.EndRequest" ) ;
67
- var currentTimestamp = ( diagnoticsEnabled || context . StartTimestamp != 0 ) ? Stopwatch . GetTimestamp ( ) : 0 ;
96
+ var eventLogEnabled = hostingEventLog . IsEnabled ( ) ;
68
97
69
- _logger . RequestFinished ( httpContext , context . StartTimestamp , currentTimestamp ) ;
98
+ // If startTimestamp is 0, don't call GetTimestamp, likely don't need the value
99
+ var currentTimestamp = ( startTimestamp != 0 ) ? Stopwatch . GetTimestamp ( ) : 0 ;
70
100
71
- if ( diagnoticsEnabled )
101
+ // To keep the hot path short we defer logging to non-inlines
102
+ if ( exception == null )
103
+ {
104
+ // No exception was thrown, request was sucessful
105
+ if ( _diagnosticSource . IsEnabled ( DiagnosticsEndRequestKey ) )
72
106
{
73
- _diagnosticSource . Write ( "Microsoft.AspNetCore.Hosting.EndRequest" , new { httpContext = httpContext , timestamp = currentTimestamp } ) ;
107
+ // Diagnostics is enabled for EndRequest, but it may not be for BeginRequest
108
+ // so call GetTimestamp if currentTimestamp is zero (from above)
109
+ RecordEndRequestDiagnostics (
110
+ httpContext ,
111
+ ( currentTimestamp != 0 ) ? currentTimestamp : Stopwatch . GetTimestamp ( ) ) ;
74
112
}
75
113
}
76
114
else
77
115
{
78
- var diagnoticsEnabled = _diagnosticSource . IsEnabled ( "Microsoft.AspNetCore.Hosting.UnhandledException" ) ;
79
- var currentTimestamp = ( diagnoticsEnabled || context . StartTimestamp != 0 ) ? Stopwatch . GetTimestamp ( ) : 0 ;
80
-
81
- _logger . RequestFinished ( httpContext , context . StartTimestamp , currentTimestamp ) ;
82
-
83
- if ( diagnoticsEnabled )
116
+ // Exception was thrown from request
117
+ if ( _diagnosticSource . IsEnabled ( DiagnosticsUnhandledExceptionKey ) )
84
118
{
85
- _diagnosticSource . Write ( "Microsoft.AspNetCore.Hosting.UnhandledException" , new { httpContext = httpContext , timestamp = currentTimestamp , exception = exception } ) ;
119
+ // Diagnostics is enabled for UnhandledException, but it may not be for BeginRequest
120
+ // so call GetTimestamp if currentTimestamp is zero (from above)
121
+ RecordUnhandledExceptionDiagnostics (
122
+ httpContext ,
123
+ ( currentTimestamp != 0 ) ? currentTimestamp : Stopwatch . GetTimestamp ( ) ,
124
+ exception ) ;
86
125
}
87
126
88
- if ( hostingLog . IsEnabled ( ) )
127
+ if ( eventLogEnabled )
89
128
{
90
- hostingLog . UnhandledException ( ) ;
129
+ // Non-inline
130
+ hostingEventLog . UnhandledException ( ) ;
91
131
}
92
132
}
93
133
94
- if ( hostingLog . IsEnabled ( ) )
134
+ // If startTimestamp was 0, then Information logging wasn't enabled at for this request (and calcuated time will be wildly wrong)
135
+ // Is used as proxy to reduce calls to virtual: _logger.IsEnabled(LogLevel.Information)
136
+ if ( startTimestamp != 0 )
95
137
{
96
- hostingLog . RequestStop ( ) ;
138
+ // Non-inline
139
+ LogRequestFinished ( httpContext , startTimestamp , currentTimestamp ) ;
97
140
}
98
141
142
+ // Logging Scope and context are finshed with
99
143
context . Scope ? . Dispose ( ) ;
100
-
101
144
_httpContextFactory . Dispose ( httpContext ) ;
145
+
146
+ if ( eventLogEnabled )
147
+ {
148
+ // Non-inline
149
+ hostingEventLog . RequestStop ( ) ;
150
+ }
102
151
}
103
152
104
- public Task ProcessRequestAsync ( Context context )
153
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
154
+ private void LogRequestStarting ( HttpContext httpContext )
105
155
{
106
- return _application ( context . HttpContext ) ;
156
+ // IsEnabled is checked in the caller, so if we are here just log
157
+ _logger . Log (
158
+ logLevel : LogLevel . Information ,
159
+ eventId : LoggerEventIds . RequestStarting ,
160
+ state : new HostingRequestStartingLog ( httpContext ) ,
161
+ exception : null ,
162
+ formatter : HostingRequestStartingLog . Callback ) ;
163
+ }
164
+
165
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
166
+ private void LogRequestFinished ( HttpContext httpContext , long startTimestamp , long currentTimestamp )
167
+ {
168
+ // IsEnabled isn't checked in the caller, startTimestamp > 0 is used as a fast proxy check
169
+ // but that may be because diagnostics are enabled, which also uses startTimestamp, so check here
170
+ if ( _logger . IsEnabled ( LogLevel . Information ) )
171
+ {
172
+ var elapsed = new TimeSpan ( ( long ) ( TimestampToTicks * ( currentTimestamp - startTimestamp ) ) ) ;
173
+
174
+ _logger . Log (
175
+ logLevel : LogLevel . Information ,
176
+ eventId : LoggerEventIds . RequestFinished ,
177
+ state : new HostingRequestFinishedLog ( httpContext , elapsed ) ,
178
+ exception : null ,
179
+ formatter : HostingRequestFinishedLog . Callback ) ;
180
+ }
181
+ }
182
+
183
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
184
+ private void RecordBeginRequestDiagnostics ( HttpContext httpContext , long startTimestamp )
185
+ {
186
+ _diagnosticSource . Write (
187
+ DiagnosticsBeginRequestKey ,
188
+ new
189
+ {
190
+ httpContext = httpContext ,
191
+ timestamp = startTimestamp
192
+ } ) ;
193
+ }
194
+
195
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
196
+ private void RecordEndRequestDiagnostics ( HttpContext httpContext , long currentTimestamp )
197
+ {
198
+ _diagnosticSource . Write (
199
+ DiagnosticsEndRequestKey ,
200
+ new
201
+ {
202
+ httpContext = httpContext ,
203
+ timestamp = currentTimestamp
204
+ } ) ;
205
+ }
206
+
207
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
208
+ private void RecordUnhandledExceptionDiagnostics ( HttpContext httpContext , long currentTimestamp , Exception exception )
209
+ {
210
+ _diagnosticSource . Write (
211
+ DiagnosticsUnhandledExceptionKey ,
212
+ new
213
+ {
214
+ httpContext = httpContext ,
215
+ timestamp = currentTimestamp ,
216
+ exception = exception
217
+ } ) ;
218
+ }
219
+
220
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
221
+ private static void RecordRequestStartEventLog ( HttpContext httpContext )
222
+ {
223
+ HostingEventSource . Log . RequestStart ( httpContext . Request . Method , httpContext . Request . Path ) ;
107
224
}
108
225
109
226
public struct Context
0 commit comments