优化来自 System.Diagnostics.EventLog 的 LINQ 读取



我在某些计算机上遇到以下查询的性能问题:

System.Diagnostics.EventLog log = new System.Diagnostics.EventLog("Application");
var entries = log.Entries
.Cast<System.Diagnostics.EventLogEntry>()
.Where(x => x.EntryType == System.Diagnostics.EventLogEntryType.Error)
.OrderByDescending(x => x.TimeGenerated)
.Take(cutoff)
.Select(x => new
{
x.Index,
x.TimeGenerated,
x.EntryType,
x.Source,
x.InstanceId,
x.Message
}).ToList();

显然,在某些查询中ToList()可能会很慢,但是我应该用什么来替换它?

log.Entries集合的工作方式如下:它知道事件的总数(log.Entries.Count(,当您访问单个元素时 - 它会进行查询以获取该元素。

这意味着当您枚举整个集合Entries时 - 它将查询每个单独的元素,因此会有Count查询。LINQ 查询的结构(例如,OrderBy(强制对该集合进行完全枚举。如您所知 - 它的效率非常低。

更有效的做法可能是仅查询所需的日志条目。为此,您可以使用EventLogQuery类。假设您有一个简单的类来保存事件信息详细信息:

private class EventLogInfo {
public int Id { get; set; }
public string Source { get; set; }
public string Message { get; set; }
public DateTime? Timestamp { get; set; }
}

然后,可以像这样转换效率低下的 LINQ 查询:

// query Application log, only entries with Level = 2 (that's error)
var query = new EventLogQuery("Application", PathType.LogName, "*[System/Level=2]");
// reverse default sort, by default it sorts oldest first
// but we need newest first (OrderByDescending(x => x.TimeGenerated)
query.ReverseDirection = true;            
var events = new List<EventLogInfo>();
// analog of Take
int cutoff = 100;
using (var reader = new EventLogReader(query)) {
while (true) {
using (var next = reader.ReadEvent()) {
if (next == null)
// we are done, no more events
break;
events.Add(new EventLogInfo {
Id = next.Id,
Source = next.ProviderName,
Timestamp = next.TimeCreated,
Message = next.FormatDescription()
});
cutoff--;
if (cutoff == 0)
// we are done, took as much as we need
break;
}
}
}

它会快 10-100 倍。但是,此 API 是更底层的,并返回EventRecord实例(而不是EventLogEntry(,因此对于某些信息,可能有不同的方法来获取它(与EventLogEntry相比(。

如果你决定你绝对必须使用log.EntriesEventLogEntry,那么至少向后枚举Entries。这是因为最新事件在最后(按时间戳升序排序(,您需要按时间戳降序排列的前 X 个错误。

EventLog log = new System.Diagnostics.EventLog("Application");
int cutoff = 100;
var events = new List<EventLogEntry>();
for (int i = log.Entries.Count - 1; i >= 0; i--) {
// note that line below might throw ArgumentException
// if, for example, entries were deleted in the middle
// of our loop. That's rare condition, but robust code should handle it
var next = log.Entries[i];
if (next.EntryType == EventLogEntryType.Error) {
// add what you need here
events.Add(next);
// got as much as we need, break
if (events.Count == cutoff)
break;
}
}

这效率较低,但仍应比当前方法快 10 倍。请注意,它更快Entries因为集合不会在内存中具体化。当您访问单个元素时,将查询它们,并且在特定情况下向后枚举时,很有可能查询更少的元素。

最新更新