-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Refactor DiagnosticSource logging (correlation part1) #15971
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Diagnostics; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace System.Net.Http | ||
{ | ||
/// <summary> | ||
/// DiagnosticHandler notifies DiagnosticSource subscribers about outgoing Http requests | ||
/// </summary> | ||
internal sealed class DiagnosticsHandler : DelegatingHandler | ||
{ | ||
/// <summary> | ||
/// DiagnosticHandler constructor | ||
/// </summary> | ||
/// <param name="innerHandler">Inner handler: Windows or Unix implementation of HttpMessageHandler. | ||
/// Note that DiagnosticHandler is the latest in the pipeline </param> | ||
public DiagnosticsHandler(HttpMessageHandler innerHandler) : base(innerHandler) | ||
{ | ||
} | ||
|
||
protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, | ||
CancellationToken cancellationToken) | ||
{ | ||
//do not write to diagnostic source if request is invalid or cancelled, | ||
//let inner handler decide what to do with it | ||
if (request == null || cancellationToken.IsCancellationRequested) | ||
{ | ||
return base.SendAsync(request, cancellationToken); | ||
} | ||
|
||
Guid loggingRequestId = LogHttpRequest(request); | ||
Task<HttpResponseMessage> responseTask = base.SendAsync(request, cancellationToken); | ||
LogHttpResponse(responseTask, loggingRequestId); | ||
return responseTask; | ||
} | ||
|
||
#region private | ||
|
||
private static readonly DiagnosticListener s_diagnosticListener = | ||
new DiagnosticListener(DiagnosticsHandlerLoggingStrings.DiagnosticListenerName); | ||
|
||
private static Guid LogHttpRequest(HttpRequestMessage request) | ||
{ | ||
return s_diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.RequestWriteName) ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than doing this more detailed check in both LogHttpRequest and LogHttpResponse, how about just doing it once in SendAsync and only calling LogHttpRequest and LogHttpResponse if it was set. |
||
LogHttpRequestCore(request) : | ||
Guid.Empty; | ||
} | ||
|
||
private static void LogHttpResponse(Task<HttpResponseMessage> responseTask, Guid loggingRequestId) | ||
{ | ||
if (s_diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ResponseWriteName)) | ||
{ | ||
ScheduleLogResponse(responseTask, loggingRequestId); | ||
} | ||
} | ||
|
||
private static void ScheduleLogResponse( | ||
Task<HttpResponseMessage> responseTask, Guid loggingRequestId) | ||
{ | ||
responseTask.ContinueWith( | ||
t => | ||
{ | ||
if (!t.IsCanceled) | ||
{ | ||
LogHttpResponseCore(t.IsFaulted ? null : t.Result, t.Exception, loggingRequestId); | ||
} | ||
}, | ||
TaskScheduler.Default); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A few things:
responseTask.ContinueWith((t,s) => LogHttpResponseCore(t.IsFaulted ? null : t.Result, t.Exception, (Guid)s),
loggingRequestId, CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that logging cancelled requests is a good idea. It also requires passing TaskStatus to Write event, so user's won't need to guess what happened to the request |
||
} | ||
|
||
private static Guid LogHttpRequestCore(HttpRequestMessage request) | ||
{ | ||
Guid loggingRequestId = Guid.NewGuid(); | ||
long timestamp = Stopwatch.GetTimestamp(); | ||
|
||
s_diagnosticListener.Write( | ||
DiagnosticsHandlerLoggingStrings.RequestWriteName, | ||
new | ||
{ | ||
Request = request, | ||
LoggingRequestId = loggingRequestId, | ||
Timestamp = timestamp | ||
} | ||
); | ||
|
||
return loggingRequestId; | ||
} | ||
|
||
private static void LogHttpResponseCore(HttpResponseMessage response, Exception exception, | ||
Guid loggingRequestId) | ||
{ | ||
// An empty loggingRequestId signifies that the request was not logged, so do | ||
// not attempt to log response. | ||
if (loggingRequestId != Guid.Empty) | ||
{ | ||
long timestamp = Stopwatch.GetTimestamp(); | ||
|
||
s_diagnosticListener.Write( | ||
DiagnosticsHandlerLoggingStrings.ResponseWriteName, | ||
new | ||
{ | ||
Response = response, | ||
LoggingRequestId = loggingRequestId, | ||
TimeStamp = timestamp, | ||
Exception = exception | ||
} | ||
); | ||
} | ||
} | ||
|
||
#endregion | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -143,6 +143,7 @@ public IDictionary<String, object> Properties | |
public HttpClientHandler() | ||
{ | ||
_curlHandler = new CurlHandler(); | ||
_curlHandlerPipeline = new DiagnosticsHandler(_curlHandler); | ||
} | ||
|
||
protected override void Dispose(bool disposing) | ||
|
@@ -160,14 +161,15 @@ protected override void Dispose(bool disposing) | |
|
||
protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | ||
{ | ||
return _curlHandler.SendAsync(request, cancellationToken); | ||
return _curlHandlerPipeline.SendAsync(request, cancellationToken); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to avoid all checks for whether logging is currently enabled except for one upfront, e.g. make this line something like: return s_diagnosticListener.IsEnabled() ?
_curlHandlerPipeline.SendAsync(request, cancellationToken) :
_curlHandler.SendAsync(request, cancellationToken); such that we only have the one additional static field read / bool check upfront, and then if logging wasn't enabled, we don't have to incur any of the additional costs, such as extra branches, extra function calls, extra virtual calls, etc. We then don't have to worry quite as much about what happens in the DiagnosticHandler in subsequent PRs with regards to perf overheads. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest that DiagnosticsHandler should own the logic to define if it's enabled or not at cost of one non-virtual static function |
||
} | ||
|
||
#endregion Request Execution | ||
|
||
#region Private | ||
|
||
private readonly CurlHandler _curlHandler; | ||
private readonly HttpMessageHandler _curlHandlerPipeline; | ||
|
||
#endregion Private | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these checks here? Seems like if we're really leaving it up to the implementation what to do with these, the implementation could very well choose to execute a request (e.g. ignore cancellation), in which case it seems you'd still want to do pre/post logging of whatever you can. I'd suggest just removing these checks and just dealing below with the possibility that request is null in LogHttpRequest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently HttpClient checks request in SendAsync before calling HttpMessageHandler, so request never could be null here anyway.