Dynamically set EventID when logging to Event Viewer using Serilog
Asked Answered
R

2

5

Currently, I have set up my logger to log to the event viewer like so:

 Log.Logger = new LoggerConfiguration()
               .MinimumLevel.Information()
               .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
               .Enrich.FromLogContext()
               .WriteTo.EventLog("MySource", "EventViewerArea")
               .CreateLogger();

When I log I use the following command:

_logger.LogWarning(logText);

It looks as though I can pass in the EventId to the LogWarning method, so I wanted to set it when running the LogWarning/LogInformation methods:

_logger.LogWarning(9876, logText);

But this does not override the Event ID in the entry in the Event Viewer. Any ideas on how I can dynamically set this EventId when logging? I need to have this dynamic and not set to one value when instantiating the logger.

Thanks in advance

Rna answered 24/1, 2020 at 14:32 Comment(0)
R
4

I was able to solve this with the following steps:

Add the following when instantiating the logger:

.WriteTo.EventLog("SourceInEventViewer", 
    "AreaInEventViewer", 
    formatProvider: new EventLogFormatProvider(),
    eventIdProvider: new EventIdProvider(),
    manageEventSource: true,
    restrictedToMinimumLevel: LogEventLevel.Information)

Add the following class to the project

using Newtonsoft.Json.Linq;
using Serilog.Events;
using Serilog.Sinks.EventLog;
using System;
using System.Linq;

namespace IndependentFile.Extensions
{
    public class LoggerSetupExtensions
    {
        public class EventLogFormatProvider : IFormatProvider, ICustomFormatter
        {
            public object GetFormat(Type formatType)
            {
                return formatType == typeof(ICustomFormatter) ? this : null;
            }

            public string Format(string format, object arg, IFormatProvider formatProvider)
            {
                return arg.ToString();
            }
        }       

        public class EventIdProvider : IEventIdProvider
        {
            public ushort ComputeEventId(LogEvent logEvent)
            {
                var eventTypeProp = logEvent.Properties.FirstOrDefault(prop => prop.Key == "EventId");

                if (eventTypeProp.Value == null)
                {
                    return (ushort)LogValuesEnum.Unknown;
                }
                try
                {
                    var val = eventTypeProp.Value;
                    string eventType = eventTypeProp.Value.ToString();

                    //this is not the right way to parse the logEventPropertyValue
                    var parseEventType = JObject.Parse(eventType);

                    var eventIdInt = parseEventType["Id"].ToString();

                    if (eventType == null) return (int)LogValuesEnum.Unknown;

                    var tryParseEventId = Enum.TryParse<LogValuesEnum>(eventIdInt, ignoreCase: true, out var res);
                    if (tryParseEventId)
                    {
                        return (ushort)res;
                    }

                    return (ushort)LogValuesEnum.Unknown;
                }
                catch(Exception exc)
                {
                    return (ushort)LogValuesEnum.Unknown;
                }

            }
        }
    }
}

Now you can use the log and pass your event id to that:

_logger.LogInformation(LogValuesEnum.MyEnumVal, "My log message");
Rna answered 11/2, 2020 at 10:7 Comment(2)
What is the source of [ LogValuesEnum.MyEnumVal ]?.. is there a way to simply pass in an ushort for the Windows Event ID?Gelatinize
I thought I had figured it out, but I was incorrect... could you pls add your source for LogValuesEnum.MyEnumValGelatinize
S
2

If you check the source code of the event log sink you will see that it uses the EventIdHashProvider to generate unique ids by hashing the message of the event.

What you can do is to provide your implementation of the IEventIdProvider interface when configuring the sink, like this (this was added eventIdProvider: new CustomEventIdProvider()):

Log.Logger = new LoggerConfiguration()
               .MinimumLevel.Information()
               .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
               .Enrich.FromLogContext()
               .WriteTo.EventLog("MySource", "EventViewerArea", eventIdProvider: new CustomEventIdProvider())
               .CreateLogger();

The .LogWarnning() is comming from Microsoft.Extensions.Logging. It actually accepts EventId strcut which has implicit cast from int.
But as you can see this id is never used in the sink. The EventId is used to easily track the identity of the event, but i am not sure if any of the sinks is reusing this id ... the EventLog sink does not.

Stoecker answered 24/1, 2020 at 15:55 Comment(1)
no way, that's annoying, surely there's a work aroundRna

© 2022 - 2024 — McMap. All rights reserved.