Luc*_*asa 6 c# windows logging
我想要完成的是一个C#应用程序,它将从Windows事件日志中读取日志并将它们存储在其他位置。这必须很快,因为将要安装它的某些设备会生成大量的日志/秒。
到目前为止,我已经尝试了三种方法:
本地WMI:效果不好,有太多错误和异常,这些异常和异常是由需要加载的集合的大小引起的。EventLogReader:我虽然这是一个完美的解决方案,但是它允许您使用XPath表达式查询事件日志。问题是,当您想获取每个日志的消息内容时(通过调用FormatDescription()),对于长集合来说会花费太多时间。例如:如果我浏览它们,我可以在0.11s内读取12k日志。如果我添加一行来为每个日志存储消息,那么将花费近6分钟的时间来完成完全相同的操作,这对于如此低的日志数量是完全疯狂的。我不知道是否可以对EventLogReader进行某种优化以更快地获取消息,我无法
我还发现,您可以使用名为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);
尽管获取消息的速度更快,但是内存的使用可能会很高,我不想在将部署此解决方案的设备上引起任何问题。
有人可以帮我吗?我找不到实现这种目标的任何解决方法或方法。
谢谢!。
您可以尝试一下 EventLogReader 类。请参阅https://learn.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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)Got 20322 events with strings in 19,320.047ms Got 20323 events with strings in 5,327.064ms
这提供了 4 倍的不错的加速。我需要使用一些技巧来获得更快的速度,因为出于某种奇怪的原因,类 ProviderMetadataCachedInformation 不是线程安全的,并且在 Format 方法周围使用内部锁(this),这会导致并行读取失败。关键技巧是再次打开转换线程中的事件日志,然后通过事件书签 Api 读取一堆查询事件。这样您就可以独立格式化字符串。
更新1
我在 .NET 5 中进行了一项更改,将性能提高了 3 至 20 倍。请参阅https://github.com/dotnet/runtime/issues/34568。您还可以从 .NET Core 复制 EventLogReader 类并使用此类,这将为您带来相同的加速。
我的博客文章描述了完整的传奇故事:https://aloiskraus.wordpress.com/2020/07/20/ms-performance-hud-analyze-eventlog-reading-performance-in-realtime/
我们在评论中讨论了一些有关阅读现有日志的信息,可以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上面代码块中显示的机制时,我认为EventLog更快 - 它只使用索引访问循环内的条目,集合Entries是只是一个引用,而在使用 时EventLogReader,它将首先执行查询并循环该结果,这应该会更慢。正如 Alois 帖子中评论的那样:如果您不需要使用查询选项,只需使用EventLog变体即可。如果您确实需要查询,请按EventLogReader原样使用,可以在比使用时更低的级别上查询EventLog(仅 LINQ 查询,这当然比执行查找时查询慢)。
为了防止您将来再次遇到这种麻烦,并且因为您说您正在运行服务,所以我将使用EventLog类的EntryWritten事件:
    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}");
    }
请注意,您必须将EnableRaisingEvents设置为true,以便在记录新条目时触发该事件。启动(例如) Task也是一个很好的做法(也是性能方面的),这样系统在对事件的调用进行排队时就不会锁定自身。
如果您想检索所有新创建的事件,则此方法效果很好,如果您想检索新创建的事件但对这些事件使用查询(过滤器),您可以查看 EventLogWatcher类,但在您的情况下,当没有约束,我只使用该EntryWritten事件,因为您不需要过滤器并且为了简单。
| 归档时间: | 
 | 
| 查看次数: | 540 次 | 
| 最近记录: |