Context: I'm trying to take advantage of MEL logging filters. To do this, I implement multiple output-specific ILoggerProvider instances, each of which creates its own Serilog logger with a particular sink.
Unfortunately, the ILogger.BeginScope method leads to memory leak.
Steps to reproduce:
Run the following code snippet and collect heap dump or use some another tool to analyze heap:
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using SerilogLogger = Serilog.ILogger;
var melFactory = LoggerFactory.Create(builder =>
{
builder.AddProvider(new OneProvider());
builder.AddProvider(new AnotherProvider());
});
var logger = melFactory.CreateLogger("Test");
while (true)
{
using var scope = logger.BeginScope("Some property");
logger.LogInformation("Hello world!");
await Task.Delay(100);
}
public class OneProvider : LoggerProviderBase
{
public OneProvider()
:base(CreateLogger())
{
}
private static SerilogLogger CreateLogger() =>
new LoggerConfiguration().WriteTo.Console().CreateLogger();
}
public class AnotherProvider : LoggerProviderBase
{
public AnotherProvider()
: base(CreateLogger())
{
}
private static SerilogLogger CreateLogger() =>
new LoggerConfiguration().WriteTo.Console().CreateLogger();
}
public class LoggerProviderBase : ILoggerProvider
{
private readonly SerilogLoggerProvider innerProvider;
public LoggerProviderBase(SerilogLogger logger)
{
this.innerProvider = new SerilogLoggerProvider(logger, dispose: true);
}
/// <inheritdoc />
public void Dispose() => this.innerProvider.Dispose();
/// <inheritdoc />
public ILogger CreateLogger(string categoryName) => this.innerProvider.CreateLogger(categoryName);
}
VS Memory analyze:
Root cause:
-
The Microsoft.Extensions.Logging.Logger object aggregates multiple ILogger instances retrieved from all registered ILoggerProvider instances (see the CreateLoggers method).
-
Logger.BeginScope calls BeginScope on the inner loggers in order.
-
Each inner logger's BeginScope execution triggers SerilogLoggerProvider.BeginScope, a new property is pushed into the LogContext, and the bookmark is retrieved.
-
Upon disposal, scopes are disposed of in order, and any incorrect bookmarks are reset.
Bookmark after first logger's BeginScope:
After second:
As it stands, the second bookmark will be restored after the first (MEL preserves order), which will result in the incorrect bookmark being restored.
Proposed solution: Implement logging scopes for each SerilogLoggerProvider that do not rely on the LogContext API. For backward compatibility, continue traversing the LogContext linked list.
I would like to contribute a fix if the proposed solution is approved.
CC @nblumhardt
Context: I'm trying to take advantage of MEL logging filters. To do this, I implement multiple output-specific
ILoggerProviderinstances, each of which creates its own Serilog logger with a particular sink.Unfortunately, the ILogger.BeginScope method leads to memory leak.
Steps to reproduce:
Run the following code snippet and collect heap dump or use some another tool to analyze heap:
VS Memory analyze:
Root cause:
The Microsoft.Extensions.Logging.Logger object aggregates multiple ILogger instances retrieved from all registered ILoggerProvider instances (see the CreateLoggers method).
Logger.BeginScope calls BeginScope on the inner loggers in order.
Each inner logger's BeginScope execution triggers SerilogLoggerProvider.BeginScope, a new property is pushed into the LogContext, and the bookmark is retrieved.
Upon disposal, scopes are disposed of in order, and any incorrect bookmarks are reset.
Bookmark after first logger's BeginScope:
After second:
As it stands, the second bookmark will be restored after the first (MEL preserves order), which will result in the incorrect bookmark being restored.
Proposed solution: Implement logging scopes for each SerilogLoggerProvider that do not rely on the LogContext API. For backward compatibility, continue traversing the LogContext linked list.
I would like to contribute a fix if the proposed solution is approved.
CC @nblumhardt