diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DefaultHttpDependencyMetadataResolver.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DefaultHttpDependencyMetadataResolver.cs new file mode 100644 index 00000000000..fa15cea6283 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DefaultHttpDependencyMetadataResolver.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.Http.Diagnostics; + +/// +/// Default implementation of that uses the base +/// trie-based lookup algorithm. +/// +public sealed class DefaultHttpDependencyMetadataResolver : HttpDependencyMetadataResolver +{ + /// + /// Initializes a new instance of the class. + /// + /// A collection of HTTP dependency metadata used for request resolution. + public DefaultHttpDependencyMetadataResolver(IEnumerable dependencyMetadata) + : base(dependencyMetadata) + { + } +} diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DownstreamDependencyMetadataManager.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs similarity index 84% rename from src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DownstreamDependencyMetadataManager.cs rename to src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs index 93c3e534e8e..e9e1845dd90 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DownstreamDependencyMetadataManager.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs @@ -8,11 +8,19 @@ using System.Net; using System.Net.Http; using System.Text.RegularExpressions; -using Microsoft.Extensions.Telemetry.Internal; +using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.Http.Diagnostics; -internal sealed class DownstreamDependencyMetadataManager : IDownstreamDependencyMetadataManager +/// +/// Resolves metadata for HTTP requests based on hostname, path, and method patterns. +/// +/// +/// This class provides a high-performance way to identify HTTP requests by mapping them to previously +/// configured metadata using specialized trie-based data structures. This enables efficient lookup +/// of service information, operation names, and other metadata for telemetry and policy application. +/// +public abstract class HttpDependencyMetadataResolver { internal readonly struct ProcessedMetadata { @@ -27,22 +35,35 @@ internal readonly struct ProcessedMetadata private readonly HostSuffixTrieNode _hostSuffixTrieRoot = new(); private readonly FrozenDictionary _frozenProcessedMetadataMap; - public DownstreamDependencyMetadataManager(IEnumerable downstreamDependencyMetadata) + /// + /// Initializes a new instance of the class. + /// + /// A collection of HTTP dependency metadata used for request resolution. + /// is . + protected HttpDependencyMetadataResolver(IEnumerable dependencyMetadata) { + _ = Throw.IfNull(dependencyMetadata); + Dictionary dependencyTrieMap = []; - foreach (var dependency in downstreamDependencyMetadata) + foreach (var dependency in dependencyMetadata) { AddDependency(dependency, dependencyTrieMap); } - _frozenProcessedMetadataMap = ProcessDownstreamDependencyMetadata(dependencyTrieMap).ToFrozenDictionary(StringComparer.Ordinal); + _frozenProcessedMetadataMap = ProcessDependencyMetadata(dependencyTrieMap).ToFrozenDictionary(StringComparer.Ordinal); } - public RequestMetadata? GetRequestMetadata(HttpRequestMessage requestMessage) +#if !NET462 + /// + /// Gets request metadata for the specified HTTP request message. + /// + /// The HTTP request message. + /// The resolved if found; otherwise, . + public virtual RequestMetadata? GetRequestMetadata(HttpRequestMessage requestMessage) { try { - if (requestMessage.RequestUri == null) + if (requestMessage?.RequestUri == null) { return null; } @@ -56,8 +77,13 @@ public DownstreamDependencyMetadataManager(IEnumerable + /// Gets request metadata for the specified HTTP web request. + /// + /// The HTTP web request. + /// The resolved if found; otherwise, . + public virtual RequestMetadata? GetRequestMetadata(HttpWebRequest requestMessage) { try { @@ -70,12 +96,12 @@ public DownstreamDependencyMetadataManager(IEnumerable ProcessDownstreamDependencyMetadata(Dictionary dependencyTrieMap) + private static Dictionary ProcessDependencyMetadata(Dictionary dependencyTrieMap) { Dictionary finalArrayDict = []; foreach (var dep in dependencyTrieMap) { - var finalArray = ProcessDownstreamDependencyMetadataInternal(dep.Value); + var finalArray = ProcessDependencyMetadataInternal(dep.Value); finalArrayDict.Add(dep.Key, finalArray); } @@ -190,7 +216,7 @@ private static Dictionary ProcessDownstreamDependency // remove the ExlcudeCodeCoverage attribute and ensure it's covered fully using local runs and enable it back before // pushing the change to PR. [ExcludeFromCodeCoverage] - private static ProcessedMetadata ProcessDownstreamDependencyMetadataInternal(RequestMetadataTrieNode requestMetadataTrieRoot) + private static ProcessedMetadata ProcessDependencyMetadataInternal(RequestMetadataTrieNode requestMetadataTrieRoot) { Queue queue = new(); queue.Enqueue(requestMetadataTrieRoot); @@ -278,17 +304,17 @@ private static ProcessedMetadata ProcessDownstreamDependencyMetadataInternal(Req return null; } - private void AddDependency(IDownstreamDependencyMetadata downstreamDependencyMetadata, Dictionary dependencyTrieMap) + private void AddDependency(IDownstreamDependencyMetadata dependencyMetadata, Dictionary dependencyTrieMap) { - foreach (var hostNameSuffix in downstreamDependencyMetadata.UniqueHostNameSuffixes) + foreach (var hostNameSuffix in dependencyMetadata.UniqueHostNameSuffixes) { // Add hostname to hostname suffix trie - AddHostnameToTrie(hostNameSuffix, downstreamDependencyMetadata.DependencyName); + AddHostnameToTrie(hostNameSuffix, dependencyMetadata.DependencyName); } - foreach (var routeMetadata in downstreamDependencyMetadata.RequestMetadata) + foreach (var routeMetadata in dependencyMetadata.RequestMetadata) { - routeMetadata.DependencyName = downstreamDependencyMetadata.DependencyName; + routeMetadata.DependencyName = dependencyMetadata.DependencyName; // Add route metadata to the route per dependency trie AddRouteToTrie(routeMetadata, dependencyTrieMap); @@ -445,4 +471,4 @@ private void AddHostnameToTrie(string hostNameSuffix, string dependencyName) hostMetadata.RequestMetadata : // Return the default request metadata for this host if no matching route is found. routeMetadataTrieRoot.RequestMetadatas[trieCurrent.RequestMetadataEntryIndex]; } -} +} \ No newline at end of file diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDiagnosticsServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDiagnosticsServiceCollectionExtensions.cs index 6cdcd74b263..8a70a5d6860 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDiagnosticsServiceCollectionExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDiagnosticsServiceCollectionExtensions.cs @@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Http.Diagnostics; -using Microsoft.Extensions.Telemetry.Internal; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.DependencyInjection; @@ -23,9 +22,10 @@ public static class HttpDiagnosticsServiceCollectionExtensions public static IServiceCollection AddDownstreamDependencyMetadata(this IServiceCollection services, IDownstreamDependencyMetadata downstreamDependencyMetadata) { _ = Throw.IfNull(services); - services.TryAddSingleton(); - _ = services.AddSingleton(downstreamDependencyMetadata); + _ = Throw.IfNull(downstreamDependencyMetadata); + services.TryAddEnumerable(ServiceDescriptor.Singleton(downstreamDependencyMetadata)); + services.TryAddSingleton(); return services; } @@ -39,9 +39,40 @@ public static IServiceCollection AddDownstreamDependencyMetadata(this IServiceCo where T : class, IDownstreamDependencyMetadata { _ = Throw.IfNull(services); - services.TryAddSingleton(); - _ = services.AddSingleton(); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddSingleton(); + return services; + } + + /// + /// Adds services required for HTTP dependency metadata resolution. + /// + /// The to add the services to. + /// The so that additional calls can be chained. + public static IServiceCollection AddHttpDependencyMetadataResolver(this IServiceCollection services) + { + services.TryAddSingleton(); + return services; + } + + /// + /// Adds services required for HTTP dependency metadata resolution with the specified metadata providers. + /// + /// The to add the services to. + /// The HTTP dependency metadata providers to register. + /// The so that additional calls can be chained. + public static IServiceCollection AddHttpDependencyMetadataResolver( + this IServiceCollection services, + params IDownstreamDependencyMetadata[] providers) + { + _ = Throw.IfNull(services); + + foreach (var provider in providers) + { + services.TryAddEnumerable(ServiceDescriptor.Singleton(provider)); + } + services.TryAddSingleton(); return services; } } diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/HttpRequestReader.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/HttpRequestReader.cs index 78cb73bbddc..c9d7883a22c 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/HttpRequestReader.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/HttpRequestReader.cs @@ -12,7 +12,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http.Diagnostics; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Telemetry.Internal; using Microsoft.Shared.Diagnostics; using Microsoft.Shared.Pools; @@ -43,7 +42,7 @@ internal sealed class HttpRequestReader : IHttpRequestReader private readonly OutgoingPathLoggingMode _outgoingPathLogMode; private readonly IOutgoingRequestContext _requestMetadataContext; - private readonly IDownstreamDependencyMetadataManager? _downstreamDependencyMetadataManager; + private readonly HttpDependencyMetadataResolver? _dependencyMetadataResolver; public HttpRequestReader( IServiceProvider serviceProvider, @@ -51,7 +50,7 @@ public HttpRequestReader( IHttpRouteFormatter routeFormatter, IHttpRouteParser httpRouteParser, IOutgoingRequestContext requestMetadataContext, - IDownstreamDependencyMetadataManager? downstreamDependencyMetadataManager = null, + HttpDependencyMetadataResolver? dependencyMetadataResolver = null, [ServiceKey] string? serviceKey = null) : this( optionsMonitor.GetKeyedOrCurrent(serviceKey), @@ -59,7 +58,7 @@ public HttpRequestReader( httpRouteParser, serviceProvider.GetRequiredOrKeyedRequiredService(serviceKey), requestMetadataContext, - downstreamDependencyMetadataManager) + dependencyMetadataResolver) { } @@ -69,7 +68,7 @@ internal HttpRequestReader( IHttpRouteParser httpRouteParser, IHttpHeadersReader httpHeadersReader, IOutgoingRequestContext requestMetadataContext, - IDownstreamDependencyMetadataManager? downstreamDependencyMetadataManager = null) + HttpDependencyMetadataResolver? dependencyMetadataResolver = null) { _outgoingPathLogMode = Throw.IfOutOfRange(options.RequestPathLoggingMode); _httpHeadersReader = httpHeadersReader; @@ -77,7 +76,7 @@ internal HttpRequestReader( _routeFormatter = routeFormatter; _httpRouteParser = httpRouteParser; _requestMetadataContext = requestMetadataContext; - _downstreamDependencyMetadataManager = downstreamDependencyMetadataManager; + _dependencyMetadataResolver = dependencyMetadataResolver; _defaultSensitiveParameters = options.RouteParameterDataClasses.ToFrozenDictionary(StringComparer.Ordinal); _queryParameterDataClasses = options.RequestQueryParametersDataClasses.ToFrozenDictionary(StringComparer.Ordinal); @@ -234,7 +233,7 @@ private void GetRedactedPathAndParameters(HttpRequestMessage request, LogRecord var requestMetadata = request.GetRequestMetadata() ?? _requestMetadataContext.RequestMetadata ?? - _downstreamDependencyMetadataManager?.GetRequestMetadata(request); + _dependencyMetadataResolver?.GetRequestMetadata(request); if (requestMetadata == null) { diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/IDownstreamDependencyMetadataManager.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/IDownstreamDependencyMetadataManager.cs index 5c49827a3b0..2da95ca2b95 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/IDownstreamDependencyMetadataManager.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/Internal/IDownstreamDependencyMetadataManager.cs @@ -1,6 +1,7 @@ // 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.Net; using System.Net.Http; using Microsoft.Extensions.Http.Diagnostics; @@ -8,8 +9,9 @@ namespace Microsoft.Extensions.Telemetry.Internal; /// -/// Interface to manage dependency metadata. +/// (Obsolete) Use . /// +[Obsolete("Use HttpDependencyMetadataResolver instead. This internal interface will be removed.")] internal interface IDownstreamDependencyMetadataManager { /// diff --git a/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Telemetry/DownstreamDependencyMetadataManagerTests.cs b/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Telemetry/DownstreamDependencyMetadataManagerTests.cs index e7cdb4d3f32..c0763cc7ed8 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Telemetry/DownstreamDependencyMetadataManagerTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Telemetry/DownstreamDependencyMetadataManagerTests.cs @@ -4,14 +4,14 @@ using System; using System.Net.Http; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Telemetry.Internal; +using Microsoft.Extensions.Http.Diagnostics; using Xunit; namespace Microsoft.Extensions.Telemetry.Telemetry; public class DownstreamDependencyMetadataManagerTests : IDisposable { - private readonly IDownstreamDependencyMetadataManager _depMetadataManager; + private readonly HttpDependencyMetadataResolver _metadataResolver; private readonly ServiceProvider _sp; public DownstreamDependencyMetadataManagerTests() @@ -20,7 +20,7 @@ public DownstreamDependencyMetadataManagerTests() .AddDownstreamDependencyMetadata(new BackslashDownstreamDependencyMetadata()) .AddDownstreamDependencyMetadata(new EmptyRouteDependencyMetadata()) .BuildServiceProvider(); - _depMetadataManager = _sp.GetRequiredService(); + _metadataResolver = _sp.GetRequiredService(); } [Theory] @@ -51,7 +51,7 @@ public void GetRequestMetadata_RoutesRegisteredWithBackslashes_ShouldReturnHostM RequestUri = new Uri(uriString: urlString) }; - var requestMetadata = _depMetadataManager.GetRequestMetadata(requestMessage); + var requestMetadata = _metadataResolver.GetRequestMetadata(requestMessage); Assert.NotNull(requestMetadata); Assert.Equal(new BackslashDownstreamDependencyMetadata().DependencyName, requestMetadata.DependencyName); Assert.Equal(expectedRequestName, requestMetadata.RequestName); @@ -71,7 +71,7 @@ public void GetRequestMetadata_EmptyRouteRegisteredForDependency_ShouldReturnMet RequestUri = new Uri(uriString: urlString) }; - var requestMetadata = _depMetadataManager.GetRequestMetadata(requestMessage); + var requestMetadata = _metadataResolver.GetRequestMetadata(requestMessage); Assert.NotNull(requestMetadata); Assert.Equal("EmptyRouteService", requestMetadata.DependencyName);