Log Analyzer plugin¶
Turn the editor into a capable log workbench: parse structured log files through editable profiles, navigate by parsed fields and timestamps, merge multiple sources onto one timeline, follow a single request across sources, and analyse rates / percentiles / top-N values — all without loading the raw text into a normal editor tab.
Where to find it¶
The plugin adds three entries under the Log Analyzer menu:
- Log Analyzer ▸ Open as Log… — open a single log file in a dedicated Log View window.
- Log Analyzer ▸ Open Merged Logs… — pick two or more files, give each its own profile, and see them interleaved on one timeline.
- Log Analyzer ▸ Open System Log… (Windows only) — query the Windows Event
Log through the same view, with filters pushed down to PowerShell's
Get-WinEvent.
A Preferences ▸ Plugins ▸ Log Analyzer page lets you set the default level chips, the fallback profile for files that don't match a glob, the "warn before parsing files above N MB" threshold, the default filter mode, and the follow-tail poll interval — one knob for both the file tail timer and the system-log stream (each Windows Event Log poll spawns a PowerShell process, so heavyweight sources may want it slower).
Large files parse on a background thread with a live record counter, and the expensive aggregations (message templates, anomalies) also compute in the background — the status bar shows "Analyzing statistics in the background…" and the panels fill in when ready, so the window stays responsive throughout. On very large loads the statistics aggregate over the newest 150,000 records and the timeline samples the whole span.
Single-file Log View¶
Opening a log shows the dedicated Log View window. It mirrors the ergonomics of the editor's own table-based windows (like the Diff Compare window) and stays out of the editor tab strip.
Layout¶
- Toolbar — level chips (TRACE…FATAL), filter mode + filter line,
Next / Previous Error (
F8/Shift+F8), Go to Time (Ctrl+G). - Timeline strip at the top — event volume bucketed over time, coloured by level. Drag a range to filter by time; click a bar to jump the table to the first record at or after that bucket.
- Table in the middle — Time / Level / Source / Message, virtualised so it scrolls smoothly even for hundreds of thousands of records.
- Detail pane below the table — shows the full raw multi-line entry for the selected row.
- Stats panel docked on the right — per-level counts, event rate,
percentile picker (p50/p95/p99 over any numeric field, default
bytes), HTTP status-class distribution (1xx…5xx, hidden when nostatusfield is present), and a top-N table over any field (defaulturl, capped at 25 rows).
The record table and the detail pane use the editor font from Preferences ▸ General — the same face and size as a normal editor tab, so timestamps and levels line up column by column. Changing that setting applies to log views that are already open, no reopening needed. The same goes for the merged view, its side-by-side panes and the sequence diagram. The toolbar, filter line and statistics panel keep the interface font: they are chrome, not log content.
Filter modes¶
The filter line in the toolbar accepts five different expressions; pick the mode from the combo to its left:
| Mode | What it matches |
|---|---|
| Plain | Case-insensitive substring against the raw line. |
| Words | Whitespace-split tokens; every token must appear somewhere in the line, in any order. phpinfo GET matches a line containing both phpinfo and GET. |
| Wildcard | * and ? glob against the raw line, case-insensitive. |
| Regex | Plain QRegularExpression pattern, case-insensitive by default. |
| Query | Structured grammar with field, level, and time predicates plus AND/OR/NOT and parentheses. |
Examples for Query mode:
level>=WARN
status:500 AND method:POST
message~/timeout|refused/ AND @time in [09:00..09:05]
NOT (source:nginx OR source:apache)
level>=ERROR OR (status:5\d\d AND method:POST)
The filter line debounces typing by 200 ms so the table doesn't refilter on every keystroke; pressing Enter or losing focus applies immediately, and clearing the line also applies immediately. When a Wildcard / Regex / Query expression fails to compile the line turns red and the tooltip shows the diagnostic — the proxy keeps the previous valid filter on screen so the table doesn't blank out while you type.
Navigation¶
- F8 / Shift+F8 — next / previous error or fatal record.
- Ctrl+G — go to a specific time (UTC, calendar popup).
- Click a timeline bar to jump to the first record in that bucket.
These three chords are user-remappable in the Shortcut mapper (category Log Analyzer); open Log Views apply a remap immediately and the toolbar tooltips show the current chord.
Live parse + cancel¶
Large files start showing records within tens of milliseconds; parsing
continues in the background and the table grows as records arrive.
Filtering, scrolling, and F8 work while the parse is still running.
Closing the window stops the worker promptly.
Merged Log View¶
Log Analyzer ▸ Open Merged Logs… lets you investigate a problem that spans multiple sources — a client log plus a server log, an app log plus a system journal, web access plus error, etc.
The per-file profile picker¶
After choosing two or more files you see a small dialog with one row per
file and a profile combo per row. The combo is pre-selected by matching
the file name against each profile's filename globs (case-insensitive).
For a typical nginx deployment, access.log auto-picks
nginx — access log (default combined) and error.log auto-picks
nginx — error log. Override any row manually before clicking OK.
Layout¶
- Toolbar — level chips, filter line with mode combo, trace controls (combo + Follow / Clear), Sequence diagram toggle, Side-by-side toggle.
- Timeline strip at the top — stacked per-source histogram with one lane per source, so you can see correlated bursts visually.
- Trace banner above the table — amber strip shown only when a
trace filter is active:
Following <field> = <value> — N rows across all sources. - Stacked main area — three pages switchable from the toolbar:
- Table (default): one row per merged record with Time / Src /
Level / Logger / Message columns; the
Srccolumn foreground is the source's lane colour so you can scan source identity at a glance. - Sequence diagram — see Follow trace.
- Side-by-side panes — see Side-by-side.
- Table (default): one row per merged record with Time / Src /
Level / Logger / Message columns; the
- Detail pane below the table.
Filters that compose¶
In the merge view the filter pipeline runs in this order, all composing on top of each other:
- Source visibility — a checkbox in the timeline's left margin, one per lane, next to the source's name and colour. An unchecked lane keeps its row, dimmed: it has to, since that checkbox is the only way back. Right-click the margin for Show only this source / Show all sources.
- Level chips — TRACE…FATAL.
- Follow trace — see below.
- Text filter — Plain / Words / Wildcard / Regex / Query (same modes and behaviour as the single-file view, applied to the merged stream).
That means you can do, for example, level>=ERROR AND status:500
across both nginx access and error logs in one expression, and follow
a single client through both — the trace filter narrows the set further
without breaking the level / source / text filters.
Follow trace¶
- Pick a correlation key in the toolbar's Trace: combo. It's
seeded with the conventional set (
trace_id/request_id/session_id/correlation_id/span_id/xRequestId) and any distinct field name actually present in the loaded records. Type a custom name if needed. - Select a row in the table.
- Click Follow trace — or right-click the row and pick Follow trace: \<field>=\<value> for any conventional key the record carries (a one-shot path that doesn't need the combo step).
The table immediately filters to just the records sharing that key's value across all sources. Click Clear trace to remove the filter.
Sequence diagram¶
Once a trace filter is active and exactly 2–4 sources are visible, the toolbar's Sequence diagram toggle becomes available. It switches the main area to a fixed-lane diagram:
- Each visible source is a vertical lane labelled at the top with a coloured swatch.
- One row per record, time flowing top-to-bottom. The actual timestamp is on the left so you don't lose precision; rows are rank-based (fixed height) rather than time-proportional, because a real-world trace has bursts plus long pauses and a time-proportional axis would waste most of the screen on the pauses.
- A coloured dot on the owning source's lane, plus a subtle grey arrow between consecutive records on different lanes — so you can literally see the trace bouncing between systems.
- Level chip and message text on the right.
Click any row in the diagram to scroll the underlying table to that record. Toggle the action off to go back to the table.
For >4 sources the diagram is disabled and the table view stays.
Side-by-side panes¶
When exactly two sources are visible, the toolbar's Side-by-side toggle becomes available. It splits the main area into two table panes — one source each — with scroll positions linked by timestamp: scroll either pane, and the other tracks to the nearest record at or after that time. Drift between server clocks shows up naturally as the panes pulling apart.
The Sequence diagram and Side-by-side toggles are mutually exclusive (turning one on snaps the other off); both reset to the table view when the active source count moves outside their supported range.
Rotation sets are one lane¶
Picking three files out of one rotated set is picking one log, so they load
as one lane. The picker shows a row per lane with its segments listed
underneath, oldest first, and the lane is labelled after its newest file
with a count of the older ones — MxAPILog.txt (+2).
Right-click a lane in the timeline for Show each rotated file separately and, once split, Merge rotated files into one lane. Splitting is a re-partition of what is already in memory, so nothing is read again — but it changes what a row means, so the table drops its selection.
Sources must state comparable times¶
A merge places records from several sources on one axis, which only means something if they can all state the same amount of date. A year-less syslog cannot be positioned against a dated log without inventing the year, and a build log with no timestamps cannot be positioned at all. Mixing them is refused up front, with a message naming which sources fall in which group. Merge sources of the same kind, or open them separately.
Restoring¶
A merged view comes back with the session. What is saved is the profile chosen for each lane and the files that lane resolved to; the records are re-read in the background, so the tab appears at once and fills in. Files that vanished and profiles that were deleted are skipped and noted in the log rather than turned into a question at launch.
Native system logs (Windows)¶
Log Analyzer ▸ Open System Log… queries the Windows Event Log without
needing a file on disk. The dialog asks for a channel
(Application / System / Security / a custom name), an optional minimum
level, and a record cap. The plugin shells out to PowerShell's
Get-WinEvent | ConvertTo-Json -Compress with the filter pushed
down, then parses the NDJSON output through the bundled
windows-event-log profile.
The action is only registered when the host actually has a working Windows Event Log source — it is genuinely absent on Linux and macOS rather than greyed.
macOS unified log and Linux journald sources are planned and slot in behind the same internal interface, with no UI changes when they land.
Profiles¶
The engine has no built-in special-cased parsers. Every supported
format — Apache, nginx, syslog, JSON Lines, logfmt, CSV, Windows Event
Log — is a LogProfile JSON file shipped in the application's
log-profiles/ directory. User profiles live in the user config
directory's log-profiles/ folder and override bundled ones by id.
Each profile describes:
- line model —
regex,json,logfmt,csvordelimited. - timestamp — which field, accepted
strftime-style formats, source timezone, epoch units. - level — which field plus a mapping of raw tokens to the canonical
Trace / Debug / Info / Warn / Error / Fatalenum. - continuation — how following lines glue to the current record (Java stack traces become one entry, not 30).
- derived fields — extra fields computed by a sub-pattern over an
existing field. The bundled apache + nginx profiles use this to
expose a uniform
client_ipfield so the merge view's Follow trace can correlate web access and error events for the same client across both sources. - rotation — which files form one logical log, and in what order. See Rotation: describing a set of files.
You do not have to write these files by hand: the editor for them — with a field grabber, grok-style aliases and a live timestamp tester — is described under Editing profiles.
Times are shown to the precision the log states¶
Not every log states a full date. RFC 3164 syslog gives a month, a day and a time but no year; some embedded and in-house logs give only a time of day; build output gives none at all.
The Time column shows exactly what the line said — 13:36:55,
Oct 10 14:32:52, or a full timestamp with UTC — and never fills a gap
in with a guess. The same holds for the TSV export and for the profile
editor's preview, so an invented year cannot leave the application and be
mistaken downstream for a real one.
Ordering, the timeline and the statistics keep working inside such a file: the missing parts are anchored internally so records stay comparable with each other. They just never surface.
Editing profiles¶
Log Analyzer ▸ Edit Profile… lists every profile with a Built-in or Custom badge. Selecting one loads it.
Built-in profiles are read-only, the way built-in themes are: the form is shown but disabled, and Clone is the way to change one. A clone takes a free id, so a copy can never quietly shadow the profile it came from. New starts from scratch, Delete removes one of your own, and switching away from unsaved work asks first.
When a profile is imported whose id matches a built-in one, it is stored under a free id instead of replacing it — and the editor says which.
Rotation: describing a set of files¶
A rotated log is one logical stream cut into several files. A profile says which files belong together with two names — nothing else is needed, and because there is only one description there is nothing that can contradict itself.
| Field | Meaning |
|---|---|
liveName |
The file being written to right now. |
rotatedName |
The shape the older files take. |
indexOrder |
Which end of the numbering is newest. |
orderBy |
Where the order comes from. Almost always name. |
Placeholders¶
Both names accept these. Outside them, every character is literal — . is
a dot, not "any character", and * is an asterisk, not a wildcard.
| Placeholder | Means |
|---|---|
%LIVE% |
The file the user opened. Use it when the profile cannot know the name in advance. |
%N |
The rotation number. Read as a number, so _10 sorts after _9 rather than between _1 and _2. |
%Y %y |
Year, four or two digits. Two-digit years use the usual pivot: 69–99 mean 19xx, 00–68 mean 20xx. |
%m %b %B |
Month as a number, an abbreviation (Jul), or in full (July). |
%d %j |
Day of the month, or day of the year. |
%H %M %S |
Hour, minute, second — for schemes that rotate more than once a day. |
%% |
A literal percent sign. |
These are the same strftime fields the profile's timestamp formats
use, so there is no second set to learn.
A compression extension (.gz, .zip, .bz2, .xz) is handled
automatically and must not appear in either name: every scheme appends
it last, after whatever the pattern describes.
The three states of liveName¶
| Value | Use when |
|---|---|
%LIVE% |
A general-purpose profile that cannot know the name — the set is anchored to whatever file was opened. |
A literal name, e.g. access.log |
The profile is written for one application whose live file always has that name. |
| Empty | There is no unnumbered live file: every file is numbered or dated. IIS and java.util.logging work this way. |
Worked examples¶
| Scheme | liveName |
rotatedName |
|---|---|---|
| logrotate | %LIVE% |
%LIVE%.%N |
logrotate with dateext |
%LIVE% |
%LIVE%-%Y%m%d |
| Apache / nginx | access.log |
access.log.%N |
| IIS | (empty) | u_ex%y%m%d.log |
| log4j2 / logback, default rolling | %LIVE% |
app-%Y-%m-%d-%N.log |
| Hourly rotation | %LIVE% |
app-%Y-%m-%d-%H.log |
java.util.logging |
(empty) | java%N.log |
How the order is decided¶
With orderBy: name — the default — the date in the name decides first,
the number breaks ties within a date, and a file with no number is the live
one and therefore newest. indexOrder says which way the numbers run:
higher-is-older for logrotate and spdlog, higher-is-newer for Serilog,
Boost.Log and anything counting upwards as it goes. The name alone cannot
tell the two apart, which is why the profile has to.
The other orderBy values exist for sets whose numbers cannot be trusted.
Cyclic rotation reuses the same handful of numbers for ever, so the
number is a slot, not an age; first-record-time reads each file's first
timestamp instead, and mtime falls back to the filesystem.
If name is chosen and the names turn out to carry neither a date nor a
number, the order falls through to the first record, then to mtime, then
to the plain name — and the status bar says which, because mtime is
rewritten by copying, unpacking, restoring a backup or checking out a
branch.
A date in a file name never becomes a record's timestamp
Dates read out of file names are used for one thing only: putting the
segments in order. Records always take their timestamps from the line
they were parsed from. A file called access.log-20260730 still holds
records from whatever times its own lines state.
Patterns that are refused¶
A pattern with two %N, or with %N directly against a date field, is
rejected when the profile loads and rotation is switched off for it, with
the reason in the diagnostics. app_%N%Y%m%d.log against
app_1220260730.log can be read several ways, and quietly picking one
would mis-order the set with nothing on screen to explain it. Put any
literal character between them — every real-world scheme already does.
The profile editor validates both names as you type, so a refused pattern shows up there rather than at open time.
Splitting by application run¶
A run is one start-to-stop life of the application, and it is not the same thing as a file: rotation by size cuts a run across several files, and a log that only rotates on size can hold several runs in one file.
If the log prints a line when it starts — Volluto logging system
started…, Begin file log, a framework banner — put a regex matching it in
the profile's runStartMarker. Right-clicking a lane in the timeline
then offers Split by application run, and each run becomes its own lane.
The entry only appears when the records really do contain more than one run.
Without a marker the boundaries can only be guessed from gaps in time, and that guess is off by default on purpose. A run is a concept of application logs; nginx, apache, syslog and container logs are continuous services with no runs at all, and a quiet night would otherwise be read as hundreds of restarts. Where the guess is used it adapts to the log's own rhythm and is discarded outright if it produces runs too short to be real.
Time running backwards inside a source is always treated as a boundary and always reported — it means either a restart or segments put in the wrong order, and both are worth knowing about.
Themes¶
Per-level colours come from the theme — the plugin registers a palette category per canonical level, the Theme Editor surfaces them, and any theme can override them. Switching themes updates the level column, the timeline strip, the stats panel and the sequence diagram in lockstep.
See also¶
- Compare / diff — the Diff Compare window that the Log Analyzer's windows are modelled after.
- Logging & diagnostics — the editor's own log file (separate from the Log Analyzer's view of arbitrary log formats).