5

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?

2
  • 1
    If you are configuring the logging with appsettings.{Environment}.json files, you can get the current logging level from the appsettings.{Environment}.json file, the add it to your condition. Reference: Configure logging. Commented Aug 17, 2021 at 6:48
  • Thanks @ZhiLv - it is a good idea and one I had considered -- although to make a non-cheating version of that I would have to cycle through all the permutations of the various entries if I wanted to make a general solution. And since presumably the .NET Logging extensions are already doing that, it seems a shame to duplicate that whole process.... Commented Aug 17, 2021 at 14:51

2 Answers 2

2
+50

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);
    }
}
Sign up to request clarification or add additional context in comments.

9 Comments

Thanks, based on your suggestion I have looked at this source code and others. It does seem to be quite simple what they are doing. But also very incomplete. I have seen some other examples, for instance codeproject.com/Articles/1556475/… that suggest a more complete solution, but I haven't figured out how to take these plain .net core examples and apply them to the asp.net core scenario.
Keep in mind that the framework already checks the log level configuration before forwarding the call to your logger implementation, so you don't need to concern yourself with that. You only need to implement logger-specific logic.
Thanks @Axel, but please see (as per initial question) I am aware of that. I only want to make that check if there is a significant or expensive operation to generate the actual message that would be logged. No need, as per my example, to serialize a complex object that would be logged if the logging is not to happen. My understanding is that this is the very purpose of this method being part of the ILogger interface....
I see... I overlooked that part. Please take a look at my latest edit.
I stumbled over this as well, with the same intention as @StephanG. My IsEnabled(...) implementation does just check if logLevel != LogLevel.None as I have copied from some tutorial. But debugging and putting a breakpoint in my implementation revealed, that this method isn't hit at all when I call logger.IsEnabled(LogLevel level) on an ILogger<T> instance. Instead, the result is as expected, true only if the actual log level for the namespace is >= the provided parameter. Does anyone now how this works?
|
0

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);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.