Skip to content
Merged
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
Expand Up @@ -188,41 +188,14 @@ private static IHttpClientBuilder AddNamedClientLoggingInternal(IHttpClientBuild
.AddHttpHeadersRedactor()
.AddOutgoingRequestContext();

/*
TODO: Uncomment this code once https://github.com/dotnet/runtime/issues/89447 is resolved:

builder.Services.TryAddKeyedSingleton<HttpClientLogger>(builder.Name)
builder.Services.TryAddKeyedSingleton<IHttpRequestReader, HttpRequestReader>(builder.Name)
builder.Services.TryAddKeyedSingleton<IHttpHeadersReader, HttpHeadersReader>(builder.Name)
builder.Services.TryAddKeyedSingleton<HttpClientLogger>(builder.Name);
builder.Services.TryAddKeyedSingleton<IHttpRequestReader, HttpRequestReader>(builder.Name);
builder.Services.TryAddKeyedSingleton<IHttpHeadersReader, HttpHeadersReader>(builder.Name);

return builder
.RemoveAllLoggers()
.AddLogger(
serviceProvider => serviceProvider.GetRequiredKeyedService<HttpClientLogger>(builder.Name),
wrapHandlersPipeline: true)
*/

builder.Services.TryAddActivatedSingleton<IHttpRequestReader, HttpRequestReader>();
builder.Services.TryAddActivatedSingleton<IHttpHeadersReader, HttpHeadersReader>();

return builder
.RemoveAllLoggers()
.AddLogger(
serviceProvider =>
{
var loggingOptions = Options.Options.Create(serviceProvider
.GetRequiredService<IOptionsMonitor<LoggingOptions>>().Get(builder.Name));

return ActivatorUtilities.CreateInstance<HttpClientLogger>(
serviceProvider,
ActivatorUtilities.CreateInstance<HttpRequestReader>(
serviceProvider,
ActivatorUtilities.CreateInstance<HttpHeadersReader>(
serviceProvider,
loggingOptions),
loggingOptions),
loggingOptions);
},
wrapHandlersPipeline: true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http.Logging;
using Microsoft.Extensions.Http.Telemetry.Logging.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Telemetry.Logging;
using Microsoft.Shared.Diagnostics;
using Microsoft.Shared.Pools;

namespace Microsoft.Extensions.Http.Telemetry.Logging;
Expand All @@ -36,20 +36,32 @@ internal sealed class HttpClientLogger : IHttpClientAsyncLogger
private IHttpClientLogEnricher[] _enrichers;

public HttpClientLogger(
IServiceProvider serviceProvider,
ILogger<HttpClientLogger> logger,
IEnumerable<IHttpClientLogEnricher> enrichers,
IOptionsMonitor<LoggingOptions> optionsMonitor,
[ServiceKey] string? serviceKey = null)
: this(
logger,
serviceProvider.GetRequiredOrKeyedRequiredService<IHttpRequestReader>(serviceKey),
enrichers,
optionsMonitor.GetKeyedOrCurrent(serviceKey))
{
}

internal HttpClientLogger(
ILogger<HttpClientLogger> logger,
IHttpRequestReader httpRequestReader,
IEnumerable<IHttpClientLogEnricher> enrichers,
IOptions<LoggingOptions> options)
LoggingOptions options)
{
_logger = logger;
_httpRequestReader = httpRequestReader;
_enrichers = enrichers.Where(static x => x is not null).ToArray();
var optionsValue = Throw.IfMemberNull(options, options.Value);

_logRequestStart = optionsValue.LogRequestStart;
_logResponseHeaders = optionsValue.ResponseHeadersDataClasses.Count > 0;
_logRequestHeaders = optionsValue.RequestHeadersDataClasses.Count > 0;
_pathParametersRedactionSkipped = optionsValue.RequestPathParameterRedactionMode == HttpRouteParameterRedactionMode.None;
_logRequestStart = options.LogRequestStart;
_logResponseHeaders = options.ResponseHeadersDataClasses.Count > 0;
_logRequestHeaders = options.RequestHeadersDataClasses.Count > 0;
_pathParametersRedactionSkipped = options.RequestPathParameterRedactionMode == HttpRouteParameterRedactionMode.None;
}

[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "The logger shouldn't throw")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.Extensions.Compliance.Classification;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Telemetry.Internal;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.Http.Telemetry.Logging.Internal;

Expand All @@ -19,14 +19,14 @@ internal sealed class HttpHeadersReader : IHttpHeadersReader
private readonly FrozenDictionary<string, DataClassification> _responseHeaders;
private readonly IHttpHeadersRedactor _redactor;

public HttpHeadersReader(IOptions<LoggingOptions> options, IHttpHeadersRedactor redactor)
public HttpHeadersReader(IOptionsMonitor<LoggingOptions> optionsMonitor, IHttpHeadersRedactor redactor, [ServiceKey] string? serviceKey = null)
{
_ = Throw.IfMemberNull(options, options.Value);
var options = optionsMonitor.GetKeyedOrCurrent(serviceKey);

_redactor = redactor;

_requestHeaders = options.Value.RequestHeadersDataClasses.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
_responseHeaders = options.Value.ResponseHeadersDataClasses.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
_requestHeaders = options.RequestHeadersDataClasses.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
_responseHeaders = options.ResponseHeadersDataClasses.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
}

public void ReadRequestHeaders(HttpRequestMessage request, List<KeyValuePair<string, string>>? destination)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#if NETCOREAPP3_1_OR_GREATER
using Microsoft.Extensions.ObjectPool;
#endif
using Microsoft.Extensions.Options;
using Microsoft.Shared.Diagnostics;
#if NETCOREAPP3_1_OR_GREATER
using Microsoft.Shared.Pools;
Expand All @@ -34,10 +33,8 @@ internal sealed class HttpRequestBodyReader
private readonly FrozenSet<string> _readableRequestContentTypes;
private readonly int _requestReadLimit;

public HttpRequestBodyReader(IOptions<LoggingOptions> options, IDebuggerState? debugger = null)
public HttpRequestBodyReader(LoggingOptions requestOptions, IDebuggerState? debugger = null)
{
var requestOptions = Throw.IfMemberNull(options, options.Value);

_readableRequestContentTypes = requestOptions.RequestBodyContentTypes.ToFrozenSet(StringComparer.OrdinalIgnoreCase);
debugger ??= DebuggerState.System;
_requestReadLimit = requestOptions.BodySizeLimit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Compliance.Classification;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Telemetry;
using Microsoft.Extensions.Telemetry.Internal;
Expand Down Expand Up @@ -40,34 +41,50 @@ internal sealed class HttpRequestReader : IHttpRequestReader
private readonly IDownstreamDependencyMetadataManager? _downstreamDependencyMetadataManager;

public HttpRequestReader(
IOptions<LoggingOptions> options,
IServiceProvider serviceProvider,
IOptionsMonitor<LoggingOptions> optionsMonitor,
IHttpRouteFormatter routeFormatter,
IOutgoingRequestContext requestMetadataContext,
IDownstreamDependencyMetadataManager? downstreamDependencyMetadataManager = null,
[ServiceKey] string? serviceKey = null)
: this(
optionsMonitor.GetKeyedOrCurrent(serviceKey),
routeFormatter,
serviceProvider.GetRequiredOrKeyedRequiredService<IHttpHeadersReader>(serviceKey),
requestMetadataContext,
downstreamDependencyMetadataManager)
{
}

internal HttpRequestReader(
LoggingOptions options,
IHttpRouteFormatter routeFormatter,
IHttpHeadersReader httpHeadersReader,
IOutgoingRequestContext requestMetadataContext,
IDownstreamDependencyMetadataManager? downstreamDependencyMetadataManager = null)
{
var optionsValue = Throw.IfMemberNull(options, options.Value);
_routeFormatter = routeFormatter;
_outgoingPathLogMode = Throw.IfOutOfRange(optionsValue.RequestPathLoggingMode);
_outgoingPathLogMode = Throw.IfOutOfRange(options.RequestPathLoggingMode);
_httpHeadersReader = httpHeadersReader;

_routeFormatter = routeFormatter;
_requestMetadataContext = requestMetadataContext;
_downstreamDependencyMetadataManager = downstreamDependencyMetadataManager;

_defaultSensitiveParameters = optionsValue.RouteParameterDataClasses.ToFrozenDictionary(StringComparer.Ordinal);
_defaultSensitiveParameters = options.RouteParameterDataClasses.ToFrozenDictionary(StringComparer.Ordinal);

if (optionsValue.LogBody)
if (options.LogBody)
{
_logRequestBody = optionsValue.RequestBodyContentTypes.Count > 0;
_logResponseBody = optionsValue.ResponseBodyContentTypes.Count > 0;
_logRequestBody = options.RequestBodyContentTypes.Count > 0;
_logResponseBody = options.ResponseBodyContentTypes.Count > 0;
}

_logRequestHeaders = optionsValue.RequestHeadersDataClasses.Count > 0;
_logResponseHeaders = optionsValue.ResponseHeadersDataClasses.Count > 0;
_logRequestHeaders = options.RequestHeadersDataClasses.Count > 0;
_logResponseHeaders = options.ResponseHeadersDataClasses.Count > 0;

_httpRequestBodyReader = new HttpRequestBodyReader(options);
_httpResponseBodyReader = new HttpResponseBodyReader(options);

_routeParameterRedactionMode = optionsValue.RequestPathParameterRedactionMode;
_routeParameterRedactionMode = options.RequestPathParameterRedactionMode;
}

public async Task ReadRequestAsync(LogRecord logRecord, HttpRequestMessage request,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Microsoft.IO;
using Microsoft.Shared.Diagnostics;
using Microsoft.Shared.Pools;
Expand All @@ -29,11 +28,8 @@ internal sealed class HttpResponseBodyReader

private readonly RecyclableMemoryStreamManager _streamManager;

public HttpResponseBodyReader(IOptions<LoggingOptions> options, IDebuggerState? debugger = null)
public HttpResponseBodyReader(LoggingOptions responseOptions, IDebuggerState? debugger = null)
{
_ = Throw.IfMemberNull(options, options.Value);

var responseOptions = options.Value;
_streamManager = new RecyclableMemoryStreamManager();
_readableResponseContentTypes = responseOptions.ResponseBodyContentTypes.ToFrozenSet(StringComparer.OrdinalIgnoreCase);
_responseReadLimit = responseOptions.BodySizeLimit;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Http.Telemetry.Logging.Internal;

internal static class OptionsExtensions
{
/// <summary>
/// Gets the options for the given service key, or the current value if the service key is <see langword="null"/>.
/// </summary>
/// <typeparam name="TOptions">The options type.</typeparam>
/// <param name="optionsMonitor">The <see cref="IOptionsMonitor{TOptions}"/> to load the options object from.</param>
/// <param name="serviceKey">An optional string that specifies the name of the options object to get.</param>
/// <returns>The <typeparamref name="TOptions"/> instance.</returns>
public static TOptions GetKeyedOrCurrent<TOptions>(this IOptionsMonitor<TOptions> optionsMonitor, string? serviceKey)
where TOptions : class
{
if (serviceKey is null)
{
return optionsMonitor.CurrentValue;
}

return optionsMonitor.Get(serviceKey);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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 Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.Http.Telemetry.Logging.Internal;

internal static class ServiceProviderExtensions
{
/// <summary>
/// Gets a keyed service from the <see cref="IServiceProvider"/>, or a non-keyed service if the key is <see langword="null"/>.
/// </summary>
/// <typeparam name="T">The type of service object to get.</typeparam>
/// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param>
/// <param name="serviceKey">An optional string that specifies the key of service object to get.</param>
/// <returns>A service object of type <typeparamref name="T"/>.</returns>
/// <exception cref="InvalidOperationException">There is no service of type <typeparamref name="T"/> registered.</exception>
public static T GetRequiredOrKeyedRequiredService<T>(this IServiceProvider provider, string? serviceKey)
where T : notnull
{
return serviceKey is null
? provider.GetRequiredService<T>()
: provider.GetRequiredKeyedService<T>(serviceKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static class OptionsBuilderExtensions

_ = services.AddOptions();

return new OptionsBuilder<TOptions>(services, name ?? Microsoft.Extensions.Options.Options.DefaultName)
return new OptionsBuilder<TOptions>(services, name ?? Options.DefaultName)
.ValidateOnStart();
}

Expand All @@ -59,7 +59,7 @@ public static OptionsBuilder<TOptions> AddValidatedOptions<
.AddOptions()
.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<TOptions>, TValidateOptions>());

return new OptionsBuilder<TOptions>(services, name ?? Microsoft.Extensions.Options.Options.DefaultName)
return new OptionsBuilder<TOptions>(services, name ?? Options.DefaultName)
.ValidateOnStart();
}
}
Loading