< Summary

Information
Class: System.Net.Http.DiagnosticsHandler
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\DiagnosticsHandler.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 230
Coverable lines: 230
Total lines: 383
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 82
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
.ctor(...)0%14140%
IsEnabled()0%440%
StartActivity(...)0%880%
SendAsync(...)0%440%
SendAsyncCore()0%44440%
Dispose(...)0%220%
.ctor(...)100%110%
ToString()100%110%
.ctor(...)100%110%
ToString()100%110%
.ctor(...)100%110%
ToString()100%110%
.ctor(...)100%110%
ToString()100%110%
.ctor(...)100%110%
ToString()100%110%
InjectHeaders(...)0%660%
Write(...)100%110%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\DiagnosticsHandler.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Net.Http.Headers;
 8using System.Threading;
 9using System.Threading.Tasks;
 10
 11namespace System.Net.Http
 12{
 13    /// <summary>
 14    /// DiagnosticHandler notifies DiagnosticSource subscribers about outgoing Http requests
 15    /// </summary>
 16    internal sealed class DiagnosticsHandler : HttpMessageHandlerStage
 17    {
 018        private static readonly DiagnosticListener s_diagnosticListener = new DiagnosticListener(DiagnosticsHandlerLoggi
 019        internal static readonly ActivitySource s_activitySource = new ActivitySource(DiagnosticsHandlerLoggingStrings.R
 20
 21        private readonly HttpMessageHandler _innerHandler;
 22        private readonly DistributedContextPropagator _propagator;
 23        private readonly HeaderDescriptor[]? _propagatorFields;
 24        private readonly IWebProxy? _proxy;
 25
 026        public DiagnosticsHandler(HttpMessageHandler innerHandler, DistributedContextPropagator propagator, IWebProxy? p
 027        {
 028            Debug.Assert(GlobalHttpSettings.DiagnosticsHandler.EnableActivityPropagation);
 029            Debug.Assert(innerHandler is not null && propagator is not null);
 30
 031            _innerHandler = innerHandler;
 032            _propagator = propagator;
 033            _proxy = proxy;
 34
 35            // Prepare HeaderDescriptors for fields we need to clear when following redirects
 036            if (autoRedirect && _propagator.Fields is IReadOnlyCollection<string> fields && fields.Count > 0)
 037            {
 038                var fieldDescriptors = new List<HeaderDescriptor>(fields.Count);
 039                foreach (string field in fields)
 040                {
 041                    if (field is not null && HeaderDescriptor.TryGet(field, out HeaderDescriptor descriptor))
 042                    {
 043                        fieldDescriptors.Add(descriptor);
 044                    }
 045                }
 046                _propagatorFields = fieldDescriptors.ToArray();
 047            }
 048        }
 49
 50        private static bool IsEnabled()
 051        {
 52            // check if there is a parent Activity or if someone listens to "System.Net.Http" ActivitySource or "HttpHan
 053            return Activity.Current != null ||
 054                   s_activitySource.HasListeners() ||
 055                   s_diagnosticListener.IsEnabled();
 056        }
 57
 58        private static Activity? StartActivity(HttpRequestMessage request)
 059        {
 060            Activity? activity = null;
 061            if (s_activitySource.HasListeners())
 062            {
 063                activity = s_activitySource.StartActivity(DiagnosticsHandlerLoggingStrings.RequestActivityName, Activity
 064            }
 65
 066            if (activity is null &&
 067                (Activity.Current is not null ||
 068                s_diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.RequestActivityName, request)))
 069            {
 070                activity = new Activity(DiagnosticsHandlerLoggingStrings.RequestActivityName).Start();
 071            }
 72
 073            return activity;
 074        }
 75
 76        internal override ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationT
 077        {
 078            if (IsEnabled())
 079            {
 080                return SendAsyncCore(request, async, cancellationToken);
 81            }
 82            else
 083            {
 084                return async ?
 085                    new ValueTask<HttpResponseMessage>(_innerHandler.SendAsync(request, cancellationToken)) :
 086                    new ValueTask<HttpResponseMessage>(_innerHandler.Send(request, cancellationToken));
 87            }
 088        }
 89
 90        private async ValueTask<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request, bool async, CancellationT
 091        {
 92            // HttpClientHandler is responsible to call static GlobalHttpSettings.DiagnosticsHandler.IsEnabled before fo
 93            // It will check if propagation is on (because parent Activity exists or there is a listener) or off (forcib
 94            // This code won't be called unless consumer unsubscribes from DiagnosticListener right after the check.
 95            // So some requests happening right after subscription starts might not be instrumented. Similarly,
 96            // when consumer unsubscribes, extra requests might be instrumented
 97
 98            // Since we are reusing the request message instance on redirects, clear any existing headers
 99            // Do so before writing DiagnosticListener events as instrumentations use those to inject headers
 0100            if (request.WasPropagatorStateInjectedByDiagnosticsHandler() && _propagatorFields is HeaderDescriptor[] fiel
 0101            {
 0102                foreach (HeaderDescriptor field in fields)
 0103                {
 0104                    request.Headers.Remove(field);
 0105                }
 0106            }
 107
 0108            DiagnosticListener diagnosticListener = s_diagnosticListener;
 109
 0110            Guid loggingRequestId = Guid.Empty;
 0111            Activity? activity = StartActivity(request);
 112
 0113            if (activity is not null)
 0114            {
 115                // https://github.com/open-telemetry/semantic-conventions/blob/release/v1.23.x/docs/http/http-spans.md#n
 0116                activity.DisplayName = HttpMethod.GetKnownMethod(request.Method.Method)?.Method ?? "HTTP";
 117
 0118                if (activity.IsAllDataRequested)
 0119                {
 120                    // Add standard tags known before sending the request.
 0121                    KeyValuePair<string, object?> methodTag = DiagnosticsHelper.GetMethodTag(request.Method, out bool is
 0122                    activity.SetTag(methodTag.Key, methodTag.Value);
 0123                    if (isUnknownMethod)
 0124                    {
 0125                        activity.SetTag("http.request.method_original", request.Method.Method);
 0126                    }
 127
 0128                    if (request.RequestUri is Uri requestUri && requestUri.IsAbsoluteUri)
 0129                    {
 0130                        activity.SetTag("server.address", DiagnosticsHelper.GetServerAddress(request, _proxy));
 0131                        activity.SetTag("server.port", requestUri.Port);
 0132                        activity.SetTag("url.full", UriRedactionHelper.GetRedactedUriString(requestUri));
 0133                    }
 0134                }
 135
 136                // Only send start event to users who subscribed for it.
 0137                if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.RequestActivityStartName))
 0138                {
 0139                    Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.RequestActivityStartName, new ActivitySta
 0140                }
 0141            }
 142
 143            // Try to write System.Net.Http.Request event (deprecated)
 0144            if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.RequestWriteNameDeprecated))
 0145            {
 0146                long timestamp = Stopwatch.GetTimestamp();
 0147                loggingRequestId = Guid.NewGuid();
 0148                Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.RequestWriteNameDeprecated,
 0149                    new RequestData(
 0150                        request,
 0151                        loggingRequestId,
 0152                        timestamp));
 0153            }
 154
 0155            if (activity is not null)
 0156            {
 0157                InjectHeaders(activity, request);
 0158            }
 159
 0160            HttpResponseMessage? response = null;
 0161            Exception? exception = null;
 0162            TaskStatus taskStatus = TaskStatus.RanToCompletion;
 163            try
 0164            {
 0165                response = async ?
 0166                    await _innerHandler.SendAsync(request, cancellationToken).ConfigureAwait(false) :
 0167                    _innerHandler.Send(request, cancellationToken);
 0168                return response;
 169            }
 0170            catch (OperationCanceledException ex)
 0171            {
 0172                taskStatus = TaskStatus.Canceled;
 0173                exception = ex;
 174
 175                // we'll report task status in HttpRequestOut.Stop
 0176                throw;
 177            }
 0178            catch (Exception ex)
 0179            {
 0180                taskStatus = TaskStatus.Faulted;
 0181                exception = ex;
 182
 0183                if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ExceptionEventName))
 0184                {
 185                    // If request was initially instrumented, Activity.Current has all necessary context for logging
 186                    // Request is passed to provide some context if instrumentation was disabled and to avoid
 187                    // extensive Activity.Tags usage to tunnel request properties
 0188                    Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.ExceptionEventName, new ExceptionData(ex,
 0189                }
 0190                throw;
 191            }
 192            finally
 0193            {
 194                // Always stop activity if it was started.
 0195                if (activity is not null)
 0196                {
 0197                    activity.SetEndTime(DateTime.UtcNow);
 198
 0199                    if (activity.IsAllDataRequested)
 0200                    {
 201                        // Add standard tags known at request completion.
 0202                        if (response is not null)
 0203                        {
 0204                            activity.SetTag("http.response.status_code", DiagnosticsHelper.GetBoxedInt32((int)response.S
 0205                            activity.SetTag("network.protocol.version", DiagnosticsHelper.GetProtocolVersionString(respo
 0206                        }
 207
 0208                        if (DiagnosticsHelper.TryGetErrorType(response, exception, out string? errorType))
 0209                        {
 0210                            activity.SetTag("error.type", errorType);
 211
 212                            // The presence of error.type indicates that the conditions for setting Error status are als
 213                            // https://github.com/open-telemetry/semantic-conventions/blob/v1.34.0/docs/http/http-spans.
 0214                            activity.SetStatus(ActivityStatusCode.Error);
 215
 0216                            if (exception is not null)
 0217                            {
 218                                // Records the exception as per https://github.com/open-telemetry/opentelemetry-specific
 219                                // Add the exception event with a timestamp matching the activity's end time
 220                                // to ensure it falls within the activity's duration.
 0221                                activity.AddException(exception, timestamp: activity.StartTimeUtc + activity.Duration);
 0222                            }
 0223                        }
 0224                    }
 225
 226                    // Only send stop event to users who subscribed for it.
 0227                    if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.RequestActivityStopName))
 0228                    {
 0229                        Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.RequestActivityStopName, new Activity
 0230                    }
 231
 0232                    activity.Stop();
 0233                }
 234
 235                // Try to write System.Net.Http.Response event (deprecated)
 0236                if (diagnosticListener.IsEnabled(DiagnosticsHandlerLoggingStrings.ResponseWriteNameDeprecated))
 0237                {
 0238                    long timestamp = Stopwatch.GetTimestamp();
 0239                    Write(diagnosticListener, DiagnosticsHandlerLoggingStrings.ResponseWriteNameDeprecated,
 0240                        new ResponseData(
 0241                            response,
 0242                            loggingRequestId,
 0243                            timestamp,
 0244                            taskStatus));
 0245                }
 0246            }
 0247        }
 248
 249        protected override void Dispose(bool disposing)
 0250        {
 0251            if (disposing)
 0252            {
 0253                _innerHandler.Dispose();
 0254            }
 255
 0256            base.Dispose(disposing);
 0257        }
 258
 259        #region private
 260
 261        private sealed class ActivityStartData
 262        {
 263            // matches the properties selected in https://github.com/dotnet/diagnostics/blob/ffd0254da3bcc47847b1183fa54
 264            [DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
 265            [DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))]
 266            [DynamicDependency(nameof(Uri.Host), typeof(Uri))]
 267            [DynamicDependency(nameof(Uri.Port), typeof(Uri))]
 0268            internal ActivityStartData(HttpRequestMessage request)
 0269            {
 0270                Request = request;
 0271            }
 272
 0273            public HttpRequestMessage Request { get; }
 274
 0275            public override string ToString() => $"{{ {nameof(Request)} = {Request} }}";
 276        }
 277
 278        private sealed class ActivityStopData
 279        {
 0280            internal ActivityStopData(HttpResponseMessage? response, HttpRequestMessage request, TaskStatus requestTaskS
 0281            {
 0282                Response = response;
 0283                Request = request;
 0284                RequestTaskStatus = requestTaskStatus;
 0285            }
 286
 0287            public HttpResponseMessage? Response { get; }
 0288            public HttpRequestMessage Request { get; }
 0289            public TaskStatus RequestTaskStatus { get; }
 290
 0291            public override string ToString() => $"{{ {nameof(Response)} = {Response}, {nameof(Request)} = {Request}, {n
 292        }
 293
 294        private sealed class ExceptionData
 295        {
 296            // preserve the same properties as ActivityStartData above + common Exception properties
 297            [DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
 298            [DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))]
 299            [DynamicDependency(nameof(Uri.Host), typeof(Uri))]
 300            [DynamicDependency(nameof(Uri.Port), typeof(Uri))]
 301            [DynamicDependency(nameof(System.Exception.Message), typeof(Exception))]
 302            [DynamicDependency(nameof(System.Exception.StackTrace), typeof(Exception))]
 0303            internal ExceptionData(Exception exception, HttpRequestMessage request)
 0304            {
 0305                Exception = exception;
 0306                Request = request;
 0307            }
 308
 0309            public Exception Exception { get; }
 0310            public HttpRequestMessage Request { get; }
 311
 0312            public override string ToString() => $"{{ {nameof(Exception)} = {Exception}, {nameof(Request)} = {Request} }
 313        }
 314
 315        private sealed class RequestData
 316        {
 317            // preserve the same properties as ActivityStartData above
 318            [DynamicDependency(nameof(HttpRequestMessage.RequestUri), typeof(HttpRequestMessage))]
 319            [DynamicDependency(nameof(HttpRequestMessage.Method), typeof(HttpRequestMessage))]
 320            [DynamicDependency(nameof(Uri.Host), typeof(Uri))]
 321            [DynamicDependency(nameof(Uri.Port), typeof(Uri))]
 0322            internal RequestData(HttpRequestMessage request, Guid loggingRequestId, long timestamp)
 0323            {
 0324                Request = request;
 0325                LoggingRequestId = loggingRequestId;
 0326                Timestamp = timestamp;
 0327            }
 328
 0329            public HttpRequestMessage Request { get; }
 0330            public Guid LoggingRequestId { get; }
 0331            public long Timestamp { get; }
 332
 0333            public override string ToString() => $"{{ {nameof(Request)} = {Request}, {nameof(LoggingRequestId)} = {Loggi
 334        }
 335
 336        private sealed class ResponseData
 337        {
 338            [DynamicDependency(nameof(HttpResponseMessage.StatusCode), typeof(HttpResponseMessage))]
 0339            internal ResponseData(HttpResponseMessage? response, Guid loggingRequestId, long timestamp, TaskStatus reque
 0340            {
 0341                Response = response;
 0342                LoggingRequestId = loggingRequestId;
 0343                Timestamp = timestamp;
 0344                RequestTaskStatus = requestTaskStatus;
 0345            }
 346
 0347            public HttpResponseMessage? Response { get; }
 0348            public Guid LoggingRequestId { get; }
 0349            public long Timestamp { get; }
 0350            public TaskStatus RequestTaskStatus { get; }
 351
 0352            public override string ToString() => $"{{ {nameof(Response)} = {Response}, {nameof(LoggingRequestId)} = {Log
 353        }
 354
 355        private void InjectHeaders(Activity currentActivity, HttpRequestMessage request)
 0356        {
 0357            _propagator.Inject(currentActivity, request, static (carrier, key, value) =>
 0358            {
 0359                if (carrier is HttpRequestMessage request && key is not null)
 0360                {
 0361                    HeaderDescriptor descriptor = request.Headers.GetHeaderDescriptor(key);
 0362
 0363                    if (!request.Headers.Contains(descriptor))
 0364                    {
 0365                        request.Headers.Add(descriptor, value);
 0366                    }
 0367                }
 0368            });
 0369            request.MarkPropagatorStateInjectedByDiagnosticsHandler();
 0370        }
 371
 372        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
 373            Justification = "The values being passed into Write have the commonly used properties being preserved with D
 374        private static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
 375            DiagnosticSource diagnosticSource,
 376            string name,
 377            T value)
 0378        {
 0379            diagnosticSource.Write(name, value);
 0380        }
 381        #endregion
 382    }
 383}