我要完成的是一个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);
尽管获取消息的速度更快,但内存的使用可能会很高,我不想在将部署此解决方案的设备上引起任何问题。
有人可以帮助我吗?我找不到实现这种目标的任何解决方法或方法。
谢谢!。
答案 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读取查询中的一堆事件。这样,您可以独立设置字符串的格式。