Stephan G
Stephan G

Reputation: 3467

How to properly implement ILogger.IsEnabled() in custom logger in ASP.NET Core MVC

I am (as something of a novice) implementing my own custom logger for use in ASP.NET Core MVC apps. I have this logger working functionally in every regard. But I cheated a little so far, namely I implemented the ILogger.IsEnabled method as follows:

public bool IsEnabled(LogLevel logLevel)
{
    return true;
}

Functionally, this works fine, since the framework ensures that the Log() method is only invoked if the log level is at or higher than the one specified. So the correct "things" are being logged and the lower-level "things" are not being logged as expected.

However, I also want to support the following kind of situation in my code, where _logger is typed as ILogger and is properly injected in my controller:

if (_logger.IsEnabled(LogLevel.Debug))
{
    _logger.LogDebug("This is an expensive message to generate: " +
        JsonConvert.SerializeObject(request));
}

To make this effective, my IsEnabled() method should be able to know what the log level IS for the instance of the logger that was created with my LoggerProvider, but I don't know how to get that information directly, or how to pass it properly to the injected instance of the the logger I am working with.

Complex examples and tutorials I have been able to find seem to be constructed in every case for console app types, not network app types, and so far I have been unsuccessful at figuring out how to do this through the templated Startup class in ASP.NET MVC.

What is the simplest and most effective way to stop cheating at my custom IsEnabled() method in order to avoid the unnecessary serialization (in my example) if none of the registered loggers in the injected instance are handling the Debug log level? Or do you have a favorite example or tutorial in the ASP.NET core setting you can point me to?

Upvotes: 5

Views: 3282

Answers (2)

Ruslan K.
Ruslan K.

Reputation: 1981

You don't need to implement filtering logic in your logger's IsEnabled method because this logic is implemented in the wrapper of each registered logger. The wrapper type is Microsoft.Extensions.Logging.MessageLogger, and it also contains the IsEnabled method. Here's a piece of code from the Microsoft.Extensions.Logging.Logger.Log method:

for (int i = 0; i < loggers.Length; i++)
{
    ref readonly MessageLogger loggerInfo = ref loggers[i];
    if (!loggerInfo.IsEnabled(logLevel))
    {
        continue;
    }

    LoggerLog(logLevel, eventId, loggerInfo.Logger, exception, formatter, ref exceptions, state);
}

Upvotes: 0

Axel Zarate
Axel Zarate

Reputation: 566

You can take a look at built-in loggers source code and see how they implement it.

In short, they only check that logLevel != LogLevel.None, but depending on the logger logic, you might also want to check some other configuration. For example, DebugLogger logger also checks the Debugger.IsAttached property and EventLogLogger checks the EventLogSettings.Filter (supplied via constructor).

Update

To make this effective, my IsEnabled() method should be able to know what the log level IS for the instance of the logger that was created with my LoggerProvider, but I don't know how to get that information directly, or how to pass it properly to the injected instance of the the logger I am working with.

You can create an implementation of ILoggerProvider which in turn can make use of dependency injection to get the configuration you want. If you want to use the options pattern to configure it, you must do something along the lines of:

public class MyLoggerProvider : ILoggerProvider
{
    private readonly IOptions<MyLoggerOptions> _options;

    public MyLoggerProvider(IOptions<MyLoggerOptions> options)
    {
        _options = options;
    }

    public ILogger CreateLogger(string name)
    {
        return new MyLogger(name, _options.Value);
    }
}

And optionally add an extension method to make registration easier:

public static class MyLoggerExtensions
{
    public static ILoggingBuilder AddMyLogger(this ILoggingBuilder builder, Action<MyLoggerOptions> configure)
    {
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, MyLoggerProvider>());
        LoggerProviderOptions.RegisterProviderOptions<MyLoggerOptions, MyLoggerProvider>(builder.Services);
        builder.Services.Configure(configure);
    }
}

Upvotes: 2

Related Questions