Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Default implementation of <see cref="HttpDependencyMetadataResolver"/> that uses the base
/// trie-based lookup algorithm.
/// </summary>
public sealed class DefaultHttpDependencyMetadataResolver : HttpDependencyMetadataResolver

Check failure on line 12 in src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DefaultHttpDependencyMetadataResolver.cs

View check run for this annotation

Azure Pipelines / extensions-ci (Correctness WarningsCheck)

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DefaultHttpDependencyMetadataResolver.cs#L12

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DefaultHttpDependencyMetadataResolver.cs(12,21): error LA0003: (NETCORE_ENGINEERING_TELEMETRY=Build) Newly added symbol 'Microsoft.Extensions.Http.Diagnostics.DefaultHttpDependencyMetadataResolver' must be marked as experimental

Check failure on line 12 in src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DefaultHttpDependencyMetadataResolver.cs

View check run for this annotation

Azure Pipelines / extensions-ci

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DefaultHttpDependencyMetadataResolver.cs#L12

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/DefaultHttpDependencyMetadataResolver.cs(12,21): error LA0003: (NETCORE_ENGINEERING_TELEMETRY=Build) Newly added symbol 'Microsoft.Extensions.Http.Diagnostics.DefaultHttpDependencyMetadataResolver' must be marked as experimental
{
/// <summary>
/// Initializes a new instance of the <see cref="DefaultHttpDependencyMetadataResolver"/> class.
/// </summary>
/// <param name="dependencyMetadata">A collection of HTTP dependency metadata used for request resolution.</param>
public DefaultHttpDependencyMetadataResolver(IEnumerable<IDownstreamDependencyMetadata> dependencyMetadata)
: base(dependencyMetadata)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Telemetry.Internal;

namespace Microsoft.Extensions.Http.Diagnostics;

internal sealed class DownstreamDependencyMetadataManager : IDownstreamDependencyMetadataManager
/// <summary>
/// Resolves metadata for HTTP requests based on hostname, path, and method patterns.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public abstract class HttpDependencyMetadataResolver

Check failure on line 22 in src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs

View check run for this annotation

Azure Pipelines / extensions-ci (Correctness WarningsCheck)

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs#L22

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs(22,23): error LA0003: (NETCORE_ENGINEERING_TELEMETRY=Build) Newly added symbol 'Microsoft.Extensions.Http.Diagnostics.HttpDependencyMetadataResolver' must be marked as experimental
{
internal readonly struct ProcessedMetadata
{
Expand All @@ -27,22 +34,37 @@
private readonly HostSuffixTrieNode _hostSuffixTrieRoot = new();
private readonly FrozenDictionary<string, ProcessedMetadata> _frozenProcessedMetadataMap;

public DownstreamDependencyMetadataManager(IEnumerable<IDownstreamDependencyMetadata> downstreamDependencyMetadata)
/// <summary>
/// Initializes a new instance of the <see cref="HttpDependencyMetadataResolver"/> class.
/// </summary>
/// <param name="dependencyMetadata">A collection of HTTP dependency metadata used for request resolution.</param>
/// <exception cref="ArgumentNullException"><paramref name="dependencyMetadata"/> is <see langword="null"/>.</exception>
protected HttpDependencyMetadataResolver(IEnumerable<IDownstreamDependencyMetadata> dependencyMetadata)
{
if (dependencyMetadata == null)
{
throw new ArgumentNullException(nameof(dependencyMetadata));

Check failure on line 46 in src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs

View check run for this annotation

Azure Pipelines / extensions-ci (Correctness WarningsCheck)

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs#L46

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs(46,13): error LA0001: (NETCORE_ENGINEERING_TELEMETRY=Build) Use 'Microsoft.Shared.Diagnostics.Throws.ArgumentNullException' to throw the exception instead to improve performance

Check failure on line 46 in src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs

View check run for this annotation

Azure Pipelines / extensions-ci

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs#L46

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs(46,13): error LA0001: (NETCORE_ENGINEERING_TELEMETRY=Build) Use 'Microsoft.Shared.Diagnostics.Throws.ArgumentNullException' to throw the exception instead to improve performance
}

Dictionary<string, RequestMetadataTrieNode> 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)
/// <summary>
/// Gets request metadata for the specified HTTP request message.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <returns>The resolved <see cref="RequestMetadata"/> if found; otherwise, <see langword="null"/>.</returns>
public virtual RequestMetadata? GetRequestMetadata(HttpRequestMessage requestMessage)
{
try
{
if (requestMessage.RequestUri == null)
if (requestMessage?.RequestUri == null)
{
return null;
}
Expand All @@ -57,11 +79,16 @@
}
}

public RequestMetadata? GetRequestMetadata(HttpWebRequest requestMessage)
/// <summary>
/// Gets request metadata for the specified HTTP web request.
/// </summary>
/// <param name="requestMessage">The HTTP web request.</param>
/// <returns>The resolved <see cref="RequestMetadata"/> if found; otherwise, <see langword="null"/>.</returns>
public virtual RequestMetadata? GetRequestMetadata(HttpWebRequest requestMessage)
{
try
{
var hostMetadata = GetHostMetadata(requestMessage.RequestUri.Host);

Check failure on line 91 in src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs

View check run for this annotation

Azure Pipelines / extensions-ci (Correctness WarningsCheck)

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs#L91

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs(91,48): error CA1062: (NETCORE_ENGINEERING_TELEMETRY=Build) In externally visible method 'RequestMetadata? HttpDependencyMetadataResolver.GetRequestMetadata(HttpWebRequest requestMessage)', validate parameter 'requestMessage' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)

Check failure on line 91 in src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs

View check run for this annotation

Azure Pipelines / extensions-ci

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs#L91

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDependencyMetadataResolver.cs(91,48): error CA1062: (NETCORE_ENGINEERING_TELEMETRY=Build) In externally visible method 'RequestMetadata? HttpDependencyMetadataResolver.GetRequestMetadata(HttpWebRequest requestMessage)', validate parameter 'requestMessage' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)
return GetRequestMetadataInternal(requestMessage.Method, requestMessage.RequestUri.AbsolutePath, hostMetadata);
}
catch (Exception)
Expand All @@ -75,7 +102,6 @@
{
// Initialize the _toUpper array for quick conversion of any ascii char to upper
// without incurring cost of checking whether the character requires conversion.

var a = new char[Constants.ASCIICharCount];
for (int i = 0; i < Constants.ASCIICharCount; i++)
{
Expand Down Expand Up @@ -173,12 +199,12 @@
trieCurrent.RequestMetadata = routeMetadata;
}

private static Dictionary<string, ProcessedMetadata> ProcessDownstreamDependencyMetadata(Dictionary<string, RequestMetadataTrieNode> dependencyTrieMap)
private static Dictionary<string, ProcessedMetadata> ProcessDependencyMetadata(Dictionary<string, RequestMetadataTrieNode> dependencyTrieMap)
{
Dictionary<string, ProcessedMetadata> finalArrayDict = [];
foreach (var dep in dependencyTrieMap)
{
var finalArray = ProcessDownstreamDependencyMetadataInternal(dep.Value);
var finalArray = ProcessDependencyMetadataInternal(dep.Value);
finalArrayDict.Add(dep.Key, finalArray);
}

Expand All @@ -190,7 +216,7 @@
// 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<RequestMetadataTrieNode> queue = new();
queue.Enqueue(requestMetadataTrieRoot);
Expand Down Expand Up @@ -278,17 +304,17 @@
return null;
}

private void AddDependency(IDownstreamDependencyMetadata downstreamDependencyMetadata, Dictionary<string, RequestMetadataTrieNode> dependencyTrieMap)
private void AddDependency(IDownstreamDependencyMetadata dependencyMetadata, Dictionary<string, RequestMetadataTrieNode> 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);
Expand Down Expand Up @@ -445,4 +471,4 @@
hostMetadata.RequestMetadata : // Return the default request metadata for this host if no matching route is found.
routeMetadataTrieRoot.RequestMetadatas[trieCurrent.RequestMetadataEntryIndex];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,9 +22,10 @@
public static IServiceCollection AddDownstreamDependencyMetadata(this IServiceCollection services, IDownstreamDependencyMetadata downstreamDependencyMetadata)
{
_ = Throw.IfNull(services);
services.TryAddSingleton<IDownstreamDependencyMetadataManager, DownstreamDependencyMetadataManager>();
_ = services.AddSingleton(downstreamDependencyMetadata);
_ = Throw.IfNull(downstreamDependencyMetadata);

services.TryAddEnumerable(ServiceDescriptor.Singleton<IDownstreamDependencyMetadata>(downstreamDependencyMetadata));
services.TryAddSingleton<HttpDependencyMetadataResolver, DefaultHttpDependencyMetadataResolver>();
return services;
}

Expand All @@ -39,9 +39,40 @@
where T : class, IDownstreamDependencyMetadata
{
_ = Throw.IfNull(services);
services.TryAddSingleton<IDownstreamDependencyMetadataManager, DownstreamDependencyMetadataManager>();
_ = services.AddSingleton<IDownstreamDependencyMetadata, T>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IDownstreamDependencyMetadata, T>());
services.TryAddSingleton<HttpDependencyMetadataResolver, DefaultHttpDependencyMetadataResolver>();
return services;
}

/// <summary>
/// Adds services required for HTTP dependency metadata resolution.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddHttpDependencyMetadataResolver(this IServiceCollection services)
{
services.TryAddSingleton<HttpDependencyMetadataResolver, DefaultHttpDependencyMetadataResolver>();
return services;
}

/// <summary>
/// Adds services required for HTTP dependency metadata resolution with the specified metadata providers.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="providers">The HTTP dependency metadata providers to register.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddHttpDependencyMetadataResolver(
this IServiceCollection services,
params IDownstreamDependencyMetadata[] providers)
{
_ = Throw.IfNull(services);

foreach (var provider in providers)

Check failure on line 70 in src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDiagnosticsServiceCollectionExtensions.cs

View check run for this annotation

Azure Pipelines / extensions-ci (Correctness WarningsCheck)

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDiagnosticsServiceCollectionExtensions.cs#L70

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDiagnosticsServiceCollectionExtensions.cs(70,34): error CA1062: (NETCORE_ENGINEERING_TELEMETRY=Build) In externally visible method 'IServiceCollection HttpDiagnosticsServiceCollectionExtensions.AddHttpDependencyMetadataResolver(IServiceCollection services, params IDownstreamDependencyMetadata[] providers)', validate parameter 'providers' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)

Check failure on line 70 in src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDiagnosticsServiceCollectionExtensions.cs

View check run for this annotation

Azure Pipelines / extensions-ci

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDiagnosticsServiceCollectionExtensions.cs#L70

src/Libraries/Microsoft.Extensions.Http.Diagnostics/Http/HttpDiagnosticsServiceCollectionExtensions.cs(70,34): error CA1062: (NETCORE_ENGINEERING_TELEMETRY=Build) In externally visible method 'IServiceCollection HttpDiagnosticsServiceCollectionExtensions.AddHttpDependencyMetadataResolver(IServiceCollection services, params IDownstreamDependencyMetadata[] providers)', validate parameter 'providers' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IDownstreamDependencyMetadata>(provider));
}

services.TryAddSingleton<HttpDependencyMetadataResolver, DefaultHttpDependencyMetadataResolver>();
return services;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -43,23 +42,23 @@ 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,
IOptionsMonitor<LoggingOptions> optionsMonitor,
IHttpRouteFormatter routeFormatter,
IHttpRouteParser httpRouteParser,
IOutgoingRequestContext requestMetadataContext,
IDownstreamDependencyMetadataManager? downstreamDependencyMetadataManager = null,
HttpDependencyMetadataResolver? dependencyMetadataResolver = null,
[ServiceKey] string? serviceKey = null)
: this(
optionsMonitor.GetKeyedOrCurrent(serviceKey),
routeFormatter,
httpRouteParser,
serviceProvider.GetRequiredOrKeyedRequiredService<IHttpHeadersReader>(serviceKey),
requestMetadataContext,
downstreamDependencyMetadataManager)
dependencyMetadataResolver)
{
}

Expand All @@ -69,15 +68,15 @@ internal HttpRequestReader(
IHttpRouteParser httpRouteParser,
IHttpHeadersReader httpHeadersReader,
IOutgoingRequestContext requestMetadataContext,
IDownstreamDependencyMetadataManager? downstreamDependencyMetadataManager = null)
HttpDependencyMetadataResolver? dependencyMetadataResolver = null)
{
_outgoingPathLogMode = Throw.IfOutOfRange(options.RequestPathLoggingMode);
_httpHeadersReader = httpHeadersReader;

_routeFormatter = routeFormatter;
_httpRouteParser = httpRouteParser;
_requestMetadataContext = requestMetadataContext;
_downstreamDependencyMetadataManager = downstreamDependencyMetadataManager;
_dependencyMetadataResolver = dependencyMetadataResolver;

_defaultSensitiveParameters = options.RouteParameterDataClasses.ToFrozenDictionary(StringComparer.Ordinal);
_queryParameterDataClasses = options.RequestQueryParametersDataClasses.ToFrozenDictionary(StringComparer.Ordinal);
Expand Down Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
// 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;

namespace Microsoft.Extensions.Telemetry.Internal;

/// <summary>
/// Interface to manage dependency metadata.
/// (Obsolete) Use <see cref="HttpDependencyMetadataResolver"/>.
/// </summary>
[Obsolete("Use HttpDependencyMetadataResolver instead. This internal interface will be removed.")]
internal interface IDownstreamDependencyMetadataManager
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -20,7 +20,7 @@ public DownstreamDependencyMetadataManagerTests()
.AddDownstreamDependencyMetadata(new BackslashDownstreamDependencyMetadata())
.AddDownstreamDependencyMetadata(new EmptyRouteDependencyMetadata())
.BuildServiceProvider();
_depMetadataManager = _sp.GetRequiredService<IDownstreamDependencyMetadataManager>();
_metadataResolver = _sp.GetRequiredService<HttpDependencyMetadataResolver>();
}

[Theory]
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Loading