This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5k
/
DiagnosticsHandler.cs
232 lines (208 loc) · 10.5 KB
/
DiagnosticsHandler.cs
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
// 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.Collections.Generic;
using System.Diagnostics;
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 : 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)
{
}
internal static bool IsEnabled()
{
// check if there is a parent Activity (and propagation is not suppressed)
// or if someone listens to HttpHandlerDiagnosticListener
return s_enableActivityPropagation && (Activity.Current != null || s_diagnosticListener.IsEnabled());
}
protected internal override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
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
if (request == null)
{
throw new ArgumentNullException(nameof(request), SR.net_http_handler_norequest);
}
Activity activity = null;
// if there is no listener, but propagation is enabled (with previous IsEnabled() check)
// do not write any events just start/stop Activity and propagate Ids
if (!s_diagnosticListener.IsEnabled())
{
activity = new Activity(DiagnosticsHandlerLoggingStrings.ActivityName);
activity.Start();
InjectHeaders(activity, request);
try
{
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
finally
{
activity.Stop();
}
}
Guid loggingRequestId = Guid.Empty;
// There is a listener. Check if listener wants to be notified about HttpClient Activities
if (s_diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityName, request))
{
activity = new Activity(DiagnosticsHandlerLoggingStrings.ActivityName);
// Only send start event to users who subscribed for it, but start activity anyway
if (s_diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ActivityStartName))
{
s_diagnosticListener.StartActivity(activity, new { Request = request });
}
else
{
activity.Start();
}
}
// try to write System.Net.Http.Request event (deprecated)
if (s_diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.RequestWriteNameDeprecated))
{
long timestamp = Stopwatch.GetTimestamp();
loggingRequestId = Guid.NewGuid();
s_diagnosticListener.Write(DiagnosticsHandlerLoggingStrings.RequestWriteNameDeprecated,
new
{
Request = request,
LoggingRequestId = loggingRequestId,
Timestamp = timestamp
}
);
}
// If we are on at all, we propagate current activity information
Activity currentActivity = Activity.Current;
if (currentActivity != null)
{
InjectHeaders(currentActivity, request);
}
Task<HttpResponseMessage> responseTask = null;
try
{
responseTask = base.SendAsync(request, cancellationToken);
return await responseTask.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// we'll report task status in HttpRequestOut.Stop
throw;
}
catch (Exception ex)
{
if (s_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
s_diagnosticListener.Write(DiagnosticsHandlerLoggingStrings.ExceptionEventName, new { Exception = ex, Request = request });
}
throw;
}
finally
{
// always stop activity if it was started
if (activity != null)
{
s_diagnosticListener.StopActivity(activity, new
{
Response = responseTask?.Status == TaskStatus.RanToCompletion ? responseTask.Result : null,
// If request is failed or cancelled, there is no response, therefore no information about request;
// pass the request in the payload, so consumers can have it in Stop for failed/canceled requests
// and not retain all requests in Start
Request = request,
RequestTaskStatus = responseTask?.Status ?? TaskStatus.Faulted
});
}
// Try to write System.Net.Http.Response event (deprecated)
if (s_diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ResponseWriteNameDeprecated))
{
long timestamp = Stopwatch.GetTimestamp();
s_diagnosticListener.Write(DiagnosticsHandlerLoggingStrings.ResponseWriteNameDeprecated,
new
{
Response = responseTask?.Status == TaskStatus.RanToCompletion ? responseTask.Result : null,
LoggingRequestId = loggingRequestId,
TimeStamp = timestamp,
RequestTaskStatus = responseTask?.Status ?? TaskStatus.Faulted
}
);
}
}
}
#region private
private const string EnableActivityPropagationEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_ENABLEACTIVITYPROPAGATION";
private const string EnableActivityPropagationAppCtxSettingName = "System.Net.Http.EnableActivityPropagation";
private static readonly bool s_enableActivityPropagation = GetEnableActivityPropagationValue();
private static bool GetEnableActivityPropagationValue()
{
// First check for the AppContext switch, giving it priority over the environment variable.
if (AppContext.TryGetSwitch(EnableActivityPropagationAppCtxSettingName, out bool enableActivityPropagation))
{
return enableActivityPropagation;
}
// AppContext switch wasn't used. Check the environment variable to determine which handler should be used.
string envVar = Environment.GetEnvironmentVariable(EnableActivityPropagationEnvironmentVariableSettingName);
if (envVar != null && (envVar.Equals("false", StringComparison.OrdinalIgnoreCase) || envVar.Equals("0")))
{
// Suppress Activity propagation.
return false;
}
// Defaults to enabling Activity propagation.
return true;
}
private static void InjectHeaders(Activity currentActivity, HttpRequestMessage request)
{
if (currentActivity.IdFormat == ActivityIdFormat.W3C)
{
if (!request.Headers.Contains(DiagnosticsHandlerLoggingStrings.TraceParentHeaderName))
{
request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.TraceParentHeaderName, currentActivity.Id);
if (currentActivity.TraceStateString != null)
{
request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.TraceStateHeaderName, currentActivity.TraceStateString);
}
}
}
else
{
if (!request.Headers.Contains(DiagnosticsHandlerLoggingStrings.RequestIdHeaderName))
{
request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.RequestIdHeaderName, currentActivity.Id);
}
}
// we expect baggage to be empty or contain a few items
using (IEnumerator<KeyValuePair<string, string>> e = currentActivity.Baggage.GetEnumerator())
{
if (e.MoveNext())
{
var baggage = new List<string>();
do
{
KeyValuePair<string, string> item = e.Current;
baggage.Add(new NameValueHeaderValue(WebUtility.UrlEncode(item.Key), WebUtility.UrlEncode(item.Value)).ToString());
}
while (e.MoveNext());
request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.CorrelationContextHeaderName, baggage);
}
}
}
private static readonly DiagnosticListener s_diagnosticListener =
new DiagnosticListener(DiagnosticsHandlerLoggingStrings.DiagnosticListenerName);
#endregion
}
}