.net.net-coreloggingevent-viewer

.NET logging level not mapping to event viewer event level


When using the Microsoft.Extensions.Logging.EventLog package for logging, the level isn't carrying over for Trace & Debug to event viewer; they are coming over as Information.

Is it me or that's just how the package works? I would think they would have mapped Debug or Trace level events to Verbose but that doesn't appear to be the case.

logger.LogTrace("trace");
logger.LogDebug("debug");
logger.LogInformation("info");

screenshot of trace level as info leve in event viewer

log levels in event viewer: screenshot of log levels in event viewer


Solution

  • The source code of the GetEventLogEntryType method shows that Trace and Debug are intentionally mapped to Information:

    private static EventLogEntryType GetEventLogEntryType(LogLevel level)
    {
        switch (level)
        {
            case LogLevel.Information:
            case LogLevel.Debug:
            case LogLevel.Trace:
                return EventLogEntryType.Information;
            case LogLevel.Warning:
                return EventLogEntryType.Warning;
            case LogLevel.Critical:
            case LogLevel.Error:
                return EventLogEntryType.Error;
            default:
                return EventLogEntryType.Information;
        }
    }
    

    Why

    This makes sense because the Windows Event Log is meant to be a

    In that respect it's similar to Kafka, not Elastic. In modern monitoring terms, the Windows Event Log is closer to OpenTelemetry's Tracing than logging.

    The emphasis in speed of writing with minimal impact. The Event Log is an OS resource and abusing it will affect all its users - which is everything from the driver level upwards. An event entry is supposed to contain only the Event ID and a few parameters, so it takes as little space as possible. Definitely not long text descriptions. Emitting Debug events means emitting more granular events, not bigger ones.

    The expectation is that enabling Debug logging for a service will not affect the rest of the system. Instead of emitting eg 1 24-byte event every second, a service could emit 20 32-byte events.

    The message itself is rendered by tools like Event Viewer using templates provided as resources in event libraries. It's even possible to have multilingual templates.

    This is similar to semantic logging, where only the event ID and parameters are actually part of the log message. The event itself is displayed as needed using a message template.

    On the other hand, the .NET Trace and Debug levels are used to emit large amounts of very detailed text information. Writing such information to the OS's Event Log

    It's traditional

    As for why that mapping was added, it was probably for consistency with other logging libraries at the time. The source is on Github and View Blame helps track the code across versions and repos.

    The mapping already existed in the first commit of EventLogLogger back in 2015:

    private EventLogEntryType GetEventLogEntryType(LogLevel level)
    {
        switch (level)
        {
            case LogLevel.Information:
            case LogLevel.Debug:
            case LogLevel.Verbose:
                return EventLogEntryType.Information;
            case LogLevel.Warning:
                return EventLogEntryType.Warning;
            case LogLevel.Critical:
            case LogLevel.Error:
                return EventLogEntryType.Error;
            default:
                return EventLogEntryType.Information;
        }
    }
    

    At the time other logging frameworks did the same, eg Serilog's EventLogSink :

    switch (logEvent.Level)
    {
        case LogEventLevel.Debug:
        case LogEventLevel.Information:
        case LogEventLevel.Verbose:
            type = EventLogEntryType.Information;
            break;
    
        case LogEventLevel.Error:
        case LogEventLevel.Fatal:
            type = EventLogEntryType.Error;
            break;
    
        case LogEventLevel.Warning:
            type = EventLogEntryType.Warning;
            break;
    
        default:
            SelfLog.WriteLine("Unexpected logging level, writing to EventLog as Information");
            type = EventLogEntryType.Information;
            break;
    }