forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDiagnosticsHandler.cs
More file actions
334 lines (288 loc) · 14.8 KB
/
Copy pathDiagnosticsHandler.cs
File metadata and controls
334 lines (288 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Headers;
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 : HttpMessageHandlerStage
{
private static readonly DiagnosticListener s_diagnosticListener = new DiagnosticListener(DiagnosticsHandlerLoggingStrings.DiagnosticListenerName);
private static readonly ActivitySource s_activitySource = new ActivitySource(DiagnosticsHandlerLoggingStrings.Namespace);
private readonly HttpMessageHandler _innerHandler;
private readonly DistributedContextPropagator _propagator;
private readonly HeaderDescriptor[]? _propagatorFields;
public DiagnosticsHandler(HttpMessageHandler innerHandler, DistributedContextPropagator propagator, bool autoRedirect = false)
{
Debug.Assert(IsGloballyEnabled());
Debug.Assert(innerHandler is not null && propagator is not null);
_innerHandler = innerHandler;
_propagator = propagator;
// Prepare HeaderDescriptors for fields we need to clear when following redirects
if (autoRedirect && _propagator.Fields is IReadOnlyCollection<string> fields && fields.Count > 0)
{
var fieldDescriptors = new List<HeaderDescriptor>(fields.Count);
foreach (string field in fields)
{
if (field is not null && HeaderDescriptor.TryGet(field, out HeaderDescriptor descriptor))
{
fieldDescriptors.Add(descriptor);
}
}
_propagatorFields = fieldDescriptors.ToArray();
}
}
private static bool IsEnabled()
{
// check if there is a parent Activity or if someone listens to "System.Net.Http" ActivitySource or "HttpHandlerDiagnosticListener" DiagnosticListener.
return Activity.Current != null ||
s_activitySource.HasListeners() ||
s_diagnosticListener.IsEnabled();
}
private static Activity? CreateActivity(HttpRequestMessage requestMessage)
{
Activity? activity = null;
if (s_activitySource.HasListeners())
{
activity = s_activitySource.CreateActivity(DiagnosticsHandlerLoggingStrings.ActivityName, ActivityKind.Client);
}
if (activity is null)
{
if (Activity.Current is not null || s_diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityName, requestMessage))
{
activity = new Activity(DiagnosticsHandlerLoggingStrings.ActivityName);
}
}
return activity;
}
internal static bool IsGloballyEnabled() => GlobalHttpSettings.DiagnosticsHandler.EnableActivityPropagation;
internal override ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
{
if (IsEnabled())
{
ArgumentNullException.ThrowIfNull(request);
return SendAsyncCore(request, async, cancellationToken);
}
else
{
return async ?
new ValueTask<HttpResponseMessage>(_innerHandler.SendAsync(request, cancellationToken)) :
new ValueTask<HttpResponseMessage>(_innerHandler.Send(request, cancellationToken));
}
}
private async ValueTask<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
{
// HttpClientHandler is responsible to call static DiagnosticsHandler.IsEnabled() before forwarding request here.
// It will check if propagation is on (because parent Activity exists or there is a listener) or off (forcibly disabled)
// This code won't be called unless consumer unsubscribes from DiagnosticListener right after the check.
// So some requests happening right after subscription starts might not be instrumented. Similarly,
// when consumer unsubscribes, extra requests might be instrumented
// Since we are reusing the request message instance on redirects, clear any existing headers
// Do so before writing DiagnosticListener events as instrumentations use those to inject headers
if (request.WasRedirected() && _propagatorFields is HeaderDescriptor[] fields)
{
foreach (HeaderDescriptor field in fields)
{
request.Headers.Remove(field);
}
}
DiagnosticListener diagnosticListener = s_diagnosticListener;
Guid loggingRequestId = Guid.Empty;
Activity? activity = CreateActivity(request);
// Start activity anyway if it was created.
if (activity is not null)
{
activity.Start();
// Only send start event to users who subscribed for it.
if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityStartName))
{
Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.ActivityStartName, new ActivityStartData(request));
}
}
// Try to write System.Net.Http.Request event (deprecated)
if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.RequestWriteNameDeprecated))
{
long timestamp = Stopwatch.GetTimestamp();
loggingRequestId = Guid.NewGuid();
Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.RequestWriteNameDeprecated,
new RequestData(
request,
loggingRequestId,
timestamp));
}
if (activity is not null)
{
InjectHeaders(activity, request);
}
HttpResponseMessage? response = null;
TaskStatus taskStatus = TaskStatus.RanToCompletion;
try
{
response = async ?
await _innerHandler.SendAsync(request, cancellationToken).ConfigureAwait(false) :
_innerHandler.Send(request, cancellationToken);
return response;
}
catch (OperationCanceledException)
{
taskStatus = TaskStatus.Canceled;
// we'll report task status in HttpRequestOut.Stop
throw;
}
catch (Exception ex)
{
taskStatus = TaskStatus.Faulted;
if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ExceptionEventName))
{
// If request was initially instrumented, Activity.Current has all necessary context for logging
// Request is passed to provide some context if instrumentation was disabled and to avoid
// extensive Activity.Tags usage to tunnel request properties
Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.ExceptionEventName, new ExceptionData(ex, request));
}
throw;
}
finally
{
// Always stop activity if it was started.
if (activity is not null)
{
activity.SetEndTime(DateTime.UtcNow);
// Only send stop event to users who subscribed for it.
if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityStopName))
{
Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.ActivityStopName, new ActivityStopData(response, request, taskStatus));
}
activity.Stop();
}
// Try to write System.Net.Http.Response event (deprecated)
if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ResponseWriteNameDeprecated))
{
long timestamp = Stopwatch.GetTimestamp();
Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.ResponseWriteNameDeprecated,
new ResponseData(
response,
loggingRequestId,
timestamp,
taskStatus));
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_innerHandler.Dispose();
}
base.Dispose(disposing);
}
#region private
private sealed class ActivityStartData
{
// matches the properties selected in https://github.com/dotnet/diagnostics/blob/ffd0254da3bcc47847b1183fa5453c0877020abd/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/HttpRequestSourceConfiguration.cs#L36-L40
[DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
[DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))]
[DynamicDependency(nameof(Uri.Host), typeof(Uri))]
[DynamicDependency(nameof(Uri.Port), typeof(Uri))]
internal ActivityStartData(HttpRequestMessage request)
{
Request = request;
}
public HttpRequestMessage Request { get; }
public override string ToString() => $"{{ {nameof(Request)} = {Request} }}";
}
private sealed class ActivityStopData
{
internal ActivityStopData(HttpResponseMessage? response, HttpRequestMessage request, TaskStatus requestTaskStatus)
{
Response = response;
Request = request;
RequestTaskStatus = requestTaskStatus;
}
public HttpResponseMessage? Response { get; }
public HttpRequestMessage Request { get; }
public TaskStatus RequestTaskStatus { get; }
public override string ToString() => $"{{ {nameof(Response)} = {Response}, {nameof(Request)} = {Request}, {nameof(RequestTaskStatus)} = {RequestTaskStatus} }}";
}
private sealed class ExceptionData
{
// preserve the same properties as ActivityStartData above + common Exception properties
[DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
[DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))]
[DynamicDependency(nameof(Uri.Host), typeof(Uri))]
[DynamicDependency(nameof(Uri.Port), typeof(Uri))]
[DynamicDependency(nameof(System.Exception.Message), typeof(Exception))]
[DynamicDependency(nameof(System.Exception.StackTrace), typeof(Exception))]
internal ExceptionData(Exception exception, HttpRequestMessage request)
{
Exception = exception;
Request = request;
}
public Exception Exception { get; }
public HttpRequestMessage Request { get; }
public override string ToString() => $"{{ {nameof(Exception)} = {Exception}, {nameof(Request)} = {Request} }}";
}
private sealed class RequestData
{
// preserve the same properties as ActivityStartData above
[DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
[DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))]
[DynamicDependency(nameof(Uri.Host), typeof(Uri))]
[DynamicDependency(nameof(Uri.Port), typeof(Uri))]
internal RequestData(HttpRequestMessage request, Guid loggingRequestId, long timestamp)
{
Request = request;
LoggingRequestId = loggingRequestId;
Timestamp = timestamp;
}
public HttpRequestMessage Request { get; }
public Guid LoggingRequestId { get; }
public long Timestamp { get; }
public override string ToString() => $"{{ {nameof(Request)} = {Request}, {nameof(LoggingRequestId)} = {LoggingRequestId}, {nameof(Timestamp)} = {Timestamp} }}";
}
private sealed class ResponseData
{
[DynamicDependency(nameof(HttpResponseMessage.StatusCode), typeof(HttpResponseMessage))]
internal ResponseData(HttpResponseMessage? response, Guid loggingRequestId, long timestamp, TaskStatus requestTaskStatus)
{
Response = response;
LoggingRequestId = loggingRequestId;
Timestamp = timestamp;
RequestTaskStatus = requestTaskStatus;
}
public HttpResponseMessage? Response { get; }
public Guid LoggingRequestId { get; }
public long Timestamp { get; }
public TaskStatus RequestTaskStatus { get; }
public override string ToString() => $"{{ {nameof(Response)} = {Response}, {nameof(LoggingRequestId)} = {LoggingRequestId}, {nameof(Timestamp)} = {Timestamp}, {nameof(RequestTaskStatus)} = {RequestTaskStatus} }}";
}
private void InjectHeaders(Activity currentActivity, HttpRequestMessage request)
{
_propagator.Inject(currentActivity, request, static (carrier, key, value) =>
{
if (carrier is HttpRequestMessage request &&
key is not null &&
HeaderDescriptor.TryGet(key, out HeaderDescriptor descriptor) &&
!request.Headers.TryGetHeaderValue(descriptor, out _))
{
request.Headers.TryAddWithoutValidation(descriptor, value);
}
});
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = "The values being passed into Write have the commonly used properties being preserved with DynamicDependency.")]
private static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
DiagnosticSource diagnosticSource,
string name,
T value)
{
diagnosticSource.Write(name, value);
}
#endregion
}
}