快速高效地读取Windows日志

时间:2018-11-22 11:54:56

标签: c# windows logging

我要完成的是一个C#应用程序,它将从Windows事件日志中读取日志并将它们存储在其他位置。这必须很快,因为将要安装它的某些设备会产生大量的日志/秒。

到目前为止,我已经尝试了三种方法:

本地WMI:效果不佳,由于需要加载的集合大小而导致太多错误和异常。 EventLogReader:尽管这是一个完美的解决方案,但由于它允许您使用XPath表达式查询事件日志,所以我很满意。问题是,当您想获取每个日志的消息内容时(通过调用FormatDescription()),对于长集合来说会花费太多时间。 例如:如果我浏览它们,我可以在0.11s内读取12k日志。 如果我添加一行来为每个日志存储消息,那么将花费近6分钟的时间来完成完全相同的操作,这对于如此低的日志数量是完全疯狂的。 我不知道是否可以对EventLogReader进行某种优化以更快地获取消息,我在MS文档或Internet上都找不到任何内容。

我还发现,您可以使用名为EventLog的类读取日志条目。但是,该技术不允许您输入任何类型的过滤器,因此您基本上必须将整个日志列表加载到内存中,然后根据需要将其过滤掉。 这是一个示例:

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

尽管获取消息的速度更快,但内存的使用可能会很高,我不想在将部署此解决方案的设备上引起任何问题。

有人可以帮助我吗?我找不到实现这种目标的任何解决方法或方法。

谢谢!。

2 个答案:

答案 0 :(得分:1)

我们讨论了一些有关阅读注释中现有日志的问题,可以通过访问以下内容来访问带有Security标签的日志:

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

这可能不是最干净的方法(性能方面),但是我怀疑其他方法会更快,因为您自己已经通过尝试不同的技术找到了答案。 一个对Alois帖子的小型编辑二人组:EventLogReader开箱即用的速度并不比EventLog快,尤其是当使用上面代码块中显示的for-loop机制时,我认为{{1} }更快-仅使用其索引访问循环内的条目,EventLog集合只是一个引用,而使用Entries时,它将首先执行查询并遍历该结果,这应该比较慢。正如对Alois的帖子所述:如果不需要使用查询选项,只需使用EventLogReader变体。如果您确实需要查询,请像使用EventLog时一样,使用EventLogReader进行较低级别的查询(仅LINQ查询,这比执行查询时的查询速度慢)

为防止您将来再次遇到麻烦,并且因为您说您正在运行服务,我将使用EntryWritten类的EventLog事件:

EventLog

请注意,您必须EnableRaisingEvents设置为 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}"); } ,以便在记录新条目时触发该事件。 (例如在性能上)启动Task也是一种好习惯,这样在排队对事件的调用时,系统就不会锁定自身。

如果您要检索所有个新创建的事件,则此方法很好用;如果要检索新创建的事件,但对这些事件使用查询(过滤器),则可以检出{{ 3}}类,但是在您的情况下,当没有任何约束时,我将只使用true事件,因为您不需要过滤器,并且简单明了。

答案 1 :(得分:1)

您可以尝试EventLogReader类。参见https://docs.microsoft.com/en-us/previous-versions/bb671200(v=vs.90)

它比EventLog类更好,因为访问EventLog.Entries集合具有令人讨厌的属性,当您从中读取它时,其计数可以更改。更糟糕的是,读取发生在IO线程池线程上,这将使您的应用程序崩溃并产生未处理的异常。至少几年前就是这种情况。

EventLogReader还使您能够提供查询字符串以过滤您感兴趣的事件。这是编写新应用程序时要采用的方法。

这是一个显示如何并行阅读的应用程序:

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

这使速度提高了4倍。我需要使用一些技巧来加快速度,因为出于某些奇怪的原因,类ProviderMetadataCachedInformation不是线程安全的,并且在Format方法内部使用了一个锁(this),这使paralell读取失败。 。 关键技巧是再次在转换线程中打开事件日志,然后通过事件书签Api读取查询中的一堆事件。这样,您可以独立设置字符串的格式。