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 @@ -39,7 +39,10 @@ public static ILoggingBuilder AddPerIncomingRequestBuffer(this ILoggingBuilder b
_ = Throw.IfNull(configuration);

_ = builder.Services
.AddSingleton<IConfigureOptions<PerRequestLogBufferingOptions>>(new PerRequestLogBufferingConfigureOptions(configuration))
.AddSingleton<IConfigureOptions<PerRequestLogBufferingOptions>>(
new PerRequestLogBufferingConfigureOptions(configuration))
.AddSingleton<IOptionsChangeTokenSource<PerRequestLogBufferingOptions>>(
new ConfigurationChangeTokenSource<PerRequestLogBufferingOptions>(configuration))
.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsValidator>()
.Services.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsCustomValidator>();

Expand All @@ -64,8 +67,8 @@ public static ILoggingBuilder AddPerIncomingRequestBuffer(this ILoggingBuilder b
_ = Throw.IfNull(builder);
_ = Throw.IfNull(configure);

_ = builder.Services
.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsValidator>()
_ = builder
.Services.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsValidator>()
.Services.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsCustomValidator>()
.Configure(configure);

Expand All @@ -92,8 +95,8 @@ public static ILoggingBuilder AddPerIncomingRequestBuffer(this ILoggingBuilder b
{
_ = Throw.IfNull(builder);

_ = builder.Services
.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsValidator>()
_ = builder
.Services.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsValidator>()
.Services.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsCustomValidator>()
.Configure(options =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ namespace Microsoft.AspNetCore.Diagnostics.Buffering;

internal sealed class PerRequestLogBufferManager : PerRequestLogBuffer
{
internal readonly IOptionsMonitor<PerRequestLogBufferingOptions> Options;

private readonly GlobalLogBuffer _globalBuffer;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly LogBufferingFilterRuleSelector _ruleSelector;
private readonly IOptionsMonitor<PerRequestLogBufferingOptions> _options;

public PerRequestLogBufferManager(
GlobalLogBuffer globalBuffer,
Expand All @@ -26,7 +27,7 @@ public PerRequestLogBufferManager(
_globalBuffer = globalBuffer;
_httpContextAccessor = httpContextAccessor;
_ruleSelector = ruleSelector;
_options = options;
Options = options;
}

public override void Flush()
Expand All @@ -47,7 +48,7 @@ public override bool TryEnqueue<TState>(IBufferedLogger bufferedLogger, in LogEn
IncomingRequestLogBufferHolder? bufferHolder =
httpContext.RequestServices.GetService<IncomingRequestLogBufferHolder>();
IncomingRequestLogBuffer? buffer = bufferHolder?.GetOrAdd(category, _ =>
new IncomingRequestLogBuffer(bufferedLogger, category, _ruleSelector, _options));
new IncomingRequestLogBuffer(bufferedLogger, category, _ruleSelector, Options));

if (buffer is null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ namespace Microsoft.Extensions.Diagnostics.Buffering;

internal sealed class GlobalBuffer : IDisposable
{
internal LogBufferingFilterRule[] LastKnownGoodFilterRules;

private const int MaxBatchSize = 256;
private static readonly ObjectPool<List<DeserializedLogRecord>> _recordsToEmitListPool =
PoolFactory.CreateListPoolWithCapacity<DeserializedLogRecord>(MaxBatchSize);
Expand All @@ -34,7 +36,6 @@ internal sealed class GlobalBuffer : IDisposable

private DateTimeOffset _lastFlushTimestamp;
private int _activeBufferSize;
private LogBufferingFilterRule[] _lastKnownGoodFilterRules;

private volatile bool _disposed;

Expand All @@ -50,7 +51,7 @@ public GlobalBuffer(
_bufferedLogger = bufferedLogger;
_category = Throw.IfNullOrEmpty(category);
_ruleSelector = Throw.IfNull(ruleSelector);
_lastKnownGoodFilterRules = LogBufferingFilterRuleSelector.SelectByCategory(_options.CurrentValue.Rules.ToArray(), _category);
LastKnownGoodFilterRules = LogBufferingFilterRuleSelector.SelectByCategory(_options.CurrentValue.Rules.ToArray(), _category);
_optionsChangeTokenRegistration = options.OnChange(OnOptionsChanged);
}

Expand Down Expand Up @@ -83,7 +84,7 @@ public bool TryEnqueue<TState>(LogEntry<TState> logEntry)
$"Unsupported type of log state detected: {typeof(TState)}, expected IReadOnlyList<KeyValuePair<string, object?>>");
}

if (_ruleSelector.Select(_lastKnownGoodFilterRules, logEntry.LogLevel, logEntry.EventId, attributes) is null)
if (_ruleSelector.Select(LastKnownGoodFilterRules, logEntry.LogLevel, logEntry.EventId, attributes) is null)
{
// buffering is not enabled for this log entry,
// return false to indicate that the log entry should be logged normally.
Expand Down Expand Up @@ -162,11 +163,11 @@ private void OnOptionsChanged(GlobalLogBufferingOptions? updatedOptions)
{
if (updatedOptions is null)
{
_lastKnownGoodFilterRules = [];
LastKnownGoodFilterRules = [];
}
else
{
_lastKnownGoodFilterRules = LogBufferingFilterRuleSelector.SelectByCategory(updatedOptions.Rules.ToArray(), _category);
LastKnownGoodFilterRules = LogBufferingFilterRuleSelector.SelectByCategory(updatedOptions.Rules.ToArray(), _category);
}

_ruleSelector.InvalidateCache();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ public static ILoggingBuilder AddGlobalBuffer(this ILoggingBuilder builder, ICon
_ = builder
.Services.AddOptionsWithValidateOnStart<GlobalLogBufferingOptions, GlobalLogBufferingOptionsValidator>()
.Services.AddOptionsWithValidateOnStart<GlobalLogBufferingOptions, GlobalLogBufferingOptionsCustomValidator>()
.Services.AddSingleton<IConfigureOptions<GlobalLogBufferingOptions>>(new GlobalLogBufferingConfigureOptions(configuration));
.Services.AddSingleton<IConfigureOptions<GlobalLogBufferingOptions>>(
new GlobalLogBufferingConfigureOptions(configuration))
.AddSingleton<IOptionsChangeTokenSource<GlobalLogBufferingOptions>>(
new ConfigurationChangeTokenSource<GlobalLogBufferingOptions>(configuration));

return builder.AddGlobalBufferManager();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.Diagnostics.Buffering;

internal sealed class GlobalLogBufferManager : GlobalLogBuffer
{
private readonly ConcurrentDictionary<string, GlobalBuffer> _buffers = [];
internal readonly ConcurrentDictionary<string, GlobalBuffer> Buffers = [];
private readonly IOptionsMonitor<GlobalLogBufferingOptions> _options;
private readonly TimeProvider _timeProvider;
private readonly LogBufferingFilterRuleSelector _ruleSelector;
Expand All @@ -35,7 +35,7 @@ internal GlobalLogBufferManager(

public override void Flush()
{
foreach (GlobalBuffer buffer in _buffers.Values)
foreach (GlobalBuffer buffer in Buffers.Values)
{
buffer.Flush();
}
Expand All @@ -44,7 +44,7 @@ public override void Flush()
public override bool TryEnqueue<TState>(IBufferedLogger bufferedLogger, in LogEntry<TState> logEntry)
{
string category = logEntry.Category;
GlobalBuffer buffer = _buffers.GetOrAdd(category, _ => new GlobalBuffer(
GlobalBuffer buffer = Buffers.GetOrAdd(category, _ => new GlobalBuffer(
bufferedLogger,
category,
_ruleSelector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@
#if NET9_0_OR_GREATER
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.Buffering;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Buffering;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Test;
using Microsoft.Extensions.Options;
using Xunit;
using PerRequestLogBuffer = Microsoft.Extensions.Diagnostics.Buffering.PerRequestLogBuffer;

namespace Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Diagnostics.Logging.Test;

public class PerIncomingRequestLoggingBuilderExtensionsTests
{
Expand Down Expand Up @@ -82,5 +90,72 @@ public void WhenConfigurationActionProvided_RegistersInDI()
Assert.NotNull(options.CurrentValue);
Assert.Equivalent(expectedData, options.CurrentValue.Rules);
}

[Fact]
public async Task WhenConfigUpdated_PicksUpConfigChanges()
{
List<LogBufferingFilterRule> initialData =
[
new(categoryName: "Program.MyLogger", logLevel: LogLevel.Information, eventId: 1, eventName: "number one"),
new(logLevel : LogLevel.Information),
];
List<LogBufferingFilterRule> updatedData =
[
new(logLevel: LogLevel.Information),
];
string jsonConfig =
@"
{
""PerIncomingRequestLogBuffering"": {
""Rules"": [
{
""CategoryName"": ""Program.MyLogger"",
""LogLevel"": ""Information"",
""EventId"": 1,
""EventName"": ""number one"",
},
{
""LogLevel"": ""Information"",
},
]
}
}
";

using ConfigurationRoot config = TestConfiguration.Create(() => jsonConfig);
using IHost host = await FakeHost.CreateBuilder()
.ConfigureWebHost(builder => builder
.UseTestServer()
.ConfigureServices(x => x.AddRouting())
.ConfigureLogging(loggingBuilder => loggingBuilder
.AddPerIncomingRequestBuffer(config))
.Configure(app => app.UseRouting()))
.StartAsync();

IOptionsMonitor<PerRequestLogBufferingOptions>? options = host.Services.GetService<IOptionsMonitor<PerRequestLogBufferingOptions>>();
Assert.NotNull(options);
Assert.NotNull(options.CurrentValue);
Assert.Equivalent(initialData, options.CurrentValue.Rules);

jsonConfig =
@"
{
""PerIncomingRequestLogBuffering"": {
""Rules"": [
{
""LogLevel"": ""Information"",
},
]
}
}
";
config.Reload();

var bufferManager = host.Services.GetRequiredService<PerRequestLogBuffer>() as PerRequestLogBufferManager;
Assert.NotNull(bufferManager);
Assert.Equivalent(updatedData, bufferManager.Options.CurrentValue.Rules, strict: true);

await host.StopAsync();
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;

namespace Microsoft.Extensions.Logging.Test;

internal class TestConfiguration : JsonConfigurationProvider
{
private Func<string> _json;

public TestConfiguration(JsonConfigurationSource source, Func<string> json)
: base(source)
{
_json = json;
}

public static ConfigurationRoot Create(Func<string> getJson)
{
var provider = new TestConfiguration(new JsonConfigurationSource { Optional = true }, getJson);
return new ConfigurationRoot(new List<IConfigurationProvider> { provider });
}

public override void Load()
{
var stream = new MemoryStream();
using var writer = new StreamWriter(stream);
writer.Write(_json());
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
Load(stream);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Test;
using Microsoft.Extensions.Options;
using Xunit;

Expand Down Expand Up @@ -68,5 +69,79 @@ public void WithConfiguration_RegistersInDI()
Assert.Equal(TimeSpan.FromSeconds(30), options.CurrentValue.AutoFlushDuration); // value comes from default
Assert.Equivalent(expectedData, options.CurrentValue.Rules);
}

[Fact]
public void WhenConfigUpdated_PicksUpConfigChanges()
{
List<LogBufferingFilterRule> initialData =
[
new(categoryName: "Program.MyLogger", logLevel: LogLevel.Information, eventId: 1, eventName: "number one"),
new(logLevel : LogLevel.Information),
];
List<LogBufferingFilterRule> updatedData =
[
new(logLevel: LogLevel.Information),
];
string jsonConfig =
@"
{
""GlobalLogBuffering"": {
""Rules"": [
{
""CategoryName"": ""Program.MyLogger"",
""LogLevel"": ""Information"",
""EventId"": 1,
""EventName"": ""number one"",
},
{
""LogLevel"": ""Information"",
},
]
}
}
";

using ConfigurationRoot config = TestConfiguration.Create(() => jsonConfig);

using ExtendedLoggerTests.Provider provider = new ExtendedLoggerTests.Provider();
using ILoggerFactory factory = Utils.CreateLoggerFactory(
builder =>
{
builder.AddProvider(provider);
builder.AddGlobalBuffer(config);
});
ILogger logger = factory.CreateLogger("Program.MyLogger");
Utils.DisposingLoggerFactory dlf = (Utils.DisposingLoggerFactory)factory;
var bufferManager = dlf.ServiceProvider.GetRequiredService<GlobalLogBuffer>() as GlobalLogBufferManager;

IOptionsMonitor<GlobalLogBufferingOptions>? options = dlf.ServiceProvider.GetService<IOptionsMonitor<GlobalLogBufferingOptions>>();
Assert.NotNull(options);
Assert.NotNull(options.CurrentValue);
Assert.Equivalent(initialData, options.CurrentValue.Rules);

// this is just to trigger creating an internal buffer:
logger.LogInformation(new EventId(1, "number one"), null);

jsonConfig =
@"
{
""GlobalLogBuffering"": {
""Rules"": [
{
""LogLevel"": ""Information"",
},
]
}
}
";
config.Reload();

Assert.NotNull(bufferManager);
Assert.NotEmpty(bufferManager.Buffers);
foreach (GlobalBuffer buffer in bufferManager.Buffers.Values)
{
Assert.Equivalent(updatedData, buffer.LastKnownGoodFilterRules, strict: true);
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -1114,7 +1114,7 @@ private enum ThrowExceptionAt
IsEnabled
}

private sealed class Provider : ILoggerProvider
internal sealed class Provider : ILoggerProvider
{
public FakeLogger? Logger { get; private set; }

Expand Down
Loading