Search code examples
c#windowslogging

Reading Windows Logs efficiently and fast


What I'm trying to accomplish is a C# application that will read logs from the Windows Event Logs and store them somewhere else. This has to be fast, since some of the devices where it will be installed generate a high amount of logs/s.

I have tried three approaches so far:

Local WMI: it didn't work good, there are too many errors and exceptions caused by the size of the collections that need to be loaded. EventLogReader: I though this was the perfect solution, since it allows you to query the event log however you like by using XPath expressions. The problem is that when you want to get the content of the message for each log (by calling FormatDescription()) takes way too much time for long collections. E.g: I can read 12k logs in 0.11s if I just go over them. If I add a line to store the message for each log, it takes nearly 6 minutes to complete exactly the same operation, which is totally crazy for such a low number of logs. I don't know if there's any kind of optimization that might be done to EventLogReader in order to get the message faster, I couldn't find anything either on MS documentation nor on the Internet.

I also found that you can read the log entries by using a class called EventLog. However, this technology does not allow you to enter any kind of filters so you basically have to load the entire list of logs to memory and then filter it out according to your needs. Here's an example:

EventLog eventLog = EventLog.GetEventLogs().FirstOrDefault(el => el.Log.Equals("Security", StringComparison.OrdinalIgnoreCase));
var newEntries = (from entry in eventLog.Entries.OfType()
orderby entry.TimeWritten ascending
where entry.TimeWritten > takefrom
select entry);

Despite of being faster in terms of getting the message, the use of memory might be high and I don't want to cause any issues on the devices where this solution will get deployed.

Can anybody help me with this? I cannot find any workarounds or approaches to achieve something like this.

Thank you!.


Solution

  • We discussed a bit about reading the existing logs in the comments, can access the Security-tagged logs by accessing:

     var eventLog = new EventLog("Security");
     for (int i = 0; i < eventLog.Entries.Count; i++)
     {
          Console.WriteLine($"{eventLog.Entries[i].Message}");
     }
    

    This might not be the cleanest (performance-wise) way of doing it, but I doubt any other will be faster, as you yourself have already found out by trying out different techniques. A small edit duo to Alois post: EventLogReader is not faster out of the box than EventLog, especially when using the for-loop mechanism showed in the code block above, I think EventLog is faster -- it only accesses the entries inside the loop using their index, the Entries collection is just a reference, whereas while using the EventLogReader, it will perform a query first and loop through that result, which should be slower. As commented on Alois's post: if you don't need to use the query option, just use the EventLog variant. If you do need querying, use the EventLogReader as is can query on a lower level than you could while using EventLog (only LINQ queries, which is slower ofcourse than querying in while executing the look-up).

    To prevent you from having this hassle again in the future, and because you said you are running a service, I'd use the EntryWritten event of the EventLog class:

        var eventLog = new EventLog("Security")
        {
            EnableRaisingEvents = true
        };
        eventLog.EntryWritten += EventLog_EntryWritten;
    
        // .. read existing logs or do other work ..
    
        private static void EventLog_EntryWritten(object sender, EntryWrittenEventArgs e)
        {
            Console.WriteLine($"received new entry: {e.Entry.Message}");
        }
    

    Note that you must set the EnableRaisingEvents to true in order for the event to fire whenever a new entry is logged. It'll also be a good practice (also, performance-wise) to start a (for example) Task, so that the system won't lock itself while queuing up the calls to your event.

    This approach works fine if you want to retrieve all newly created events, if you want to retrieve newly created events but use a query (filter) for these events, you can check out the EventLogWatcher class, but in your case, when there are no constraints, I'd just use the EntryWritten event because you don't need filters and for plain old simplicity.