Skip to content
Sysadmin

Why Event Viewer Is Slow, and How I Replaced It in Rust

The first time I had to triage a crash on a production Windows box, I opened Event Viewer, double-clicked System, and waited. Forty-five seconds. Then I spent ten minutes scrolling a list where each tick lagged.

The machine had about 120,000 events in the System log. That’s not a lot. The Event Log API — EvtQuery, EvtNext, EvtRender — can stream those in under a second on any modern box. The lag is entirely the Event Viewer MMC snap-in, which hasn’t been meaningfully updated since Vista.

I wrote a replacement in Rust called EventSleuth. It opens instantly and scrolls smoothly on million-event logs.

Why Event Viewer is slow

When you open a channel, the MMC snap-in calls EvtQuery and then synchronously pumps EvtNext on the UI thread until it’s enumerated the whole log. Then it paints. On a 120k-event log that’s 120k XML parses and 120k ListView row insertions all blocking the UI thread. Scrolling jitters for the same reason — more work on the same thread.

The fix isn’t complicated. Move the query to a worker thread, parse events into a flat struct, and virtualise the list so the UI only renders the rows on screen. That’s all EventSleuth does.

How EventSleuth does it

UI thread                 Query thread
---------                 ------------
filter/scroll                      [blocked on channel recv]
    │                              │
    ▼                              │
request_query(filter) ─────────────▶ EvtQuery(channel, xpath, ...)
    │                              │
    │                              ▼
    ◀────────────── send batches ─── EvtNext(200 events)
    │ (event_batches: Vec<Event>)   │
    ▼                              ▼
append to in-memory table       EvtNext(next 200) ...

The worker streams events in batches of 200 using EvtRenderEventValues — not the full XML render, just the fields the table needs (EventID, Level, time, provider, one-line message). Full XML only gets fetched when you click a row. The UI paints after the first batch arrives, about 20ms on a warm cache.

Virtual scrolling is egui’s ScrollArea::show_rows. Ten million entries in the backing Vec, egui renders the ~40 rows on screen. This is the bit Event Viewer has never done.

One thing that caught me: the event’s human-readable message isn’t stored in the log. It’s a template in the provider manifest. To render it you call EvtFormatMessage with the provider metadata. That metadata load is expensive, so you cache it by provider name — open it once per provider per process lifetime.

EvtQuery also accepts a path to an .evtx file, not just a channel name. So EventSleuth opens exported logs from another machine with zero extra code.

If you can’t install anything

wevtutil qe Security /q:"*[System[EventID=4624]]" /f:text /c:50

This is the most useful thing in wevtutil. It streams events, takes XPath, and formats them as readable text. Pipe it to findstr and you have a workable triage flow with nothing installed.

EventSleuth: github.com/swatto86/EventSleuth. Rust, egui, MIT.

← All articles