Files
PatchProbe-Server/PatchProbe.Cli/Collectors/EventCollector.cs
2026-05-25 10:29:38 +08:00

79 lines
2.8 KiB
C#

using System.Diagnostics.Eventing.Reader;
using Microsoft.Extensions.Logging;
using PatchProbe.Shared.Contracts;
using PatchProbe.Shared.Models;
namespace PatchProbe.Cli.Collectors;
public sealed class EventCollector(ILogger<EventCollector> logger) : ICollector<List<UpdateEvent>>
{
private static readonly (string LogName, string? Provider)[] Sources =
[
("System", null),
("Microsoft-Windows-WindowsUpdateClient/Operational", "Microsoft-Windows-WindowsUpdateClient"),
("Microsoft-Windows-UpdateOrchestrator/Operational", "Microsoft-Windows-UpdateOrchestrator"),
];
private const int LookbackHours = 72;
private const int MaxEventsPerSource = 50;
public Task<List<UpdateEvent>> CollectAsync(CancellationToken cancellationToken = default)
{
logger.LogInformation("Collecting recent Windows Update events (last {Hours}h)", LookbackHours);
var events = new List<UpdateEvent>();
var since = DateTime.UtcNow.AddHours(-LookbackHours);
foreach (var (logName, provider) in Sources)
{
try
{
events.AddRange(ReadEvents(logName, provider, since));
}
catch (Exception ex)
{
logger.LogDebug(ex, "Could not read event log: {LogName}", logName);
}
}
logger.LogInformation("Collected {Count} update events", events.Count);
return Task.FromResult(events);
}
private static IEnumerable<UpdateEvent> ReadEvents(string logName, string? provider, DateTime since)
{
var providerFilter = provider != null ? $" and Provider[@Name='{provider}']" : string.Empty;
var query = new EventLogQuery(
logName,
PathType.LogName,
$"*[System[(Level<=3){providerFilter} and TimeCreated[@SystemTime>='{since:yyyy-MM-ddTHH:mm:ss.000Z}']]]");
using var reader = new EventLogReader(query);
int count = 0;
while (reader.ReadEvent() is EventRecord record && count < MaxEventsPerSource)
{
using (record)
{
yield return new UpdateEvent
{
EventId = record.Id,
Source = record.ProviderName,
LogName = record.LogName,
Level = record.LevelDisplayName,
TimeCreated = record.TimeCreated.HasValue
? new DateTimeOffset(record.TimeCreated.Value, TimeSpan.Zero)
: null,
Message = TryFormatMessage(record),
};
count++;
}
}
}
private static string? TryFormatMessage(EventRecord record)
{
try { return record.FormatDescription(); }
catch { return null; }
}
}