Reading Windows Logs efficiently and fast
up vote
4
down vote
favorite
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!.
c# windows logging
|
show 4 more comments
up vote
4
down vote
favorite
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!.
c# windows logging
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.
Meaning the user will use the application while its moving logs? I'd just use a Task to handle the work..
– Jevgeni Geurtsen
Nov 22 at 12:20
No, it just means that I don't want to get a SystemOutOfMemoryException or something like that on the service because of the high memory usage. I just want to know if there's some more efficient workaround than the ones I could find so far.
– Lucas Álvarez Lacasa
Nov 22 at 12:29
This will basically run in a Windows Service, what I don't know about the third approach (EventLog) is if there's a way to filter the log collection without having to read everything in memory and then applying a LINQ query.
– Lucas Álvarez Lacasa
Nov 22 at 12:31
I think you want to use the constructor ofEventLog
:new EventLog("Security")
. Also, if it runs as a service, I would copy the existing Logs during the first run (on background), then hook theEntryWrittenEventHandler
withinEventLog
in your service to handle newly created logs.
– Jevgeni Geurtsen
Nov 22 at 12:40
So, you are basically telling me to subscribe to the EntryWrittenEventHandler for each one of the logfiles from where I need to read? And then every time this event fires I will have the newly created log and I can forward it to whatever I want to. Is that correct?
– Lucas Álvarez Lacasa
Nov 22 at 12:58
|
show 4 more comments
up vote
4
down vote
favorite
up vote
4
down vote
favorite
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!.
c# windows logging
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!.
c# windows logging
c# windows logging
asked Nov 22 at 11:54
Lucas Álvarez Lacasa
234
234
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.
Meaning the user will use the application while its moving logs? I'd just use a Task to handle the work..
– Jevgeni Geurtsen
Nov 22 at 12:20
No, it just means that I don't want to get a SystemOutOfMemoryException or something like that on the service because of the high memory usage. I just want to know if there's some more efficient workaround than the ones I could find so far.
– Lucas Álvarez Lacasa
Nov 22 at 12:29
This will basically run in a Windows Service, what I don't know about the third approach (EventLog) is if there's a way to filter the log collection without having to read everything in memory and then applying a LINQ query.
– Lucas Álvarez Lacasa
Nov 22 at 12:31
I think you want to use the constructor ofEventLog
:new EventLog("Security")
. Also, if it runs as a service, I would copy the existing Logs during the first run (on background), then hook theEntryWrittenEventHandler
withinEventLog
in your service to handle newly created logs.
– Jevgeni Geurtsen
Nov 22 at 12:40
So, you are basically telling me to subscribe to the EntryWrittenEventHandler for each one of the logfiles from where I need to read? And then every time this event fires I will have the newly created log and I can forward it to whatever I want to. Is that correct?
– Lucas Álvarez Lacasa
Nov 22 at 12:58
|
show 4 more comments
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.
Meaning the user will use the application while its moving logs? I'd just use a Task to handle the work..
– Jevgeni Geurtsen
Nov 22 at 12:20
No, it just means that I don't want to get a SystemOutOfMemoryException or something like that on the service because of the high memory usage. I just want to know if there's some more efficient workaround than the ones I could find so far.
– Lucas Álvarez Lacasa
Nov 22 at 12:29
This will basically run in a Windows Service, what I don't know about the third approach (EventLog) is if there's a way to filter the log collection without having to read everything in memory and then applying a LINQ query.
– Lucas Álvarez Lacasa
Nov 22 at 12:31
I think you want to use the constructor ofEventLog
:new EventLog("Security")
. Also, if it runs as a service, I would copy the existing Logs during the first run (on background), then hook theEntryWrittenEventHandler
withinEventLog
in your service to handle newly created logs.
– Jevgeni Geurtsen
Nov 22 at 12:40
So, you are basically telling me to subscribe to the EntryWrittenEventHandler for each one of the logfiles from where I need to read? And then every time this event fires I will have the newly created log and I can forward it to whatever I want to. Is that correct?
– Lucas Álvarez Lacasa
Nov 22 at 12:58
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.
Meaning the user will use the application while its moving logs? I'd just use a Task to handle the work..– Jevgeni Geurtsen
Nov 22 at 12:20
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.
Meaning the user will use the application while its moving logs? I'd just use a Task to handle the work..– Jevgeni Geurtsen
Nov 22 at 12:20
No, it just means that I don't want to get a SystemOutOfMemoryException or something like that on the service because of the high memory usage. I just want to know if there's some more efficient workaround than the ones I could find so far.
– Lucas Álvarez Lacasa
Nov 22 at 12:29
No, it just means that I don't want to get a SystemOutOfMemoryException or something like that on the service because of the high memory usage. I just want to know if there's some more efficient workaround than the ones I could find so far.
– Lucas Álvarez Lacasa
Nov 22 at 12:29
This will basically run in a Windows Service, what I don't know about the third approach (EventLog) is if there's a way to filter the log collection without having to read everything in memory and then applying a LINQ query.
– Lucas Álvarez Lacasa
Nov 22 at 12:31
This will basically run in a Windows Service, what I don't know about the third approach (EventLog) is if there's a way to filter the log collection without having to read everything in memory and then applying a LINQ query.
– Lucas Álvarez Lacasa
Nov 22 at 12:31
I think you want to use the constructor of
EventLog
: new EventLog("Security")
. Also, if it runs as a service, I would copy the existing Logs during the first run (on background), then hook the EntryWrittenEventHandler
within EventLog
in your service to handle newly created logs.– Jevgeni Geurtsen
Nov 22 at 12:40
I think you want to use the constructor of
EventLog
: new EventLog("Security")
. Also, if it runs as a service, I would copy the existing Logs during the first run (on background), then hook the EntryWrittenEventHandler
within EventLog
in your service to handle newly created logs.– Jevgeni Geurtsen
Nov 22 at 12:40
So, you are basically telling me to subscribe to the EntryWrittenEventHandler for each one of the logfiles from where I need to read? And then every time this event fires I will have the newly created log and I can forward it to whatever I want to. Is that correct?
– Lucas Álvarez Lacasa
Nov 22 at 12:58
So, you are basically telling me to subscribe to the EntryWrittenEventHandler for each one of the logfiles from where I need to read? And then every time this event fires I will have the newly created log and I can forward it to whatever I want to. Is that correct?
– Lucas Álvarez Lacasa
Nov 22 at 12:58
|
show 4 more comments
2 Answers
2
active
oldest
votes
up vote
1
down vote
accepted
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.
What if the service where this tasks are running stops? Is there a way to re enable this callback from a certain point? Or I will have to start again from that point and potentially lose logs in the middle?.
– Lucas Álvarez Lacasa
Nov 22 at 18:14
What I'm trying to say is, suppose that I'm reading logs with the callback. Suddenly my service stops. If I start over again and register the callback, I will lose the logs that were generated in the middle. Am I right?
– Lucas Álvarez Lacasa
Nov 22 at 18:15
Yes, those logs won't be logged through the event handler, although you could easily write some code that retrieves the logs in between the last log you did handle and the most recent one (logs are saved sequentially, indexed).
– Jevgeni Geurtsen
Nov 22 at 18:24
Yeah, it looks like a good approach. I'm gonna give it a try. Thank you man!. I will post any news I might get.
– Lucas Álvarez Lacasa
Nov 22 at 19:06
Jevgeni Geurtsen, I created a small POC with EventLogWatcher, but you end up getting an EventRecord, and thus, you have the slowdown of getting the message through "FormatDescription()". Looks like the most performant way to resolve this problem is using EventLog with OnEntryWritten callback.
– Lucas Álvarez Lacasa
Nov 23 at 12:49
|
show 6 more comments
up vote
0
down vote
You can give the EventLogReader class a try. See https://docs.microsoft.com/en-us/previous-versions/bb671200(v=vs.90).
It is better than the EventLog class because accessing the EventLog.Entries collection has the nasty property that its count can change while you are reading from it. What is even worse is that the reading happens on an IO threadpool thread which will let your application crash with an unhandled exception. At least that was the case some years ago.
The EventLogReader also gives you the ability to supply a query string to filter for the events you are interested in. That is the way to go if you write a new application.
Here is an application which shows how you can parallelize reading:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Threading.Tasks;
namespace EventLogReading
{
class Program
{
static volatile bool myHasStoppedReading = false;
static void ParseEventsParallel()
{
var sw = Stopwatch.StartNew();
var query = new EventLogQuery("Application", PathType.LogName, "*");
const int BatchSize = 100;
ConcurrentQueue<EventRecord> events = new ConcurrentQueue<EventRecord>();
var readerTask = Task.Factory.StartNew(() =>
{
using (EventLogReader reader = new EventLogReader(query))
{
EventRecord ev;
bool bFirst = true;
int count = 0;
while ((ev = reader.ReadEvent()) != null)
{
if ( count % BatchSize == 0)
{
events.Enqueue(ev);
}
count++;
}
}
myHasStoppedReading = true;
});
ConcurrentQueue<KeyValuePair<string, EventRecord>> eventsWithStrings = new ConcurrentQueue<KeyValuePair<string, EventRecord>>();
Action conversion = () =>
{
EventRecord ev = null;
using (var reader = new EventLogReader(query))
{
while (!myHasStoppedReading || events.TryDequeue(out ev))
{
if (ev != null)
{
reader.Seek(ev.Bookmark);
for (int i = 0; i < BatchSize; i++)
{
ev = reader.ReadEvent();
if (ev == null)
{
break;
}
eventsWithStrings.Enqueue(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
}
}
}
}
};
Parallel.Invoke(Enumerable.Repeat(conversion, 8).ToArray());
sw.Stop();
Console.WriteLine($"Got {eventsWithStrings.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
}
static void ParseEvents()
{
var sw = Stopwatch.StartNew();
List<KeyValuePair<string, EventRecord>> parsedEvents = new List<KeyValuePair<string, EventRecord>>();
using (EventLogReader reader = new EventLogReader(new EventLogQuery("Application", PathType.LogName, "*")))
{
EventRecord ev;
while ((ev = reader.ReadEvent()) != null)
{
parsedEvents.Add(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
}
}
sw.Stop();
Console.WriteLine($"Got {parsedEvents.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
}
static void Main(string args)
{
ParseEvents();
ParseEventsParallel();
}
}
}
Got 20322 events with strings in 19,320.047ms
Got 20323 events with strings in 5,327.064ms
This gives a decent speedup of a factor 4. I needed to use some tricks to get faster because for some strange reason the class ProviderMetadataCachedInformation is not thread safe and uses internally a lock(this) around the Format method which defeats paralell reading.
The key trick is to open the event log in the conversion threads again and then read a bunch of events of the query there via the event bookmark Api. That way you can format the strings independently.
You'll loop through theEventLog.Entries
collection with afor-loop
, so if the count would change while iterating, it won't matter. Also this problem is solved whenever OP uses the event to intercept the new event entries. I agree that for querying theEventLogReader
is better (not performance-wise) because it allows querying out of the box. I even think that because of thefor-loop
,EventLog.Entries
is even faster than theEventLogReader
.
– Jevgeni Geurtsen
Nov 23 at 8:39
Thanks for the response!. Like I said before, the problem with EventLogReader is that you hit a huge performance slowdown when accessing the message of each log, through the "FormatDescription()" method. I know it's faster since you can query and retrieve in memory only the slides of logs that you might want, but on the other hand it takes too much time to process each log, so it's not an acceptable solution for what I have to accomplish.
– Lucas Álvarez Lacasa
Nov 23 at 11:34
1
You can always format things with multiple threads that should give you a decent speedup if you can use them.
– Alois Kraus
Nov 23 at 15:49
I don't know how you would do that, you can have a thread per logfile that you try to analyze, but that's the furthest I would go I think. What could increase the performance significantly is try to store the templates of the messages that FormatDescription looks for, but this seems to be way too complex to achieve.
– Lucas Álvarez Lacasa
Nov 26 at 12:56
@LucasÁlvarezLacasa: I have updated my sample with parallel reading.
– Alois Kraus
Nov 26 at 22:55
|
show 1 more comment
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
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.
What if the service where this tasks are running stops? Is there a way to re enable this callback from a certain point? Or I will have to start again from that point and potentially lose logs in the middle?.
– Lucas Álvarez Lacasa
Nov 22 at 18:14
What I'm trying to say is, suppose that I'm reading logs with the callback. Suddenly my service stops. If I start over again and register the callback, I will lose the logs that were generated in the middle. Am I right?
– Lucas Álvarez Lacasa
Nov 22 at 18:15
Yes, those logs won't be logged through the event handler, although you could easily write some code that retrieves the logs in between the last log you did handle and the most recent one (logs are saved sequentially, indexed).
– Jevgeni Geurtsen
Nov 22 at 18:24
Yeah, it looks like a good approach. I'm gonna give it a try. Thank you man!. I will post any news I might get.
– Lucas Álvarez Lacasa
Nov 22 at 19:06
Jevgeni Geurtsen, I created a small POC with EventLogWatcher, but you end up getting an EventRecord, and thus, you have the slowdown of getting the message through "FormatDescription()". Looks like the most performant way to resolve this problem is using EventLog with OnEntryWritten callback.
– Lucas Álvarez Lacasa
Nov 23 at 12:49
|
show 6 more comments
up vote
1
down vote
accepted
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.
What if the service where this tasks are running stops? Is there a way to re enable this callback from a certain point? Or I will have to start again from that point and potentially lose logs in the middle?.
– Lucas Álvarez Lacasa
Nov 22 at 18:14
What I'm trying to say is, suppose that I'm reading logs with the callback. Suddenly my service stops. If I start over again and register the callback, I will lose the logs that were generated in the middle. Am I right?
– Lucas Álvarez Lacasa
Nov 22 at 18:15
Yes, those logs won't be logged through the event handler, although you could easily write some code that retrieves the logs in between the last log you did handle and the most recent one (logs are saved sequentially, indexed).
– Jevgeni Geurtsen
Nov 22 at 18:24
Yeah, it looks like a good approach. I'm gonna give it a try. Thank you man!. I will post any news I might get.
– Lucas Álvarez Lacasa
Nov 22 at 19:06
Jevgeni Geurtsen, I created a small POC with EventLogWatcher, but you end up getting an EventRecord, and thus, you have the slowdown of getting the message through "FormatDescription()". Looks like the most performant way to resolve this problem is using EventLog with OnEntryWritten callback.
– Lucas Álvarez Lacasa
Nov 23 at 12:49
|
show 6 more comments
up vote
1
down vote
accepted
up vote
1
down vote
accepted
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.
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.
edited Nov 23 at 12:18
answered Nov 22 at 17:45
Jevgeni Geurtsen
2,58141232
2,58141232
What if the service where this tasks are running stops? Is there a way to re enable this callback from a certain point? Or I will have to start again from that point and potentially lose logs in the middle?.
– Lucas Álvarez Lacasa
Nov 22 at 18:14
What I'm trying to say is, suppose that I'm reading logs with the callback. Suddenly my service stops. If I start over again and register the callback, I will lose the logs that were generated in the middle. Am I right?
– Lucas Álvarez Lacasa
Nov 22 at 18:15
Yes, those logs won't be logged through the event handler, although you could easily write some code that retrieves the logs in between the last log you did handle and the most recent one (logs are saved sequentially, indexed).
– Jevgeni Geurtsen
Nov 22 at 18:24
Yeah, it looks like a good approach. I'm gonna give it a try. Thank you man!. I will post any news I might get.
– Lucas Álvarez Lacasa
Nov 22 at 19:06
Jevgeni Geurtsen, I created a small POC with EventLogWatcher, but you end up getting an EventRecord, and thus, you have the slowdown of getting the message through "FormatDescription()". Looks like the most performant way to resolve this problem is using EventLog with OnEntryWritten callback.
– Lucas Álvarez Lacasa
Nov 23 at 12:49
|
show 6 more comments
What if the service where this tasks are running stops? Is there a way to re enable this callback from a certain point? Or I will have to start again from that point and potentially lose logs in the middle?.
– Lucas Álvarez Lacasa
Nov 22 at 18:14
What I'm trying to say is, suppose that I'm reading logs with the callback. Suddenly my service stops. If I start over again and register the callback, I will lose the logs that were generated in the middle. Am I right?
– Lucas Álvarez Lacasa
Nov 22 at 18:15
Yes, those logs won't be logged through the event handler, although you could easily write some code that retrieves the logs in between the last log you did handle and the most recent one (logs are saved sequentially, indexed).
– Jevgeni Geurtsen
Nov 22 at 18:24
Yeah, it looks like a good approach. I'm gonna give it a try. Thank you man!. I will post any news I might get.
– Lucas Álvarez Lacasa
Nov 22 at 19:06
Jevgeni Geurtsen, I created a small POC with EventLogWatcher, but you end up getting an EventRecord, and thus, you have the slowdown of getting the message through "FormatDescription()". Looks like the most performant way to resolve this problem is using EventLog with OnEntryWritten callback.
– Lucas Álvarez Lacasa
Nov 23 at 12:49
What if the service where this tasks are running stops? Is there a way to re enable this callback from a certain point? Or I will have to start again from that point and potentially lose logs in the middle?.
– Lucas Álvarez Lacasa
Nov 22 at 18:14
What if the service where this tasks are running stops? Is there a way to re enable this callback from a certain point? Or I will have to start again from that point and potentially lose logs in the middle?.
– Lucas Álvarez Lacasa
Nov 22 at 18:14
What I'm trying to say is, suppose that I'm reading logs with the callback. Suddenly my service stops. If I start over again and register the callback, I will lose the logs that were generated in the middle. Am I right?
– Lucas Álvarez Lacasa
Nov 22 at 18:15
What I'm trying to say is, suppose that I'm reading logs with the callback. Suddenly my service stops. If I start over again and register the callback, I will lose the logs that were generated in the middle. Am I right?
– Lucas Álvarez Lacasa
Nov 22 at 18:15
Yes, those logs won't be logged through the event handler, although you could easily write some code that retrieves the logs in between the last log you did handle and the most recent one (logs are saved sequentially, indexed).
– Jevgeni Geurtsen
Nov 22 at 18:24
Yes, those logs won't be logged through the event handler, although you could easily write some code that retrieves the logs in between the last log you did handle and the most recent one (logs are saved sequentially, indexed).
– Jevgeni Geurtsen
Nov 22 at 18:24
Yeah, it looks like a good approach. I'm gonna give it a try. Thank you man!. I will post any news I might get.
– Lucas Álvarez Lacasa
Nov 22 at 19:06
Yeah, it looks like a good approach. I'm gonna give it a try. Thank you man!. I will post any news I might get.
– Lucas Álvarez Lacasa
Nov 22 at 19:06
Jevgeni Geurtsen, I created a small POC with EventLogWatcher, but you end up getting an EventRecord, and thus, you have the slowdown of getting the message through "FormatDescription()". Looks like the most performant way to resolve this problem is using EventLog with OnEntryWritten callback.
– Lucas Álvarez Lacasa
Nov 23 at 12:49
Jevgeni Geurtsen, I created a small POC with EventLogWatcher, but you end up getting an EventRecord, and thus, you have the slowdown of getting the message through "FormatDescription()". Looks like the most performant way to resolve this problem is using EventLog with OnEntryWritten callback.
– Lucas Álvarez Lacasa
Nov 23 at 12:49
|
show 6 more comments
up vote
0
down vote
You can give the EventLogReader class a try. See https://docs.microsoft.com/en-us/previous-versions/bb671200(v=vs.90).
It is better than the EventLog class because accessing the EventLog.Entries collection has the nasty property that its count can change while you are reading from it. What is even worse is that the reading happens on an IO threadpool thread which will let your application crash with an unhandled exception. At least that was the case some years ago.
The EventLogReader also gives you the ability to supply a query string to filter for the events you are interested in. That is the way to go if you write a new application.
Here is an application which shows how you can parallelize reading:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Threading.Tasks;
namespace EventLogReading
{
class Program
{
static volatile bool myHasStoppedReading = false;
static void ParseEventsParallel()
{
var sw = Stopwatch.StartNew();
var query = new EventLogQuery("Application", PathType.LogName, "*");
const int BatchSize = 100;
ConcurrentQueue<EventRecord> events = new ConcurrentQueue<EventRecord>();
var readerTask = Task.Factory.StartNew(() =>
{
using (EventLogReader reader = new EventLogReader(query))
{
EventRecord ev;
bool bFirst = true;
int count = 0;
while ((ev = reader.ReadEvent()) != null)
{
if ( count % BatchSize == 0)
{
events.Enqueue(ev);
}
count++;
}
}
myHasStoppedReading = true;
});
ConcurrentQueue<KeyValuePair<string, EventRecord>> eventsWithStrings = new ConcurrentQueue<KeyValuePair<string, EventRecord>>();
Action conversion = () =>
{
EventRecord ev = null;
using (var reader = new EventLogReader(query))
{
while (!myHasStoppedReading || events.TryDequeue(out ev))
{
if (ev != null)
{
reader.Seek(ev.Bookmark);
for (int i = 0; i < BatchSize; i++)
{
ev = reader.ReadEvent();
if (ev == null)
{
break;
}
eventsWithStrings.Enqueue(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
}
}
}
}
};
Parallel.Invoke(Enumerable.Repeat(conversion, 8).ToArray());
sw.Stop();
Console.WriteLine($"Got {eventsWithStrings.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
}
static void ParseEvents()
{
var sw = Stopwatch.StartNew();
List<KeyValuePair<string, EventRecord>> parsedEvents = new List<KeyValuePair<string, EventRecord>>();
using (EventLogReader reader = new EventLogReader(new EventLogQuery("Application", PathType.LogName, "*")))
{
EventRecord ev;
while ((ev = reader.ReadEvent()) != null)
{
parsedEvents.Add(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
}
}
sw.Stop();
Console.WriteLine($"Got {parsedEvents.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
}
static void Main(string args)
{
ParseEvents();
ParseEventsParallel();
}
}
}
Got 20322 events with strings in 19,320.047ms
Got 20323 events with strings in 5,327.064ms
This gives a decent speedup of a factor 4. I needed to use some tricks to get faster because for some strange reason the class ProviderMetadataCachedInformation is not thread safe and uses internally a lock(this) around the Format method which defeats paralell reading.
The key trick is to open the event log in the conversion threads again and then read a bunch of events of the query there via the event bookmark Api. That way you can format the strings independently.
You'll loop through theEventLog.Entries
collection with afor-loop
, so if the count would change while iterating, it won't matter. Also this problem is solved whenever OP uses the event to intercept the new event entries. I agree that for querying theEventLogReader
is better (not performance-wise) because it allows querying out of the box. I even think that because of thefor-loop
,EventLog.Entries
is even faster than theEventLogReader
.
– Jevgeni Geurtsen
Nov 23 at 8:39
Thanks for the response!. Like I said before, the problem with EventLogReader is that you hit a huge performance slowdown when accessing the message of each log, through the "FormatDescription()" method. I know it's faster since you can query and retrieve in memory only the slides of logs that you might want, but on the other hand it takes too much time to process each log, so it's not an acceptable solution for what I have to accomplish.
– Lucas Álvarez Lacasa
Nov 23 at 11:34
1
You can always format things with multiple threads that should give you a decent speedup if you can use them.
– Alois Kraus
Nov 23 at 15:49
I don't know how you would do that, you can have a thread per logfile that you try to analyze, but that's the furthest I would go I think. What could increase the performance significantly is try to store the templates of the messages that FormatDescription looks for, but this seems to be way too complex to achieve.
– Lucas Álvarez Lacasa
Nov 26 at 12:56
@LucasÁlvarezLacasa: I have updated my sample with parallel reading.
– Alois Kraus
Nov 26 at 22:55
|
show 1 more comment
up vote
0
down vote
You can give the EventLogReader class a try. See https://docs.microsoft.com/en-us/previous-versions/bb671200(v=vs.90).
It is better than the EventLog class because accessing the EventLog.Entries collection has the nasty property that its count can change while you are reading from it. What is even worse is that the reading happens on an IO threadpool thread which will let your application crash with an unhandled exception. At least that was the case some years ago.
The EventLogReader also gives you the ability to supply a query string to filter for the events you are interested in. That is the way to go if you write a new application.
Here is an application which shows how you can parallelize reading:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Threading.Tasks;
namespace EventLogReading
{
class Program
{
static volatile bool myHasStoppedReading = false;
static void ParseEventsParallel()
{
var sw = Stopwatch.StartNew();
var query = new EventLogQuery("Application", PathType.LogName, "*");
const int BatchSize = 100;
ConcurrentQueue<EventRecord> events = new ConcurrentQueue<EventRecord>();
var readerTask = Task.Factory.StartNew(() =>
{
using (EventLogReader reader = new EventLogReader(query))
{
EventRecord ev;
bool bFirst = true;
int count = 0;
while ((ev = reader.ReadEvent()) != null)
{
if ( count % BatchSize == 0)
{
events.Enqueue(ev);
}
count++;
}
}
myHasStoppedReading = true;
});
ConcurrentQueue<KeyValuePair<string, EventRecord>> eventsWithStrings = new ConcurrentQueue<KeyValuePair<string, EventRecord>>();
Action conversion = () =>
{
EventRecord ev = null;
using (var reader = new EventLogReader(query))
{
while (!myHasStoppedReading || events.TryDequeue(out ev))
{
if (ev != null)
{
reader.Seek(ev.Bookmark);
for (int i = 0; i < BatchSize; i++)
{
ev = reader.ReadEvent();
if (ev == null)
{
break;
}
eventsWithStrings.Enqueue(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
}
}
}
}
};
Parallel.Invoke(Enumerable.Repeat(conversion, 8).ToArray());
sw.Stop();
Console.WriteLine($"Got {eventsWithStrings.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
}
static void ParseEvents()
{
var sw = Stopwatch.StartNew();
List<KeyValuePair<string, EventRecord>> parsedEvents = new List<KeyValuePair<string, EventRecord>>();
using (EventLogReader reader = new EventLogReader(new EventLogQuery("Application", PathType.LogName, "*")))
{
EventRecord ev;
while ((ev = reader.ReadEvent()) != null)
{
parsedEvents.Add(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
}
}
sw.Stop();
Console.WriteLine($"Got {parsedEvents.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
}
static void Main(string args)
{
ParseEvents();
ParseEventsParallel();
}
}
}
Got 20322 events with strings in 19,320.047ms
Got 20323 events with strings in 5,327.064ms
This gives a decent speedup of a factor 4. I needed to use some tricks to get faster because for some strange reason the class ProviderMetadataCachedInformation is not thread safe and uses internally a lock(this) around the Format method which defeats paralell reading.
The key trick is to open the event log in the conversion threads again and then read a bunch of events of the query there via the event bookmark Api. That way you can format the strings independently.
You'll loop through theEventLog.Entries
collection with afor-loop
, so if the count would change while iterating, it won't matter. Also this problem is solved whenever OP uses the event to intercept the new event entries. I agree that for querying theEventLogReader
is better (not performance-wise) because it allows querying out of the box. I even think that because of thefor-loop
,EventLog.Entries
is even faster than theEventLogReader
.
– Jevgeni Geurtsen
Nov 23 at 8:39
Thanks for the response!. Like I said before, the problem with EventLogReader is that you hit a huge performance slowdown when accessing the message of each log, through the "FormatDescription()" method. I know it's faster since you can query and retrieve in memory only the slides of logs that you might want, but on the other hand it takes too much time to process each log, so it's not an acceptable solution for what I have to accomplish.
– Lucas Álvarez Lacasa
Nov 23 at 11:34
1
You can always format things with multiple threads that should give you a decent speedup if you can use them.
– Alois Kraus
Nov 23 at 15:49
I don't know how you would do that, you can have a thread per logfile that you try to analyze, but that's the furthest I would go I think. What could increase the performance significantly is try to store the templates of the messages that FormatDescription looks for, but this seems to be way too complex to achieve.
– Lucas Álvarez Lacasa
Nov 26 at 12:56
@LucasÁlvarezLacasa: I have updated my sample with parallel reading.
– Alois Kraus
Nov 26 at 22:55
|
show 1 more comment
up vote
0
down vote
up vote
0
down vote
You can give the EventLogReader class a try. See https://docs.microsoft.com/en-us/previous-versions/bb671200(v=vs.90).
It is better than the EventLog class because accessing the EventLog.Entries collection has the nasty property that its count can change while you are reading from it. What is even worse is that the reading happens on an IO threadpool thread which will let your application crash with an unhandled exception. At least that was the case some years ago.
The EventLogReader also gives you the ability to supply a query string to filter for the events you are interested in. That is the way to go if you write a new application.
Here is an application which shows how you can parallelize reading:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Threading.Tasks;
namespace EventLogReading
{
class Program
{
static volatile bool myHasStoppedReading = false;
static void ParseEventsParallel()
{
var sw = Stopwatch.StartNew();
var query = new EventLogQuery("Application", PathType.LogName, "*");
const int BatchSize = 100;
ConcurrentQueue<EventRecord> events = new ConcurrentQueue<EventRecord>();
var readerTask = Task.Factory.StartNew(() =>
{
using (EventLogReader reader = new EventLogReader(query))
{
EventRecord ev;
bool bFirst = true;
int count = 0;
while ((ev = reader.ReadEvent()) != null)
{
if ( count % BatchSize == 0)
{
events.Enqueue(ev);
}
count++;
}
}
myHasStoppedReading = true;
});
ConcurrentQueue<KeyValuePair<string, EventRecord>> eventsWithStrings = new ConcurrentQueue<KeyValuePair<string, EventRecord>>();
Action conversion = () =>
{
EventRecord ev = null;
using (var reader = new EventLogReader(query))
{
while (!myHasStoppedReading || events.TryDequeue(out ev))
{
if (ev != null)
{
reader.Seek(ev.Bookmark);
for (int i = 0; i < BatchSize; i++)
{
ev = reader.ReadEvent();
if (ev == null)
{
break;
}
eventsWithStrings.Enqueue(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
}
}
}
}
};
Parallel.Invoke(Enumerable.Repeat(conversion, 8).ToArray());
sw.Stop();
Console.WriteLine($"Got {eventsWithStrings.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
}
static void ParseEvents()
{
var sw = Stopwatch.StartNew();
List<KeyValuePair<string, EventRecord>> parsedEvents = new List<KeyValuePair<string, EventRecord>>();
using (EventLogReader reader = new EventLogReader(new EventLogQuery("Application", PathType.LogName, "*")))
{
EventRecord ev;
while ((ev = reader.ReadEvent()) != null)
{
parsedEvents.Add(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
}
}
sw.Stop();
Console.WriteLine($"Got {parsedEvents.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
}
static void Main(string args)
{
ParseEvents();
ParseEventsParallel();
}
}
}
Got 20322 events with strings in 19,320.047ms
Got 20323 events with strings in 5,327.064ms
This gives a decent speedup of a factor 4. I needed to use some tricks to get faster because for some strange reason the class ProviderMetadataCachedInformation is not thread safe and uses internally a lock(this) around the Format method which defeats paralell reading.
The key trick is to open the event log in the conversion threads again and then read a bunch of events of the query there via the event bookmark Api. That way you can format the strings independently.
You can give the EventLogReader class a try. See https://docs.microsoft.com/en-us/previous-versions/bb671200(v=vs.90).
It is better than the EventLog class because accessing the EventLog.Entries collection has the nasty property that its count can change while you are reading from it. What is even worse is that the reading happens on an IO threadpool thread which will let your application crash with an unhandled exception. At least that was the case some years ago.
The EventLogReader also gives you the ability to supply a query string to filter for the events you are interested in. That is the way to go if you write a new application.
Here is an application which shows how you can parallelize reading:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Threading.Tasks;
namespace EventLogReading
{
class Program
{
static volatile bool myHasStoppedReading = false;
static void ParseEventsParallel()
{
var sw = Stopwatch.StartNew();
var query = new EventLogQuery("Application", PathType.LogName, "*");
const int BatchSize = 100;
ConcurrentQueue<EventRecord> events = new ConcurrentQueue<EventRecord>();
var readerTask = Task.Factory.StartNew(() =>
{
using (EventLogReader reader = new EventLogReader(query))
{
EventRecord ev;
bool bFirst = true;
int count = 0;
while ((ev = reader.ReadEvent()) != null)
{
if ( count % BatchSize == 0)
{
events.Enqueue(ev);
}
count++;
}
}
myHasStoppedReading = true;
});
ConcurrentQueue<KeyValuePair<string, EventRecord>> eventsWithStrings = new ConcurrentQueue<KeyValuePair<string, EventRecord>>();
Action conversion = () =>
{
EventRecord ev = null;
using (var reader = new EventLogReader(query))
{
while (!myHasStoppedReading || events.TryDequeue(out ev))
{
if (ev != null)
{
reader.Seek(ev.Bookmark);
for (int i = 0; i < BatchSize; i++)
{
ev = reader.ReadEvent();
if (ev == null)
{
break;
}
eventsWithStrings.Enqueue(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
}
}
}
}
};
Parallel.Invoke(Enumerable.Repeat(conversion, 8).ToArray());
sw.Stop();
Console.WriteLine($"Got {eventsWithStrings.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
}
static void ParseEvents()
{
var sw = Stopwatch.StartNew();
List<KeyValuePair<string, EventRecord>> parsedEvents = new List<KeyValuePair<string, EventRecord>>();
using (EventLogReader reader = new EventLogReader(new EventLogQuery("Application", PathType.LogName, "*")))
{
EventRecord ev;
while ((ev = reader.ReadEvent()) != null)
{
parsedEvents.Add(new KeyValuePair<string, EventRecord>(ev.FormatDescription(), ev));
}
}
sw.Stop();
Console.WriteLine($"Got {parsedEvents.Count} events with strings in {sw.Elapsed.TotalMilliseconds:N3}ms");
}
static void Main(string args)
{
ParseEvents();
ParseEventsParallel();
}
}
}
Got 20322 events with strings in 19,320.047ms
Got 20323 events with strings in 5,327.064ms
This gives a decent speedup of a factor 4. I needed to use some tricks to get faster because for some strange reason the class ProviderMetadataCachedInformation is not thread safe and uses internally a lock(this) around the Format method which defeats paralell reading.
The key trick is to open the event log in the conversion threads again and then read a bunch of events of the query there via the event bookmark Api. That way you can format the strings independently.
edited Nov 26 at 23:07
answered Nov 23 at 0:06
Alois Kraus
9,9822250
9,9822250
You'll loop through theEventLog.Entries
collection with afor-loop
, so if the count would change while iterating, it won't matter. Also this problem is solved whenever OP uses the event to intercept the new event entries. I agree that for querying theEventLogReader
is better (not performance-wise) because it allows querying out of the box. I even think that because of thefor-loop
,EventLog.Entries
is even faster than theEventLogReader
.
– Jevgeni Geurtsen
Nov 23 at 8:39
Thanks for the response!. Like I said before, the problem with EventLogReader is that you hit a huge performance slowdown when accessing the message of each log, through the "FormatDescription()" method. I know it's faster since you can query and retrieve in memory only the slides of logs that you might want, but on the other hand it takes too much time to process each log, so it's not an acceptable solution for what I have to accomplish.
– Lucas Álvarez Lacasa
Nov 23 at 11:34
1
You can always format things with multiple threads that should give you a decent speedup if you can use them.
– Alois Kraus
Nov 23 at 15:49
I don't know how you would do that, you can have a thread per logfile that you try to analyze, but that's the furthest I would go I think. What could increase the performance significantly is try to store the templates of the messages that FormatDescription looks for, but this seems to be way too complex to achieve.
– Lucas Álvarez Lacasa
Nov 26 at 12:56
@LucasÁlvarezLacasa: I have updated my sample with parallel reading.
– Alois Kraus
Nov 26 at 22:55
|
show 1 more comment
You'll loop through theEventLog.Entries
collection with afor-loop
, so if the count would change while iterating, it won't matter. Also this problem is solved whenever OP uses the event to intercept the new event entries. I agree that for querying theEventLogReader
is better (not performance-wise) because it allows querying out of the box. I even think that because of thefor-loop
,EventLog.Entries
is even faster than theEventLogReader
.
– Jevgeni Geurtsen
Nov 23 at 8:39
Thanks for the response!. Like I said before, the problem with EventLogReader is that you hit a huge performance slowdown when accessing the message of each log, through the "FormatDescription()" method. I know it's faster since you can query and retrieve in memory only the slides of logs that you might want, but on the other hand it takes too much time to process each log, so it's not an acceptable solution for what I have to accomplish.
– Lucas Álvarez Lacasa
Nov 23 at 11:34
1
You can always format things with multiple threads that should give you a decent speedup if you can use them.
– Alois Kraus
Nov 23 at 15:49
I don't know how you would do that, you can have a thread per logfile that you try to analyze, but that's the furthest I would go I think. What could increase the performance significantly is try to store the templates of the messages that FormatDescription looks for, but this seems to be way too complex to achieve.
– Lucas Álvarez Lacasa
Nov 26 at 12:56
@LucasÁlvarezLacasa: I have updated my sample with parallel reading.
– Alois Kraus
Nov 26 at 22:55
You'll loop through the
EventLog.Entries
collection with a for-loop
, so if the count would change while iterating, it won't matter. Also this problem is solved whenever OP uses the event to intercept the new event entries. I agree that for querying the EventLogReader
is better (not performance-wise) because it allows querying out of the box. I even think that because of the for-loop
, EventLog.Entries
is even faster than the EventLogReader
.– Jevgeni Geurtsen
Nov 23 at 8:39
You'll loop through the
EventLog.Entries
collection with a for-loop
, so if the count would change while iterating, it won't matter. Also this problem is solved whenever OP uses the event to intercept the new event entries. I agree that for querying the EventLogReader
is better (not performance-wise) because it allows querying out of the box. I even think that because of the for-loop
, EventLog.Entries
is even faster than the EventLogReader
.– Jevgeni Geurtsen
Nov 23 at 8:39
Thanks for the response!. Like I said before, the problem with EventLogReader is that you hit a huge performance slowdown when accessing the message of each log, through the "FormatDescription()" method. I know it's faster since you can query and retrieve in memory only the slides of logs that you might want, but on the other hand it takes too much time to process each log, so it's not an acceptable solution for what I have to accomplish.
– Lucas Álvarez Lacasa
Nov 23 at 11:34
Thanks for the response!. Like I said before, the problem with EventLogReader is that you hit a huge performance slowdown when accessing the message of each log, through the "FormatDescription()" method. I know it's faster since you can query and retrieve in memory only the slides of logs that you might want, but on the other hand it takes too much time to process each log, so it's not an acceptable solution for what I have to accomplish.
– Lucas Álvarez Lacasa
Nov 23 at 11:34
1
1
You can always format things with multiple threads that should give you a decent speedup if you can use them.
– Alois Kraus
Nov 23 at 15:49
You can always format things with multiple threads that should give you a decent speedup if you can use them.
– Alois Kraus
Nov 23 at 15:49
I don't know how you would do that, you can have a thread per logfile that you try to analyze, but that's the furthest I would go I think. What could increase the performance significantly is try to store the templates of the messages that FormatDescription looks for, but this seems to be way too complex to achieve.
– Lucas Álvarez Lacasa
Nov 26 at 12:56
I don't know how you would do that, you can have a thread per logfile that you try to analyze, but that's the furthest I would go I think. What could increase the performance significantly is try to store the templates of the messages that FormatDescription looks for, but this seems to be way too complex to achieve.
– Lucas Álvarez Lacasa
Nov 26 at 12:56
@LucasÁlvarezLacasa: I have updated my sample with parallel reading.
– Alois Kraus
Nov 26 at 22:55
@LucasÁlvarezLacasa: I have updated my sample with parallel reading.
– Alois Kraus
Nov 26 at 22:55
|
show 1 more comment
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53430475%2freading-windows-logs-efficiently-and-fast%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
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.
Meaning the user will use the application while its moving logs? I'd just use a Task to handle the work..– Jevgeni Geurtsen
Nov 22 at 12:20
No, it just means that I don't want to get a SystemOutOfMemoryException or something like that on the service because of the high memory usage. I just want to know if there's some more efficient workaround than the ones I could find so far.
– Lucas Álvarez Lacasa
Nov 22 at 12:29
This will basically run in a Windows Service, what I don't know about the third approach (EventLog) is if there's a way to filter the log collection without having to read everything in memory and then applying a LINQ query.
– Lucas Álvarez Lacasa
Nov 22 at 12:31
I think you want to use the constructor of
EventLog
:new EventLog("Security")
. Also, if it runs as a service, I would copy the existing Logs during the first run (on background), then hook theEntryWrittenEventHandler
withinEventLog
in your service to handle newly created logs.– Jevgeni Geurtsen
Nov 22 at 12:40
So, you are basically telling me to subscribe to the EntryWrittenEventHandler for each one of the logfiles from where I need to read? And then every time this event fires I will have the newly created log and I can forward it to whatever I want to. Is that correct?
– Lucas Álvarez Lacasa
Nov 22 at 12:58