Skip to content
Open
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,25 @@
// 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;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Shared.DiagnosticIds;

namespace Microsoft.Extensions.Http.Diagnostics;

/// <summary>
/// Default implementation of <see cref="HttpDependencyMetadataResolver"/> that uses the base
/// trie-based lookup algorithm.
/// </summary>
[Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)]
internal sealed class DefaultHttpDependencyMetadataResolver : HttpDependencyMetadataResolver
{
/// <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 @@ -5,14 +5,23 @@
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Telemetry.Internal;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;

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>
[Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)]
public abstract class HttpDependencyMetadataResolver
{
internal readonly struct ProcessedMetadata
{
Expand All @@ -27,22 +36,34 @@ internal readonly struct ProcessedMetadata
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)
{
_ = Throw.IfNull(dependencyMetadata);

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 instance of <see cref="HttpRequestMessage"/>.
/// </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 @@ -56,26 +77,10 @@ public DownstreamDependencyMetadataManager(IEnumerable<IDownstreamDependencyMeta
return null;
}
}

public RequestMetadata? GetRequestMetadata(HttpWebRequest requestMessage)
{
try
{
var hostMetadata = GetHostMetadata(requestMessage.RequestUri.Host);
return GetRequestMetadataInternal(requestMessage.Method, requestMessage.RequestUri.AbsolutePath, hostMetadata);
}
catch (Exception)
{
// Catch exceptions here to avoid impacting services if a bug ever gets introduced in this path.
return null;
}
}

private static char[] MakeToUpperArray()
{
// 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 +178,12 @@ private static void AddRouteToTrie(RequestMetadata routeMetadata, Dictionary<str
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 +195,7 @@ private static Dictionary<string, ProcessedMetadata> 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<RequestMetadataTrieNode> queue = new();
queue.Enqueue(requestMetadataTrieRoot);
Expand Down Expand Up @@ -278,17 +283,17 @@ private static ProcessedMetadata ProcessDownstreamDependencyMetadataInternal(Req
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 +450,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];
}
}
}
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,7 +22,7 @@ public static class HttpDiagnosticsServiceCollectionExtensions
public static IServiceCollection AddDownstreamDependencyMetadata(this IServiceCollection services, IDownstreamDependencyMetadata downstreamDependencyMetadata)
{
_ = Throw.IfNull(services);
services.TryAddSingleton<IDownstreamDependencyMetadataManager, DownstreamDependencyMetadataManager>();
services.TryAddSingleton<HttpDependencyMetadataResolver, DefaultHttpDependencyMetadataResolver>();
_ = services.AddSingleton(downstreamDependencyMetadata);

return services;
Expand All @@ -39,9 +38,8 @@ public static IServiceCollection AddDownstreamDependencyMetadata(this IServiceCo
where T : class, IDownstreamDependencyMetadata
{
_ = Throw.IfNull(services);
services.TryAddSingleton<IDownstreamDependencyMetadataManager, DownstreamDependencyMetadataManager>();
services.TryAddSingleton<HttpDependencyMetadataResolver, DefaultHttpDependencyMetadataResolver>();
_ = services.AddSingleton<IDownstreamDependencyMetadata, T>();

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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@
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
public class HttpDependencyMetadataResolverTests : IDisposable
{
private readonly IDownstreamDependencyMetadataManager _depMetadataManager;
private readonly HttpDependencyMetadataResolver _metadataResolver;
private readonly ServiceProvider _sp;

public DownstreamDependencyMetadataManagerTests()
public HttpDependencyMetadataResolverTests()
{
_sp = new ServiceCollection()
.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