-
Notifications
You must be signed in to change notification settings - Fork 764
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1073446
commit 988c709
Showing
32 changed files
with
1,374 additions
and
5 deletions.
There are no files selected for viewing
97 changes: 97 additions & 0 deletions
97
src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Buffering/HttpRequestBuffer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
#if NET9_0_OR_GREATER | ||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using Microsoft.Extensions.Diagnostics; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Logging.Abstractions; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.AspNetCore.Diagnostics.Logging; | ||
|
||
internal sealed class HttpRequestBuffer : ILoggingBuffer | ||
{ | ||
private readonly IOptionsMonitor<HttpRequestBufferOptions> _options; | ||
private readonly ConcurrentDictionary<IBufferedLogger, ConcurrentQueue<HttpRequestBufferedLogRecord>> _buffers; | ||
private readonly TimeProvider _timeProvider = TimeProvider.System; | ||
private DateTimeOffset _lastFlushTimestamp; | ||
|
||
public HttpRequestBuffer(IOptionsMonitor<HttpRequestBufferOptions> options) | ||
{ | ||
_options = options; | ||
_buffers = new ConcurrentDictionary<IBufferedLogger, ConcurrentQueue<HttpRequestBufferedLogRecord>>(); | ||
_lastFlushTimestamp = _timeProvider.GetUtcNow(); | ||
} | ||
|
||
internal HttpRequestBuffer(IOptionsMonitor<HttpRequestBufferOptions> options, TimeProvider timeProvider) | ||
: this(options) | ||
{ | ||
_timeProvider = timeProvider; | ||
_lastFlushTimestamp = _timeProvider.GetUtcNow(); | ||
} | ||
|
||
public bool TryEnqueue( | ||
IBufferedLogger logger, | ||
LogLevel logLevel, | ||
string category, | ||
EventId eventId, | ||
IReadOnlyList<KeyValuePair<string, object?>> joiner, | ||
Exception? exception, | ||
string formatter) | ||
{ | ||
if (!IsEnabled(category, logLevel, eventId)) | ||
{ | ||
return false; | ||
} | ||
|
||
var record = new HttpRequestBufferedLogRecord(logLevel, eventId, joiner, exception, formatter); | ||
var queue = _buffers.GetOrAdd(logger, _ => new ConcurrentQueue<HttpRequestBufferedLogRecord>()); | ||
|
||
// probably don't need to limit buffer capacity? | ||
// because buffer is disposed when the respective HttpContext is disposed | ||
// don't expect it to grow so much to cause a problem? | ||
if (queue.Count >= _options.CurrentValue.PerRequestCapacity) | ||
{ | ||
_ = queue.TryDequeue(out HttpRequestBufferedLogRecord? _); | ||
} | ||
|
||
queue.Enqueue(record); | ||
|
||
return true; | ||
} | ||
|
||
public void Flush() | ||
{ | ||
foreach (var (logger, queue) in _buffers) | ||
{ | ||
var result = new List<BufferedLogRecord>(); | ||
while (!queue.IsEmpty) | ||
{ | ||
if (queue.TryDequeue(out HttpRequestBufferedLogRecord? item)) | ||
{ | ||
result.Add(item); | ||
} | ||
} | ||
|
||
logger.LogRecords(result); | ||
} | ||
|
||
_lastFlushTimestamp = _timeProvider.GetUtcNow(); | ||
} | ||
|
||
public bool IsEnabled(string category, LogLevel logLevel, EventId eventId) | ||
{ | ||
if (_timeProvider.GetUtcNow() < _lastFlushTimestamp + _options.CurrentValue.SuspendAfterFlushDuration) | ||
{ | ||
return false; | ||
} | ||
|
||
LoggerFilterRuleSelector.Select<BufferFilterRule>(_options.CurrentValue.Rules, category, logLevel, eventId, out BufferFilterRule? rule); | ||
|
||
return rule is not null; | ||
} | ||
} | ||
#endif |
43 changes: 43 additions & 0 deletions
43
...icrosoft.AspNetCore.Diagnostics.Middleware/Buffering/HttpRequestBufferConfigureOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
#if NET9_0_OR_GREATER | ||
using System.Collections.Generic; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Microsoft.AspNetCore.Diagnostics.Logging; | ||
|
||
internal sealed class HttpRequestBufferConfigureOptions : IConfigureOptions<HttpRequestBufferOptions> | ||
{ | ||
private const string BufferingKey = "Buffering"; | ||
private readonly IConfiguration _configuration; | ||
|
||
public HttpRequestBufferConfigureOptions(IConfiguration configuration) | ||
{ | ||
_configuration = configuration; | ||
} | ||
|
||
public void Configure(HttpRequestBufferOptions options) | ||
{ | ||
if (_configuration == null) | ||
{ | ||
return; | ||
} | ||
|
||
var section = _configuration.GetSection(BufferingKey); | ||
if (!section.Exists()) | ||
{ | ||
return; | ||
} | ||
|
||
var parsedOptions = section.Get<HttpRequestBufferOptions>(); | ||
if (parsedOptions is null) | ||
{ | ||
return; | ||
} | ||
|
||
options.Rules.AddRange(parsedOptions.Rules); | ||
} | ||
} | ||
#endif |
97 changes: 97 additions & 0 deletions
97
...t.AspNetCore.Diagnostics.Middleware/Buffering/HttpRequestBufferLoggerBuilderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
#if NET9_0_OR_GREATER | ||
using System; | ||
using System.Diagnostics.CodeAnalysis; | ||
using Microsoft.AspNetCore.Diagnostics.Logging; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.Shared.DiagnosticIds; | ||
using Microsoft.Shared.Diagnostics; | ||
|
||
namespace Microsoft.Extensions.Logging; | ||
|
||
/// <summary> | ||
/// Lets you register log buffers in a dependency injection container. | ||
/// </summary> | ||
[Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)] | ||
public static class HttpRequestBufferLoggerBuilderExtensions | ||
{ | ||
/// <summary> | ||
/// Adds HTTP request-aware buffer to the logging infrastructure. Matched logs will be buffered in | ||
/// a buffer specific to each HTTP request and can optionally be flushed and emitted during the request lifetime./>. | ||
/// </summary> | ||
/// <param name="builder">The <see cref="ILoggingBuilder" />.</param> | ||
/// <param name="configuration">The <see cref="IConfiguration" /> to add.</param> | ||
/// <returns>The value of <paramref name="builder"/>.</returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception> | ||
public static ILoggingBuilder AddHttpRequestBuffer(this ILoggingBuilder builder, IConfiguration configuration) | ||
{ | ||
_ = Throw.IfNull(builder); | ||
_ = Throw.IfNull(configuration); | ||
|
||
return builder | ||
.AddHttpRequestBufferConfiguration(configuration) | ||
.AddHttpRequestBufferProvider(); | ||
} | ||
|
||
/// <summary> | ||
/// Adds HTTP request-aware buffer to the logging infrastructure. Matched logs will be buffered in | ||
/// a buffer specific to each HTTP request and can optionally be flushed and emitted during the request lifetime./>. | ||
/// </summary> | ||
/// <param name="builder">The <see cref="ILoggingBuilder" />.</param> | ||
/// <param name="level">The log level (and below) to apply the buffer to.</param> | ||
/// <param name="configure">The buffer configuration options.</param> | ||
/// <returns>The value of <paramref name="builder"/>.</returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception> | ||
public static ILoggingBuilder AddHttpRequestBuffer(this ILoggingBuilder builder, LogLevel? level = null, Action<HttpRequestBufferOptions>? configure = null) | ||
{ | ||
_ = Throw.IfNull(builder); | ||
|
||
_ = builder.Services | ||
.Configure<HttpRequestBufferOptions>(options => options.Rules.Add(new BufferFilterRule(null, level, null))) | ||
.Configure(configure ?? new Action<HttpRequestBufferOptions>(_ => { })); | ||
|
||
return builder.AddHttpRequestBufferProvider(); | ||
} | ||
|
||
/// <summary> | ||
/// Adds HTTP request buffer provider to the logging infrastructure. | ||
/// </summary> | ||
/// <param name="builder">The <see cref="ILoggingBuilder" />.</param> | ||
/// <returns>The <see cref="ILoggingBuilder"/> so that additional calls can be chained.</returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception> | ||
public static ILoggingBuilder AddHttpRequestBufferProvider(this ILoggingBuilder builder) | ||
{ | ||
_ = Throw.IfNull(builder); | ||
|
||
builder.Services.TryAddScoped<HttpRequestBuffer>(); | ||
builder.Services.TryAddScoped<ILoggingBuffer>(sp => sp.GetRequiredService<HttpRequestBuffer>()); | ||
builder.Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | ||
builder.Services.TryAddActivatedSingleton<ILoggingBufferProvider, HttpRequestBufferProvider>(); | ||
|
||
return builder.AddGlobalBufferProvider(); | ||
} | ||
|
||
/// <summary> | ||
/// Configures <see cref="HttpRequestBufferOptions" /> from an instance of <see cref="IConfiguration" />. | ||
/// </summary> | ||
/// <param name="builder">The <see cref="ILoggingBuilder" />.</param> | ||
/// <param name="configuration">The <see cref="IConfiguration" /> to add.</param> | ||
/// <returns>The value of <paramref name="builder"/>.</returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception> | ||
internal static ILoggingBuilder AddHttpRequestBufferConfiguration(this ILoggingBuilder builder, IConfiguration configuration) | ||
{ | ||
_ = Throw.IfNull(builder); | ||
|
||
_ = builder.Services.AddSingleton<IConfigureOptions<HttpRequestBufferOptions>>(new HttpRequestBufferConfigureOptions(configuration)); | ||
|
||
return builder; | ||
} | ||
} | ||
#endif |
47 changes: 47 additions & 0 deletions
47
...braries/Microsoft.AspNetCore.Diagnostics.Middleware/Buffering/HttpRequestBufferOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
#if NET9_0_OR_GREATER | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Shared.DiagnosticIds; | ||
|
||
namespace Microsoft.AspNetCore.Diagnostics.Logging; | ||
|
||
/// <summary> | ||
/// The options for LoggerBuffer. | ||
/// </summary> | ||
[Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)] | ||
public class HttpRequestBufferOptions | ||
{ | ||
/// <summary> | ||
/// Gets or sets the time to suspend the buffer after flushing. | ||
/// </summary> | ||
/// <remarks> | ||
/// Use this to temporarily suspend buffering after a flush, e.g. in case of an incident you may want all logs to be emitted immediately, | ||
/// so the buffering will be suspended for the <see paramref="SuspendAfterFlushDuration"/> time. | ||
/// </remarks> | ||
public TimeSpan SuspendAfterFlushDuration { get; set; } = TimeSpan.FromSeconds(30); | ||
|
||
/// <summary> | ||
/// Gets or sets the size of the buffer for a request. | ||
/// </summary> | ||
public int PerRequestCapacity { get; set; } = 1_000; | ||
|
||
/// <summary> | ||
/// Gets or sets the size of the global buffer which applies to non-request logs only. | ||
/// </summary> | ||
public int GlobalCapacity { get; set; } = 1_000_000; | ||
|
||
#pragma warning disable CA1002 // Do not expose generic lists - List is necessary to be able to call .AddRange() | ||
#pragma warning disable CA2227 // Collection properties should be read only - setter is necessary for options pattern | ||
/// <summary> | ||
/// Gets or sets the collection of <see cref="BufferFilterRule"/> used for filtering log messages for the purpose of further buffering. | ||
/// </summary> | ||
public List<BufferFilterRule> Rules { get; set; } = []; | ||
#pragma warning restore CA2227 // Collection properties should be read only | ||
#pragma warning restore CA1002 // Do not expose generic lists | ||
} | ||
#endif |
30 changes: 30 additions & 0 deletions
30
...raries/Microsoft.AspNetCore.Diagnostics.Middleware/Buffering/HttpRequestBufferProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
#if NET9_0_OR_GREATER | ||
using System.Collections.Concurrent; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.AspNetCore.Diagnostics.Logging; | ||
|
||
internal sealed class HttpRequestBufferProvider : ILoggingBufferProvider | ||
{ | ||
private readonly GlobalBufferProvider _globalBufferProvider; | ||
private readonly IHttpContextAccessor _accessor; | ||
private readonly ConcurrentDictionary<string, HttpRequestBuffer> _requestBuffers = new(); | ||
|
||
public HttpRequestBufferProvider(GlobalBufferProvider globalBufferProvider, IHttpContextAccessor accessor) | ||
{ | ||
_globalBufferProvider = globalBufferProvider; | ||
_accessor = accessor; | ||
} | ||
|
||
public ILoggingBuffer CurrentBuffer => _accessor.HttpContext is null | ||
? _globalBufferProvider.CurrentBuffer | ||
: _requestBuffers.GetOrAdd(_accessor.HttpContext.TraceIdentifier, _accessor.HttpContext.RequestServices.GetRequiredService<HttpRequestBuffer>()); | ||
|
||
// TO DO: Dispose request buffer when the respective HttpContext is disposed | ||
} | ||
#endif |
38 changes: 38 additions & 0 deletions
38
...ies/Microsoft.AspNetCore.Diagnostics.Middleware/Buffering/HttpRequestBufferedLogRecord.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
#if NET9_0_OR_GREATER | ||
using System; | ||
using System.Collections.Generic; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Logging.Abstractions; | ||
|
||
namespace Microsoft.AspNetCore.Diagnostics.Logging; | ||
|
||
internal sealed class HttpRequestBufferedLogRecord : BufferedLogRecord | ||
{ | ||
public HttpRequestBufferedLogRecord( | ||
LogLevel logLevel, | ||
EventId eventId, | ||
IReadOnlyList<KeyValuePair<string, object?>> state, | ||
Exception? exception, | ||
string? formatter) | ||
{ | ||
LogLevel = logLevel; | ||
EventId = eventId; | ||
Attributes = state; | ||
Exception = exception?.ToString(); // wtf?? | ||
FormattedMessage = formatter; | ||
} | ||
|
||
public override IReadOnlyList<KeyValuePair<string, object?>> Attributes { get; } | ||
public override string? FormattedMessage { get; } | ||
public override string? Exception { get; } | ||
|
||
public override DateTimeOffset Timestamp { get; } | ||
|
||
public override LogLevel LogLevel { get; } | ||
|
||
public override EventId EventId { get; } | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.