| | | 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 | | |
| | | 4 | | using System.Collections.Generic; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.Diagnostics.Metrics; |
| | | 7 | | using System.Threading; |
| | | 8 | | |
| | | 9 | | namespace System.Net.Http |
| | | 10 | | { |
| | | 11 | | internal static class DiagnosticsHelper |
| | | 12 | | { |
| | | 13 | | // OTel bucket boundary recommendation for 'http.request.duration': |
| | | 14 | | // https://github.com/open-telemetry/semantic-conventions/blob/release/v1.23.x/docs/http/http-metrics.md#metric- |
| | | 15 | | // We are using the same boundaries for durations which are not expected to be longer than an HTTP request. |
| | 0 | 16 | | public static InstrumentAdvice<double> ShortHistogramAdvice { get; } = new() |
| | 0 | 17 | | { |
| | 0 | 18 | | HistogramBucketBoundaries = [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10] |
| | 0 | 19 | | }; |
| | | 20 | | |
| | | 21 | | internal static KeyValuePair<string, object?> GetMethodTag(HttpMethod method, out bool isUnknownMethod) |
| | 0 | 22 | | { |
| | | 23 | | // Return canonical names for known methods and "_OTHER" for unknown ones. |
| | 0 | 24 | | HttpMethod? known = HttpMethod.GetKnownMethod(method.Method); |
| | 0 | 25 | | isUnknownMethod = known is null; |
| | 0 | 26 | | return new KeyValuePair<string, object?>("http.request.method", isUnknownMethod ? "_OTHER" : known!.Method); |
| | 0 | 27 | | } |
| | | 28 | | |
| | 0 | 29 | | internal static string GetProtocolVersionString(Version httpVersion) => (httpVersion.Major, httpVersion.Minor) s |
| | 0 | 30 | | { |
| | 0 | 31 | | (1, 0) => "1.0", |
| | 0 | 32 | | (1, 1) => "1.1", |
| | 0 | 33 | | (2, 0) => "2", |
| | 0 | 34 | | (3, 0) => "3", |
| | 0 | 35 | | _ => httpVersion.ToString() |
| | 0 | 36 | | }; |
| | | 37 | | |
| | | 38 | | // Picks the value of the 'server.address' tag following rules specified in |
| | | 39 | | // https://github.com/open-telemetry/semantic-conventions/blob/728e5d1/docs/http/http-spans.md#http-client-span |
| | | 40 | | // When there is no proxy, we need to prioritize the contents of the Host header. |
| | | 41 | | // Note that this is a best-effort guess, e.g. we are not checking if proxy.GetProxy(uri) returns null. |
| | | 42 | | public static string GetServerAddress(HttpRequestMessage request, IWebProxy? proxy) |
| | 0 | 43 | | { |
| | 0 | 44 | | Debug.Assert(request.RequestUri is not null); |
| | 0 | 45 | | if ((proxy is null || proxy.IsBypassed(request.RequestUri)) && request.HasHeaders && request.Headers.Host is |
| | 0 | 46 | | { |
| | 0 | 47 | | return HttpUtilities.ParseHostNameFromHeader(hostHeader); |
| | | 48 | | } |
| | | 49 | | |
| | 0 | 50 | | return request.RequestUri.IdnHost; |
| | 0 | 51 | | } |
| | | 52 | | |
| | | 53 | | public static bool TryGetErrorType(HttpResponseMessage? response, Exception? exception, out string? errorType) |
| | 0 | 54 | | { |
| | 0 | 55 | | if (response is not null) |
| | 0 | 56 | | { |
| | 0 | 57 | | int statusCode = (int)response.StatusCode; |
| | | 58 | | |
| | | 59 | | // In case the status code indicates a client or a server error, return the string representation of the |
| | | 60 | | // See the paragraph Status and the definition of 'error.type' in |
| | | 61 | | // https://github.com/open-telemetry/semantic-conventions/blob/release/v1.23.x/docs/http/http-spans.md#S |
| | 0 | 62 | | if (statusCode >= 400 && statusCode <= 599) |
| | 0 | 63 | | { |
| | 0 | 64 | | errorType = GetErrorStatusCodeString(statusCode); |
| | 0 | 65 | | return true; |
| | | 66 | | } |
| | 0 | 67 | | } |
| | | 68 | | |
| | 0 | 69 | | if (exception is null) |
| | 0 | 70 | | { |
| | 0 | 71 | | errorType = null; |
| | 0 | 72 | | return false; |
| | | 73 | | } |
| | | 74 | | |
| | 0 | 75 | | Debug.Assert(Enum.GetValues<HttpRequestError>().Length == 12, "We need to extend the mapping in case new val |
| | 0 | 76 | | errorType = (exception as HttpRequestException)?.HttpRequestError switch |
| | 0 | 77 | | { |
| | 0 | 78 | | HttpRequestError.NameResolutionError => "name_resolution_error", |
| | 0 | 79 | | HttpRequestError.ConnectionError => "connection_error", |
| | 0 | 80 | | HttpRequestError.SecureConnectionError => "secure_connection_error", |
| | 0 | 81 | | HttpRequestError.HttpProtocolError => "http_protocol_error", |
| | 0 | 82 | | HttpRequestError.ExtendedConnectNotSupported => "extended_connect_not_supported", |
| | 0 | 83 | | HttpRequestError.VersionNegotiationError => "version_negotiation_error", |
| | 0 | 84 | | HttpRequestError.UserAuthenticationError => "user_authentication_error", |
| | 0 | 85 | | HttpRequestError.ProxyTunnelError => "proxy_tunnel_error", |
| | 0 | 86 | | HttpRequestError.InvalidResponse => "invalid_response", |
| | 0 | 87 | | HttpRequestError.ResponseEnded => "response_ended", |
| | 0 | 88 | | HttpRequestError.ConfigurationLimitExceeded => "configuration_limit_exceeded", |
| | 0 | 89 | | |
| | 0 | 90 | | // Fall back to the exception type name in case of HttpRequestError.Unknown or when exception is not an |
| | 0 | 91 | | _ => exception.GetType().FullName! |
| | 0 | 92 | | }; |
| | 0 | 93 | | return true; |
| | 0 | 94 | | } |
| | | 95 | | |
| | | 96 | | private static object[]? s_boxedStatusCodes; |
| | | 97 | | private static string[]? s_statusCodeStrings; |
| | | 98 | | |
| | | 99 | | #pragma warning disable CA1859 // we explicitly box here |
| | | 100 | | // Returns a pooled object if 'value' is between 0-512, |
| | | 101 | | // saving allocations for standard HTTP status codes and small port tag values. |
| | | 102 | | public static object GetBoxedInt32(int value) |
| | 0 | 103 | | { |
| | 0 | 104 | | object[] boxes = LazyInitializer.EnsureInitialized(ref s_boxedStatusCodes, static () => new object[512]); |
| | | 105 | | |
| | 0 | 106 | | return (uint)value < (uint)boxes.Length |
| | 0 | 107 | | ? boxes[value] ??= value |
| | 0 | 108 | | : value; |
| | 0 | 109 | | } |
| | | 110 | | #pragma warning restore |
| | | 111 | | |
| | | 112 | | private static string GetErrorStatusCodeString(int statusCode) |
| | 0 | 113 | | { |
| | 0 | 114 | | Debug.Assert(statusCode >= 400 && statusCode <= 599); |
| | | 115 | | |
| | 0 | 116 | | string[] strings = LazyInitializer.EnsureInitialized(ref s_statusCodeStrings, static () => new string[200]); |
| | 0 | 117 | | int index = statusCode - 400; |
| | 0 | 118 | | return (uint)index < (uint)strings.Length |
| | 0 | 119 | | ? strings[index] ??= statusCode.ToString() |
| | 0 | 120 | | : statusCode.ToString(); |
| | 0 | 121 | | } |
| | | 122 | | } |
| | | 123 | | } |