Skip to content
This repository was archived by the owner on Dec 19, 2018. It is now read-only.

Commit d5ec085

Browse files
benaadamsdavidfowl
authored andcommitted
Disabled logging fast-path (#937)
1 parent 02b639d commit d5ec085

File tree

5 files changed

+316
-219
lines changed

5 files changed

+316
-219
lines changed

src/Microsoft.AspNetCore.Hosting/Internal/HostingApplication.cs

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

44
using System;
55
using System.Diagnostics;
6+
using System.Runtime.CompilerServices;
67
using System.Threading.Tasks;
78
using Microsoft.AspNetCore.Hosting.Server;
89
using Microsoft.AspNetCore.Http;
@@ -13,6 +14,12 @@ namespace Microsoft.AspNetCore.Hosting.Internal
1314
{
1415
public class HostingApplication : IHttpApplication<HostingApplication.Context>
1516
{
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+
1623
private readonly RequestDelegate _application;
1724
private readonly ILogger _logger;
1825
private readonly DiagnosticSource _diagnosticSource;
@@ -30,25 +37,41 @@ public HostingApplication(
3037
_httpContextFactory = httpContextFactory;
3138
}
3239

40+
// Set up the request
3341
public Context CreateContext(IFeatureCollection contextFeatures)
3442
{
3543
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;
3844

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
3960
var scope = _logger.RequestScope(httpContext);
40-
_logger.RequestStarting(httpContext);
41-
if (diagnoticsEnabled)
61+
62+
if (loggingEnabled)
4263
{
43-
_diagnosticSource.Write("Microsoft.AspNetCore.Hosting.BeginRequest", new { httpContext = httpContext, timestamp = startTimestamp });
64+
// Non-inline
65+
LogRequestStarting(httpContext);
4466
}
4567

46-
var hostingLog = HostingEventSource.Log;
47-
if (hostingLog.IsEnabled())
68+
if (diagnoticsEnabled)
4869
{
49-
hostingLog.RequestStart(httpContext.Request.Method, httpContext.Request.Path);
70+
// Non-inline
71+
RecordBeginRequestDiagnostics(httpContext, startTimestamp);
5072
}
5173

74+
// Create and return the request Context
5275
return new Context
5376
{
5477
HttpContext = httpContext,
@@ -57,53 +80,147 @@ public Context CreateContext(IFeatureCollection contextFeatures)
5780
};
5881
}
5982

83+
// Execute the request
84+
public Task ProcessRequestAsync(Context context)
85+
{
86+
return _application(context.HttpContext);
87+
}
88+
89+
// Clean up the request
6090
public void DisposeContext(Context context, Exception exception)
6191
{
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;
6295
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();
6897

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;
70100

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))
72106
{
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());
74112
}
75113
}
76114
else
77115
{
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))
84118
{
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);
86125
}
87126

88-
if (hostingLog.IsEnabled())
127+
if (eventLogEnabled)
89128
{
90-
hostingLog.UnhandledException();
129+
// Non-inline
130+
hostingEventLog.UnhandledException();
91131
}
92132
}
93133

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)
95137
{
96-
hostingLog.RequestStop();
138+
// Non-inline
139+
LogRequestFinished(httpContext, startTimestamp, currentTimestamp);
97140
}
98141

142+
// Logging Scope and context are finshed with
99143
context.Scope?.Dispose();
100-
101144
_httpContextFactory.Dispose(httpContext);
145+
146+
if (eventLogEnabled)
147+
{
148+
// Non-inline
149+
hostingEventLog.RequestStop();
150+
}
102151
}
103152

104-
public Task ProcessRequestAsync(Context context)
153+
[MethodImpl(MethodImplOptions.NoInlining)]
154+
private void LogRequestStarting(HttpContext httpContext)
105155
{
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);
107224
}
108225

109226
public struct Context

src/Microsoft.AspNetCore.Hosting/Internal/HostingEventSource.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System.Diagnostics.Tracing;
5-
using Microsoft.AspNetCore.Http;
5+
using System.Runtime.CompilerServices;
66

77
namespace Microsoft.AspNetCore.Hosting.Internal
88
{
@@ -38,12 +38,14 @@ public void RequestStart(string method, string path)
3838
WriteEvent(3, method, path);
3939
}
4040

41+
[MethodImpl(MethodImplOptions.NoInlining)]
4142
[Event(4, Level = EventLevel.Informational)]
4243
public void RequestStop()
4344
{
4445
WriteEvent(4);
4546
}
4647

48+
[MethodImpl(MethodImplOptions.NoInlining)]
4749
[Event(5, Level = EventLevel.Error)]
4850
public void UnhandledException()
4951
{

0 commit comments

Comments
 (0)