efcore icon indicating copy to clipboard operation
efcore copied to clipboard

Ability to change minimum log level per database context

Open poke opened this issue 2 years ago • 5 comments

Hey, is it possible to change the minimum log level that is being used when EF Core logs per database context?

I have the following situation: Our application has two database contexts. A “normal“ one, and one that is very busy and makes a lot of requests. Because of that, the latter database context creates a lot of logging noise that completely overshadows the other database context (and the rest of the application).

Is there a way to configure the logging per database context in some way? There are the two methods UseLoggerFactory() and LogTo() that allow me to configure things as part of the database context options but I am not sure how to use these in a good way that allows me to reuse the existing logging infrastructure while changing the minimum logging level.

What I considered is creating a custom LoggerFactory/Logger implementation that wraps around the standard logging infrastructure and just skips everything below a certain level. Something like this (not yet tested):

services.AddDbContext<BusyContext>((sp, options) => options
    .UseSqlServer(_configuration.GetConnectionString("Busy"))
    .UseLoggerFactory(new WrappingLoggerFactory(sp.GetService<ILoggerFactory>(), LogLevel.Warning)));
public class WrappingLoggerFactory : ILoggerFactory
{
    private readonly ILoggerFactory _parentFactory;
    private readonly LogLevel _minimumLevel;

    public WrappingLoggerFactory(ILoggerFactory parentFactory, LogLevel minimumLevel)
    {
        _parentFactory = parentFactory;
        _minimumLevel = minimumLevel;
    }
    public void AddProvider(ILoggerProvider provider)
        => throw new NotImplementedException();
    public ILogger CreateLogger(string categoryName)
        => new WrappingLogger(_parentFactory.CreateLogger(categoryName), _minimumLevel);
    public void Dispose()
    { }
}

public class WrappingLogger : ILogger
{
    private readonly ILogger _parentLogger;
    private readonly LogLevel _minimumLevel;

    public WrappingLogger(ILogger parentLogger, LogLevel minimumLevel)
    {
        _parentLogger = parentLogger;
        _minimumLevel = minimumLevel;
    }
    public IDisposable BeginScope<TState>(TState state)
        => _parentLogger.BeginScope(state);
    public bool IsEnabled(LogLevel logLevel)
        => logLevel >= _minimumLevel && _parentLogger.IsEnabled(logLevel);
    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (logLevel >= _minimumLevel)
            _parentLogger.Log(logLevel, eventId, state, exception, formatter);
    }
}

This will probably work, in one way or another, but I am wondering if there was an easier way to do this. In particular the LogTo() method seems to be a bit more flexible but I am not entirely sure if I can set it up in a way that allows me to reuse the existing logging infrastructure for this.

Do you have any suggestions on how to approach this issue, without configuring both database contexts to be equally silent (using global category filters)?

poke avatar Sep 20 '22 09:09 poke

Hey, is it possible to change the minimum log level that is being used when EF Core logs per database context?

When using Microsoft.Extensions.Logging, EF logs events with a given level. It is up to the logger to decide whether or not to display those events. This is how Microsoft.Extensions.Logging is designed. So EF doesn't have any "minimum log level" when using this mechanism.

That being said, the level that EF uses to log an event can be configured. See https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/extensions-logging?tabs=v3#configuration-for-specific-messages

Simple logging does let you set the level, since it, by-design, doesn't integrate with a logging framework that is designed to do this externally. See https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/simple-logging. But it's not designed to be used with Microsoft.Extensions.Logging.

ajcvickers avatar Sep 21 '22 08:09 ajcvickers

Another idea may be to have two DbContext types - one extending the other, but having the same model (only one would be used with Migrations). This would allow you to cleanly split the "noisy" context from the other one.

roji avatar Sep 21 '22 09:09 roji

Thanks for your quick responses!

@roji I do have two separate database contexts for two different database connections in the same project. I basically want the one database context to be mostly silent (warnings and errors should go through), while the other should be “normal” and follow whatever log level configuration is specified by the logging system.

So yeah, the contexts are already completely separated, I am just looking for a nice solution now to tell that one database context to be silent 😊

@ajcvickers Ah, I wasn’t aware that ConfigureWarnings was also able to configure normal events. I might be able to use that then. Though I guess I would have to reconfigure pretty much all events then which might result in a very long and unstable configuration. I was hoping that there was a way to simply say “ignore all events below those configured as Warning“.

I actually was able to verify my attempt above, using a wrapping logger factory, to give the second database context special loggers that simply overwrite the globally configured minimum log level. So this is working for me now and I consider it an acceptable workaround for my rather unusual request.

I’ll leave the issue open for another day or two in case you have any useful clues for me, but will close it otherwise since I guess that there might not be a “better” way to approach this. – Thanks again! 😊

poke avatar Sep 21 '22 11:09 poke

@poke I was proposing you have two DbContext types, at which point you should be able to pass two entirely different ILoggerFactories, each configured with a different minimum log level.

roji avatar Sep 21 '22 15:09 roji

you should be able to pass two entirely different ILoggerFactories

Ah, okay, so you would suggest having separate logger factories for both, which then could be configured individually. Okay, that’s similar to what I am doing with my WrappingLoggerFactory then:

services.AddDbContext<MainContext>((sp, options) => options
    .UseSqlServer(_configuration.GetConnectionString("Main")));

services.AddDbContext<BusyContext>((sp, options) => options
    .UseSqlServer(_configuration.GetConnectionString("Busy"))
    .UseLoggerFactory(new WrappingLoggerFactory(sp.GetService<ILoggerFactory>(), LogLevel.Warning)));

So I have two database contexts (MainContext and BusyContext). The main one uses the default logger factory, and the other one uses a different logger factory that enforces its own configuration. My wrapping factory just helps me that I don’t need to set up a completely new and independent logger factory.

poke avatar Sep 21 '22 15:09 poke