File: HttpExceptionSummaryProvider.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.Diagnostics.ExceptionSummarization\Microsoft.Extensions.Diagnostics.ExceptionSummarization.csproj (Microsoft.Extensions.Diagnostics.ExceptionSummarization)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
 
namespace Microsoft.Extensions.Diagnostics.ExceptionSummarization;
 
/// <summary>
/// Http exception diagnosis for telemetry.
/// </summary>
internal sealed class HttpExceptionSummaryProvider : IExceptionSummaryProvider
{
    private const int DefaultDescriptionIndex = -1;
    private const string TaskCanceled = "TaskCanceled";
    private const string TaskTimeout = "TaskTimeout";
    private static readonly FrozenDictionary<WebExceptionStatus, int> _webExceptionStatusMap;
    private static readonly FrozenDictionary<SocketError, int> _socketErrorMap;
    private static readonly ImmutableArray<string> _descriptions;
 
    [SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline", Justification = "Can't do this since the field values are interdependent")]
    static HttpExceptionSummaryProvider()
    {
        var descriptions = new List<string>
        {
            TaskCanceled,
            TaskTimeout
        };
 
        var socketErrors = new Dictionary<SocketError, int>();
#if NET5_0_OR_GREATER
        foreach (var socketError in Enum.GetValues<SocketError>())
        {
#else
        foreach (var v in Enum.GetValues(typeof(SocketError)))
        {
            var socketError = (SocketError)v!;
#endif
            var name = socketError.ToString();
 
            socketErrors[socketError] = descriptions.Count;
            descriptions.Add(name);
        }
 
        var webStatuses = new Dictionary<WebExceptionStatus, int>();
#if NET5_0_OR_GREATER
        foreach (var status in Enum.GetValues<WebExceptionStatus>())
        {
#else
        foreach (var v in Enum.GetValues(typeof(WebExceptionStatus)))
        {
            var status = (WebExceptionStatus)v!;
#endif
            var name = status.ToString();
 
            webStatuses[status] = descriptions.Count;
            descriptions.Add(name);
        }
 
        _descriptions = descriptions.ToImmutableArray();
        _socketErrorMap = socketErrors.ToFrozenDictionary();
        _webExceptionStatusMap = webStatuses.ToFrozenDictionary();
    }
 
    public IEnumerable<Type> SupportedExceptionTypes { get; } = new[]
    {
        typeof(TaskCanceledException),
        typeof(OperationCanceledException),
        typeof(WebException),
        typeof(SocketException),
    };
 
    public IReadOnlyList<string> Descriptions => _descriptions;
 
    public int Describe(Exception exception, out string? additionalDetails)
    {
        _ = Throw.IfNull(exception);
 
        additionalDetails = null;
        switch (exception)
        {
            case OperationCanceledException ex:
            {
                return ex.CancellationToken.IsCancellationRequested ? 0 : 1;
            }
 
            case WebException ex:
            {
                if (_webExceptionStatusMap.TryGetValue(ex.Status, out var index))
                {
                    return index;
                }
 
                break;
            }
 
            case SocketException ex:
            {
                if (_socketErrorMap.TryGetValue(ex.SocketErrorCode, out var index))
                {
                    return index;
                }
 
                break;
            }
        }
 
        return DefaultDescriptionIndex;
    }
}