< Summary

Information
Class: OzonOrdersWeb.Observability.OutboundTransportFailureClassifier
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Observability/OutboundTransportFailureClassifier.cs
Line coverage
63%
Covered lines: 19
Uncovered lines: 11
Coverable lines: 30
Total lines: 86
Line coverage: 63.3%
Branch coverage
57%
Covered branches: 31
Total branches: 54
Branch coverage: 57.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
IsTransientTransportFailure(...)85%232080%
IsThirdPartyUnavailable(...)18.18%1832230.76%
IsThirdPartyHost(...)83.33%1212100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Observability/OutboundTransportFailureClassifier.cs

#LineLine coverage
 1using System.Net.Http;
 2using System.Net.Sockets;
 3using System.Security.Authentication;
 4
 5namespace OzonOrdersWeb.Observability;
 6
 7/// <summary>
 8/// Разделяет <see cref="HttpRequestException"/> на транзиентные сбои транспорта (сброс TCP удалённой стороной,
 9/// обрыв TLS-рукопожатия, преждевременно закрытый ответ) и всё остальное.
 10/// <para>
 11/// Только транзиентные сбои логируются уровнем Information (#1994): они возникают на стороне чужой
 12/// инфраструктуры и самопроходят. DNS-ошибки и connection refused намеренно НЕ считаются транзиентными —
 13/// это признак неверной конфигурации или лежащего сервиса (в том числе своих fid.mpsuper.ru и TecDocApi),
 14/// и такие записи должны оставаться в errors-*.log.
 15/// </para>
 16/// </summary>
 17public static class OutboundTransportFailureClassifier
 18{
 19    public static bool IsTransientTransportFailure(HttpRequestException exception)
 20    {
 1321        if (exception.HttpRequestError is HttpRequestError.SecureConnectionError or HttpRequestError.ResponseEnded)
 222            return true;
 23
 2224        for (var inner = exception.InnerException; inner != null; inner = inner.InnerException)
 25        {
 26            switch (inner)
 27            {
 28                case AuthenticationException:
 029                    return true;
 30                case SocketException socket:
 1031                    return socket.SocketErrorCode is SocketError.ConnectionReset
 1032                        or SocketError.ConnectionAborted
 1033                        or SocketError.Shutdown
 1034                        or SocketError.TimedOut;
 35                case IOException:
 36                    // IOException внутри HttpRequestException — всегда транспортный уровень
 37                    // («Unable to read data from the transport connection», «The response ended prematurely»).
 138                    return true;
 39            }
 40        }
 41
 042        return false;
 43    }
 44
 45    /// <summary>
 46    /// Сторонний сервис недоступен на сетевом уровне: DNS не разрешил имя, соединение отклонено
 47    /// или сеть/хост недостижимы (#2135, #2160, #2209, #2265). Это отказ чужой инфраструктуры —
 48    /// вызывающий код сообщает о нём с бизнес-контекстом. Свои сервисы (*.mpsuper.ru, localhost,
 49    /// IP-адреса, docker-имена без домена) сюда не попадают: для них та же ошибка — признак
 50    /// неверной конфигурации или упавшего сервиса и остаётся Error.
 51    /// </summary>
 52    public static bool IsThirdPartyUnavailable(HttpRequestException exception, Uri? requestUri)
 53    {
 954        if (!IsThirdPartyHost(requestUri))
 655            return false;
 56
 357        if (exception.HttpRequestError is HttpRequestError.NameResolutionError or HttpRequestError.ConnectionError)
 358            return true;
 59
 060        for (var inner = exception.InnerException; inner != null; inner = inner.InnerException)
 61        {
 062            if (inner is SocketException socket)
 63            {
 064                return socket.SocketErrorCode is SocketError.HostNotFound
 065                    or SocketError.TryAgain
 066                    or SocketError.NoData
 067                    or SocketError.ConnectionRefused
 068                    or SocketError.NetworkUnreachable
 069                    or SocketError.HostUnreachable;
 70            }
 71        }
 72
 073        return false;
 74    }
 75
 76    private static bool IsThirdPartyHost(Uri? requestUri)
 77    {
 978        var host = requestUri?.IdnHost;
 979        if (string.IsNullOrWhiteSpace(host) || requestUri!.IsLoopback || !host.Contains('.'))
 280            return false;
 781        if (System.Net.IPAddress.TryParse(host, out _))
 182            return false;
 683        return !host.Equals("mpsuper.ru", StringComparison.OrdinalIgnoreCase)
 684               && !host.EndsWith(".mpsuper.ru", StringComparison.OrdinalIgnoreCase);
 85    }
 86}