logana
A fast terminal log viewer for files of any size — including multi-GB logs. Built on SIMD-accelerated line indexing, search, and filtering. Auto-detects log formats, filters by pattern, regex, field value, or date range — bookmark lines, add annotations, and export your analysis.
What is it for
Log files are large and noisy. logana helps you cut through them — filter down to what matters, bookmark key lines, attach notes, and export your findings. Everything is saved between sessions, so you never lose your place. Filter sets can also be saved and reused across files: once you have filters for the key messages and components you care about, loading them on a new file gives you a focused view immediately.
The typical use cases are:
-
Incident investigation — narrow down a multi-gigabyte production log to the relevant window using date-range and pattern filters, mark the key lines, attach notes explaining what you found, and export your findings to Markdown or Jira.
-
Long-running process monitoring — stream a running process or Docker container, watch it in tail mode, and flip back to filter history without losing our place.
-
Recurring log review — save a filter set for a well-known log format (e.g. “show only ERRORs from the auth service”) and reuse it the next time you need it.
What makes it different
Log format detection
logana recognises common log formats automatically — JSON, syslog, journalctl, logfmt, logback, Spring Boot, Python logging, Apache access logs, DLT (AUTOSAR), and more — and shows each line broken into columns: timestamp, level, service name, message. You can hide columns you don’t care about and reorder the ones you do, per file.
Filtering
Include and exclude filters stack freely. Include filters narrow the view to matching lines; exclude filters hide lines on top of that. Both support plain text and regular expressions.
You can also filter by time: > Feb 21 01:00:00, 01:00:00 .. 02:00:00, >= 2024-02-22. Date filters work the same way regardless of which log format is open.
Filtering runs in the background — the UI stays responsive on large files, and changing a filter cancels the previous scan immediately.
Persistent sessions
Filters, scroll position, bookmarks, and notes are saved per file and restored automatically on next open. Filter sets can be exported to a file and loaded on the command line with --filters, so the same filters work across multiple log files. Combined with --tail, the last matching line is shown immediately after loading.
Notes and export
Bookmark individual lines with m. When you want to attach context, select a range with V (line selection) or v (character selection) and press c to write a note. :export produces a document with your notes and the relevant log lines ready to share.
Navigation
Feels like vim. Full motion support: j/k, gg/G, Ctrl+d/u, //? search, n/N between matches, w/b/e word motions, f/t character find, count prefixes on all motions. All keys are configurable.
Feature Overview
| Feature | Description |
|---|---|
| Auto-detected formats | JSON, syslog, journalctl, logfmt, DLT, logback/log4j2, Spring Boot, Python, loguru, Apache CLF, and more |
| Structured columns | Timestamp, level, service, message as separate columns; show/hide/reorder per file |
| Persistent sessions | Filters, scroll position, bookmarks, and notes restored on next open |
| Include/exclude filters | Plain text or regex; include and exclude stack freely |
| Date and time filters | Limit the view to a time window or comparison |
| Background filtering | Runs in the background; changing a filter cancels the previous scan immediately |
| Startup filters | --filters loads a filter set at launch; --tail jumps to the last match |
| Notes and export | Attach comments to lines; export to Markdown or Jira with :export |
| Visual line mode | Select a line range to bookmark, annotate, copy, or build a filter from |
| Visual character mode | Select within a line using vim motions to filter, search, or copy |
| Vim navigation | Full motions: j/k, gg/G, w/b/e, f/t, count prefixes, //? search |
| Multi-tab | Open multiple files, Docker streams, or DLT connections side-by-side |
| Docker | Attach to any running container with :docker |
| DLT | Stream from a DLT daemon with :dlt, or open binary .dlt files directly |
| Value coloring | HTTP methods, status codes, IP addresses, and UUIDs colored automatically; filter colors always take priority and multiple filter styles (fg + bg) compose |
| Configurable | All keys remappable; 19 bundled themes; custom themes and export templates |
Installation
Pre-built Binaries (Recommended)
Download from the Releases page, or use the install script:
Linux / macOS
curl -fsSL https://github.com/pauloremoli/logana/releases/latest/download/install.sh | sh
Windows (PowerShell)
irm https://github.com/pauloremoli/logana/releases/latest/download/install.ps1 | iex
Homebrew (macOS / Linux)
brew tap pauloremoli/logana
brew install logana
Cargo (crates.io)
cargo install logana
Cargo (from source)
cargo install --git https://github.com/pauloremoli/logana
Quick Start
Opening Logs
# Open a file
logana app.log
# Open a directory — pick which files to open from a file picker
logana /var/log/
# Pipe from stdin
journalctl -f | logana
tail -f app.log | logana
# Stream a Docker container
logana # then type :docker
# Preload a saved filter set — filters are applied in a single pass during indexing
logana app.log --filters my-filters.json
# Add inline filters directly on the command line
logana app.log -i error -o debug
logana app.log -i "--field level=ERROR" -t "> 2024-02-21"
# Start at the end of the file with tail mode enabled
logana app.log --tail
# Combined: preload filters and jump to the last matching line immediately
logana app.log --filters my-filters.json --tail
Opening Compressed and Archive Files
logana app.log.gz
logana logs.tar.gz
logana logs.zip
Opening a .gz/.bz2/.xz/.zip/.tar/.tar.gz/.tar.bz2/.tar.xz file — whether from the command line or with :open inside the TUI — shows a popup listing everything inside it as a tree, without extracting anything yet. If an entry is itself an archive (a .zip inside a .tar.gz, for example), one nested level is expanded automatically so its contents show as nested rows too; anything nested deeper than that shows as a collapsed row you can expand yourself.
Spacetoggles the file under the cursor. Toggling a nested archive’s own row selects or deselects everything inside it at once.mmarks the file under the cursor to be merged instead — independently ofSpace, and toggled the same way for a nested archive’s whole subtree.Rightreads and reveals a not-yet-expanded nested archive’s contents (or just reveals them again, with no re-read, if they were already fetched and merely folded shut).Leftfolds an expanded archive’s contents back out of view.a/nselect or deselect every file.Enterextracts the confirmed selection:Space-toggled files each open as their own tab, andm-marked files are extracted and combined into a single timestamp-sorted tab. Both happen together in one press. If anym-marked file’s format can’t be recognized, only the merge is skipped (with an error naming the file) — toggled files still open normally.Esccancels without extracting anything.
First Steps
Once logana opens, you’ll see the log content with the detected format shown in the title bar.
Basic navigation:
j/k— scroll down / up one linegg/G— jump to first / last lineCtrl+d/Ctrl+u— half page down / upq— quit
Add your first filter:
- Press
iand type a pattern to show only matching lines - Press
oand type a pattern to hide matching lines - Press
fto open the filter manager and see all active filters
Search:
- Press
/and type a query to search forward - Press
n/Nto jump between matches
Commands:
- Press
:to open command mode - Type a command and press
Enter(Tab completes commands, flags, and paths)
Navigation
logana uses Vim-style keybindings for all navigation. All bindings are configurable — see Keybindings.
Scrolling
| Key | Action |
|---|---|
j / Down | Scroll down one line |
k / Up | Scroll up one line |
Ctrl+d | Half page down |
Ctrl+u | Half page up |
PageDown | Full page down |
PageUp | Full page up |
gg | Jump to first line |
G | Jump to last line |
Horizontal Scroll
When line wrap is off, long lines can be scrolled horizontally:
| Key | Action |
|---|---|
h / Left | Scroll left |
l / Right | Scroll right |
0 | Jump to start of line (reset horizontal scroll) |
$ | Jump to end of line |
Count Prefix
Prepend a number to most motion keys to repeat them:
5j — scroll down 5 lines
10k — scroll up 10 lines
3Ctrl+d — scroll down 3 half-pages
50G — jump to line 50
3gg — jump to line 3
The active count is shown in the status bar (e.g. [NORMAL] 5). Counts are capped at 999,999.
Go to Line
From command mode, type a bare line number to jump there:
:500 — jump to line 500
:1 — jump to the first line
If the target line is hidden by an active filter, logana jumps to the nearest visible line instead.
Marks
Mark important lines to jump back to them or include them in an export.
| Key | Action |
|---|---|
m | Mark / unmark the current line |
M | Toggle marks-only view (show only marked lines) |
Marked lines show a highlighted indicator in the gutter. Marks are per-session and not persisted across runs.
Visual Selection
| Key | Action |
|---|---|
V | Enter visual line mode — select whole lines for bulk mark / comment / yank / filter |
v | Enter visual char mode — move a cursor within the current line and select a text range |
See Visual Line Mode and Visual Character Mode for the full key reference.
Log Level Navigation
Jump directly between error and warning lines without scrolling:
| Key | Action |
|---|---|
e | Jump to next ERROR / FATAL line |
E | Jump to previous ERROR / FATAL line |
w | Jump to next WARN line |
W | Jump to previous WARN line |
Navigation wraps to the nearest visible line that matches the level. Positions are pre-indexed whenever the visible set changes, so each jump is O(log n) regardless of file size.
Continuation Lines
Multiline entries (stack traces, wrapped messages, or a custom schema’s structured continuation lines) can be collapsed to just their first line:
| Key | Action |
|---|---|
> | Collapse the entry under the cursor |
< | Expand the entry under the cursor |
:collapse and :expand apply the same fold/reveal file-wide instead of to a single entry. >/< still work afterward to flip an individual entry against whatever the file-wide default currently is.
Line Wrap
Toggle line wrapping with :wrap or via the UI menu (u → w). When wrap is enabled, long lines flow onto multiple terminal rows and all viewport math accounts for the extra rows automatically.
Visual Line Mode
Press V in normal mode to enter visual line mode. The current line becomes the anchor.
| Key | Action |
|---|---|
j / k | Extend selection down / up |
c | Attach a comment to the selected lines |
m | Mark / unmark all selected lines (toggles group) |
y | Yank (copy) selected lines to system clipboard |
/ | Open search bar pre-filled with the first selected line |
Esc | Cancel |
Selected lines are highlighted in the log panel.
Visual Character Mode
Press v in normal mode to enter character-level visual mode. The cursor is placed on the current line — at the start of the active search match if one exists, otherwise at column 0. Move the cursor freely with vim motions before anchoring a selection.
Cursor motions
| Key | Action |
|---|---|
h / l / Left / Right | Move left / right one character |
w / b / e | Word start forward / backward / word end |
W / B / E | WORD (whitespace-delimited) variants |
0 | Move to start of line |
^ | Move to first non-blank character |
$ | Move to end of line |
f<c> | Find next occurrence of character c |
F<c> | Find previous occurrence of character c |
t<c> | Move to one before next c |
T<c> | Move to one after previous c |
; | Repeat last f/F/t/T motion |
, | Repeat last motion in reverse |
Anchoring and actions
Press v again to anchor the selection at the current cursor position. Any subsequent cursor motion extends the selection. Without an anchor, actions operate on the single character under the cursor.
| Key | Action |
|---|---|
v | Anchor selection at cursor |
i | Open command bar pre-filled with filter <selected> |
o | Open command bar pre-filled with exclude <selected> |
/ | Open search bar pre-filled with selected text |
y | Yank (copy) selection to system clipboard |
Esc | Cancel |
The selected character range is highlighted with a reversed colour in the log panel. When a f/F/t/T motion is pending (waiting for the target character), the mode bar shows pending — type a character.
Filtering
Filters are the primary way to narrow the log view. They are layered: include patterns narrow the view, and exclude patterns hide matching lines on top of whatever include filters already selected.
Quick Keys
| Key | Action |
|---|---|
i | Add include filter (show only matching lines) |
o | Add exclude filter (hide matching lines) |
f | Open filter manager |
F | Toggle all filtering on/off |
H | Toggle highlight mode (see below) |
How Filters Work
Include filters: If any include filter is enabled, only lines matching at least one include filter are shown.
Exclude filters: Any line matching an enabled exclude filter is hidden, regardless of include filters.
Highlight filters: Apply their color styling to matching lines but never affect visibility — every line stays shown. See Text Filters for details.
No filters: All lines are shown.
All filter types support:
- Text search — fast multi-pattern matching
- Regular expressions — full regex syntax, opt-in with
--regex/-r - Case-insensitive matching — opt-in with
--ignore-case/-i
Highlight Mode
Press H in normal mode to put all active filters — include, exclude, and highlight — into highlight mode: every line in the file stays visible, but filter colors still render on their matches. This is for reading the full context around the lines you actually care about — an include/exclude filter narrows the log down to just the matches, but the surrounding lines that explain why something happened are often not in the filter at all. Highlight mode gives you the whole log back, with your filters still marking what matters, so you can scroll through real context without losing track of what you were looking for. The sidebar title shows [HIGHLIGHT] while it’s active. Press H again to return to normal filtering.
Filter Persistence
Filters are saved to SQLite and automatically restored the next time you open the same file. When you reopen a file, logana detects whether the file has changed (via hash) and prompts you to restore the previous session.
Filter Manager
Press f to open the filter manager popup, which lists all active filters. Navigation matches the log panel: count-prefixed motions, page scrolling, jump-to-top/bottom, and search. A filter row can also be double-clicked directly in the sidebar (without opening the filter manager first) to toggle it.
| Key | Action |
|---|---|
j / k | Move selection down / up — accepts a count prefix, e.g. 4j moves down 4 |
Ctrl+d / Ctrl+u | Half page down / up |
PageDown / PageUp | Full page down / up |
gg / G | Jump to the first / last filter — {count}gg or {count}G jumps to filter N |
/ | Search the filter list (see below) |
Space | Toggle selected filter on/off |
e | Edit selected filter’s pattern |
d | Delete selected filter |
c | Set highlight color for selected filter |
t | Add a date/time range filter |
h | Add a highlight filter |
J / K | Move filter down / up (order affects priority) |
A | Toggle all filters on/off |
C | Clear all filters |
Esc | Close filter manager |
Searching the Filter List
Press / to start typing a search query — the sidebar title immediately shows a type to search... placeholder so it’s clear you’re now typing a query, even before you’ve entered any characters. The query is matched as a regex (case-insensitive) against each filter’s type, pattern, and group, e.g. error|warn narrows to filters whose row text contains either word; an invalid/incomplete regex (likely while still typing, e.g. an unclosed () falls back to a plain substring match instead of matching nothing. Backspace edits the query; j/k move between the narrowed matches. Enter confirms your selection and shows the full list again; Esc cancels and restores whatever was selected before you started searching. While searching, every key is captured as query text — including letters that are normally shortcuts (e, d, i, …) — so none of the usual filter-manager actions fire until you confirm or cancel.
Filter Colors
Each filter can have an optional highlight color. When a filter matches part of a line, that part is colored using the filter’s configured color. Colors are set per-filter with c in the filter manager, or via the :set-color command.
:set-color --fg red
:set-color --fg "#FF5555" --bg "#282A36"
Color values accept:
- Named colors:
black,red,green,yellow,blue,magenta,cyan,white,gray,darkgray,lightred,lightgreen,lightyellow,lightblue,lightmagenta,lightcyan - Hex:
"#RRGGBB"
Style composition
When multiple filters overlap on the same text segment, their fg and bg attributes are composed independently — the highest-priority filter that has fg set contributes the foreground color, and the highest-priority filter that has bg set contributes the background color. So a level filter that sets --fg yellow and a text filter that sets --bg darkgray on the same word will both apply without one canceling the other.
Color priority
Filter colors take priority over automatic value colors (HTTP methods, status codes, IPs, UUIDs) and log-level colors. Value colors are applied only to spans that are not already covered by a filter — they can still appear alongside filter colors on the same line, just not on the same character span. Log-level colors are the lowest-priority fallback and apply only to text that carries no explicit color from any other source.
Filter Groups
Assign filters to a named group to manage several of them together:
:filter --group errors ERROR
:filter --group errors -r "FATAL|CRITICAL"
:exclude --group noise debug
--group/-g <name> works on :filter, :exclude, and :highlight. Toggle every filter in a group on/off together:
:toggle-group errors
A group can also have its own predefined color, used by any filter in the group that doesn’t set its own --fg/--bg:
:group errors --fg Red
:group errors --fg Red --bg Black -l
:group errors --auto # random readable fg/bg pair
:group errors --clear # remove the group's style
:group errors # register the group with no style yet
Groups Sidebar
A Groups section at the bottom of the filter sidebar lists every group with its filter count and style — toggle it on/off from the UI options menu (u → g). Each row shows a [x]/[ ]/[-] status matching the filter list (all enabled / all disabled / mixed). Click a row to select it, double-click to toggle every filter in that group.
Press Ctrl+g in normal mode to enter group management, scoped to the selected group:
| Key | Action |
|---|---|
j / k | Select next / previous group |
Space / A | Toggle every filter in the group on/off |
e | Edit the group’s color style |
x | Clear the group’s color style |
a | Add a new group |
Esc | Exit |
Save and Load Filters
Export the current filter set to a JSON file, and reload it later:
:save-filters my-filters.json
:load-filters my-filters.json
This is useful for sharing filter sets across machines or between log files with similar structure.
File Format
A filter file is a JSON object with a filters array and an optional groups array:
{
"filters": [
{
"id": 0,
"pattern": "error",
"filter_type": "Include",
"enabled": true,
"color_config": { "fg": "Red", "match_only": true },
"use_regex": false,
"ignore_case": true,
"group": "errors"
},
{
"id": 0,
"pattern": "debug",
"filter_type": "Exclude",
"enabled": true
},
{
"id": 0,
"pattern": "@field:level:WARN",
"filter_type": "Highlight",
"enabled": true,
"color_config": { "bg": "#282A36", "match_only": false }
},
{
"id": 0,
"pattern": "@date:> 2024-02-21",
"filter_type": "Include",
"enabled": true
}
],
"groups": [
{ "name": "errors", "color_config": { "fg": "Red" } }
]
}
Each entry in filters:
| Field | Type | Required | Notes |
|---|---|---|---|
id | number | yes | Ignored on load — filters are re-assigned real IDs when imported. Any placeholder value (e.g. 0) works. |
pattern | string | yes | The match text. For field or date filters, this is a special encoded string — see below. |
filter_type | string | yes | One of "Include", "Exclude", "Highlight" (see How Filters Work). |
enabled | boolean | yes | Whether the filter is active. |
color_config | object | no | Highlight color, omit for none — see below. |
use_regex | boolean | no | Treat pattern as a regex. Defaults to false. |
ignore_case | boolean | no | Case-insensitive matching. Defaults to false. Has no effect on field filters. |
group | string | no | Group name, for toggling several filters together — see Filter Groups. |
color_config, when present:
| Field | Type | Required | Notes |
|---|---|---|---|
fg | string | no | Foreground color. |
bg | string | no | Background color. |
match_only | boolean | no | true (default) highlights only the matched text; false highlights the whole line. |
fg/bg in a hand-written filter file only accept ratatui’s 16 built-in color names (Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray, DarkGray, LightRed, LightGreen, LightYellow, LightBlue, LightMagenta, LightCyan, White) or "#RRGGBB" hex — not the extended names (orange, pink, purple, …) that --fg/--bg accept on the command line. Colors set via :set-color/--fg/--bg are always saved back out as one of these two forms, so a file produced by :save-filters never needs the extended names either way.
Each entry in groups:
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Group name, matched against filters’ group field. |
color_config | object | no | Same shape as above — the group’s fallback style. |
enabled | boolean | no | true (default) if the group’s toggle state should start on. |
A bare array of filter objects ([{...}, {...}], with no groups) is also accepted, for files saved before group support was added.
Field and date filter patterns
A plain text/regex filter’s pattern is just the search text. A field filter (see Field Filters) instead stores @field:<key>:<value> — for example @field:level:ERROR matches :filter --field level=ERROR. A date filter (see Date & Time Filters) stores @date:<expression>, using the exact same expression syntax as :date-filter — for example @date:> 2024-02-21 or @date:09:00 .. 17:00.
Only single-condition field filters round-trip through this simple @field:key:value form. Field filters combining several --field conditions and/or trailing free text use an internal encoding not meant to be hand-written — create those in the TUI or with :filter --field ... and use :save-filters to export them instead.
Inline Filters at Startup
Add filters directly on the command line without creating a JSON file first:
| Flag | Short | Purpose |
|---|---|---|
--include <args> | -i | Add include filter |
--exclude <args> | -o | Add exclude filter |
--timestamp <args> | -t | Add date/time range filter |
The argument string passed to each flag accepts exactly the same options as the corresponding TUI command (:filter, :exclude, :date-filter):
# Simple pattern
logana app.log -i error -o debug
# Field-scoped filter
logana app.log -i "--field level=ERROR"
# Include filter with highlight color (flags before pattern)
logana app.log -i "--bg red error"
# Case-insensitive include filter (the outer -i is --include; the inner
# --ignore-case is the :filter flag documented in Text Filters)
logana app.log -i "--ignore-case error"
# Date range filter
logana app.log -t "> 2024-02-21"
# Combined
logana app.log -i error -o debug -t "01:00 .. 02:00"
All flags can be repeated. Inline filters are applied after any --filters file. Invalid argument strings are rejected before the TUI opens.
Preloading Filters at Startup
Pass --filters (or -f) on the command line to apply a saved filter set before the TUI opens:
logana app.log --filters my-filters.json
The filters are evaluated in a single pass during file indexing, so the filtered view is ready as soon as loading completes — no separate computation step. The same filters remain active for interactive use once the TUI is open (you can add, remove, or edit them normally).
Combined with --tail, the last matching line is shown immediately after loading:
logana app.log --filters errors.json --tail
Tip: Save your most-used filter sets with
:save-filtersonce, then reuse them from the command line.
Sections
- Text Filters — include/exclude patterns, regex syntax
- Date & Time Filters — timestamp-based range and comparison filters
- Field Filters — match against specific parsed fields (level, message, component, …)
Text Filters
Text filters match against the raw content of each log line.
Adding Filters
From normal mode:
- Press
ito add an include filter (opens command mode pre-filled withfilter) - Press
ato add an include filter with an automatically generated, readable color pair (opens command mode pre-filled withfilter --auto) - Press
oto add an exclude filter (opens command mode pre-filled withexclude)
From the filter manager (f):
- Press
ito add an include filter,afor one with an automatically generated color pair - Press
hto add a highlight filter (opens command mode pre-filled withhighlight)
From command mode:
:filter <pattern> # show only lines matching pattern
:exclude <pattern> # hide lines matching pattern
:highlight <pattern> # color matching lines without affecting visibility
Text Search
The default mode. Fast multi-pattern scanning. Case-sensitive. Multi-word patterns work without quotes.
:filter ERROR
:filter connection refused
:filter 500 Internal Server Error
:filter database connection pool exhausted
Regex Filters
Opt in with --regex / -r. Supports full regex syntax. Words after -r are joined, so spaces in the pattern do not need quoting.
:filter -r (ERROR|WARN) # errors and warnings together
:filter -r (timeout|connection refused) # any connectivity failure
:filter -r authentication failed.*user # auth failures with user context
:filter -r response time: [5-9]\d{3}ms # slow responses over 5 seconds
Flag ordering: Options (
-r,-i,--fg,--bg,-l,--field,--auto) must appear before the pattern. Everything after the first pattern word is part of the pattern.:filter --fg red -r timeout.*retry # correct :filter timeout.*retry --fg red # wrong — "--fg" becomes part of the pattern
Case-Insensitive Filters
Opt in with --ignore-case / -i. Works with both text search and --regex, and combines with color/group flags the same way --regex does.
:filter --ignore-case error # matches "error", "ERROR", "Error", ...
:filter -i -r (error|warn) # case-insensitive regex
:exclude --ignore-case debug
--ignore-case has no effect on --field key=value filters (matching a parsed field is always case-sensitive), same as --regex. A case-insensitive filter shows an [i] tag in the filter sidebar.
Highlight Filters
A third filter kind alongside include/exclude. Highlight filters apply their color styling to matching lines but never hide or reveal anything — every line stays exactly as visible as it would be without the filter.
:highlight <pattern> # color matches, alias :h
:highlight -r (ERROR|WARN) # regex highlight
:highlight --fg yellow ERROR # with a color, same flags as :filter
Highlight filters accept the same flags as :filter (--regex/-r, --ignore-case/-i, --fg, --bg, -l, --field, --group, --auto/-a) and show up in the filter sidebar with an H type tag, e.g. [x] H: ERROR (12). They’re for marking the lines you care about while reading the log in full — mark an event, then keep scrolling to see everything around it, without an include/exclude filter narrowing the view down to just the matches.
To put your existing include/exclude filters into the same visible-but-marked state temporarily — for example when you need to see the full context around what they’re currently hiding — see Highlight Mode.
Multiple Filters
You can add as many filters as you like. They combine as follows:
- Include filters — a line must match at least one enabled include filter to be shown (if any exist).
- Exclude filters — a line matching any enabled exclude filter is hidden.
- Highlight filters — never affect which lines are shown, only their styling.
Exclude takes priority: a line that satisfies an include filter but also matches an exclude filter is hidden.
Toggling Filters
- In the filter manager (
f), pressSpaceto enable/disable individual filters. - Press
Fin normal mode to toggle all filtering on/off instantly (useful for comparing filtered vs. unfiltered view). - Press
Ain the filter manager to enable/disable all filters at once.
Filter Groups
Assign a filter to a named group with --group <name> when adding it:
:filter --group errors ERROR
:filter --group errors FATAL
:exclude --group noise health.?check --regex
Toggle every filter in a group on/off together:
:toggle-group errors
If any filter in the group is enabled, this disables the whole group; otherwise it enables the whole group. Group names autocomplete from existing filters.
Grouped filters show their group name in brackets in the filter sidebar, e.g. [x] In: [errors] ERROR (12).
A group can also have its own predefined color, used by any filter in the group that doesn’t set its own --fg/--bg/-l:
:group errors --fg Red --bg Black
:group errors --auto
:group errors --clear
--auto generates a random readable color pair, same as :filter --auto. --clear removes the group’s style. A filter’s own color always takes priority over its group’s — only filters with no color of their own fall back to it. Groups can be styled before any filter uses them.
A style is optional — :group <name> with no flags registers the group with no predefined color, useful when you just want it to show up (and be manageable) in the sidebar’s Groups section ahead of assigning any filters to it:
:group errors
The [groupname] tag in the sidebar is also colored with the group’s style when it has one, regardless of whether the filter itself has its own color.
Groups Section
Every known group also gets its own row in a Groups section at the bottom of the sidebar, below the filter list, under a Groups [n] label. Each row shows the group’s name and how many filters belong to it, e.g. errors (2). A group with a predefined style renders in that color.
Click a group row, or press Ctrl+g in normal mode, to enter group management:
| Key | Action |
|---|---|
j / k | Move to the next/previous group |
A | Toggle every filter in the group on/off together (same as :toggle-group) |
e | Edit the group’s color, prefilling :group <name> --fg ... |
x | Clear the group’s predefined style |
a | Add a new group, opening :group for you to type a name |
Esc | Exit back to normal mode |
With no groups yet, Ctrl+g still enters group management so a can create the first one.
Press g in :ui mode to toggle the Groups section on/off.
Highlight Colors
Each include filter highlights its matching byte spans in the log line. The color is configurable per filter. When no color is set, logana uses a default highlight style from the active theme.
To set a color for the currently selected filter in the filter manager, press c, then use :set-color:
:set-color --fg yellow
:set-color --fg "#FF5555" --bg "#44475A"
By default, only the matched portion of the line is colored. To highlight the entire line instead, pass -l when adding the filter:
:filter -l ERROR # highlight the full line for every ERROR match
:filter --fg red -l ERROR # full-line red highlight
The -l flag can also be applied later with :set-color -l from the filter manager.
Pass --auto/-a instead of --fg/--bg to generate a random color pair with guaranteed readable contrast, rather than picking one yourself:
:filter --auto ERROR
--auto cannot be combined with --fg/--bg.
When multiple filters overlap on the same span, their fg and bg are composed: one filter can contribute the foreground color while another contributes the background. Automatic value colors (HTTP methods, status codes, IPs, UUIDs) apply only to spans not already colored by a filter, and log-level colors are the lowest-priority fallback.
Editing Filters
Editing a filter’s pattern or color from the filter manager (e to edit pattern, c to change color) updates it in-place. The filter keeps its current position in the list — order is never changed by an edit.
Date & Time Filters
Date filters narrow the visible lines by their parsed timestamp. They work as a post-processing step after text filters — only lines already passing text filters are checked against date filters.
Adding a Date Filter
From the filter manager (f → t): opens command mode pre-filled with date-filter .
From command mode:
:date-filter <expression>
Expression Syntax
Equals (no operator)
Omitting an operator matches the full period implied by the input’s granularity.
| Input | Matches |
|---|---|
09:00 | the whole minute 09:00:00 – 09:00:59 |
09:00:30 | the exact second 09:00:30 |
Feb 21 | all of Feb 21 (00:00:00 – 23:59:59) |
Feb/21 | same — / is accepted as month/day separator |
02/21 | same — numeric month/day |
02-21 | same — numeric month-day |
02/21/2024 | all of Feb 21 2024 |
02-21-2024 | same with dash separators |
2024-02-21 | all of Feb 21 2024 |
2024-02-21 10:15 | the whole minute 10:15:00 – 10:15:59 |
2024-02-21 10:15:30 | the exact second |
:date-filter Feb/21
:date-filter 02/21
:date-filter 02-21
:date-filter 09:00
Range (..)
Both bounds are inclusive. Spaces around .. are optional.
The upper bound is expanded to the end of its granularity period: a day-level upper bound covers up to 23:59:59.999999, a minute-level upper bound covers up to :59.999999, and a second-level upper bound is exact.
# time-only (compares seconds since midnight)
:date-filter 09:00 .. 17:00 # 09:00:00 – 17:00:59
:date-filter 09:00..17:00 # same, no spaces required
:date-filter 09:00:00 .. 17:00:00 # exact seconds
# BSD month names
:date-filter Feb 21 .. Feb 22 # Feb 21 00:00:00 – Feb 22 23:59:59
:date-filter Feb/21 .. Feb/22
# numeric month/day
:date-filter 02/21 .. 02/22 # Feb 21 00:00:00 – Feb 22 23:59:59
:date-filter 02-21 .. 02-22
:date-filter 03-21..03-25 # no spaces
# ISO dates
:date-filter 2024-02-21 .. 2024-02-22
# full datetimes (second-exact bounds, no expansion)
:date-filter 2024-02-21T10:00:00 .. 2024-02-21T11:30:00
:date-filter 2024-02-21 10:00:00 .. 2024-02-21 11:30:00
Comparison operators
:date-filter > 2024-02-21T10:00:00 # after
:date-filter >= Feb 21 10:00:00 # from (inclusive)
:date-filter < 02/22 # before Feb 22
:date-filter <= Feb 22 # up to and including
Supported operators: >, >=, <, <=
Accepted Date/Time Formats
Date bounds
| Format | Example | Year |
|---|---|---|
| BSD month name + day | Feb 21, Feb/21 | none (month/day only) |
| Numeric MM/DD | 02/21 | none |
| Numeric MM-DD | 02-21 | none |
| Numeric MM/DD/YYYY | 02/21/2024 | included |
| Numeric MM-DD-YYYY | 02-21-2024 | included |
| ISO date | 2024-02-21 | included |
Time bounds
| Format | Example | Granularity |
|---|---|---|
HH:MM | 09:00 | minute |
HH:MM:SS | 09:00:30 | second |
Combined datetime bounds
Any date format above followed by a space and a time:
Feb/21 09:00
02/21 09:00:30
02-21-2024 10:15
2024-02-21T10:15:30
2024-02-21 10:15:30
ISO 8601 T separator and a plain space are both accepted.
Rules and Limitations
- Inclusive bounds:
..ranges include both endpoints (>=lower AND<=upper). - No midnight wraparound:
23:00 .. 01:00is invalid. Use two comparison filters instead. - Mixed-mode ranges are rejected: both sides of a
..must use the same format (both time-only or both date). - Multiple date filters are OR-ed: a line passes if it satisfies any enabled date filter.
- Lines without a timestamp pass through: continuation lines, stack traces, and multi-line messages are never hidden by date filters.
- Requires a detected format parser: if logana cannot detect the log format, date filters return an error. This means plain-text logs without timestamps cannot be date-filtered.
Display
Date filters appear in the filter manager and sidebar as Date: <expression>, not as raw @date: patterns.
How It Works
Date filters are stored as regular FilterDef entries in the database with an @date: prefix in the pattern field (e.g. @date:01:00:00 .. 02:00:00). They are excluded from the text-filter pipeline and applied separately in refresh_visible() after text filters run, via retain() on visible_indices.
Timestamps are normalized to a canonical YYYY-MM-DD HH:MM:SS.ffffff string before comparison, so all supported log format timestamps (ISO 8601, BSD, logback datetime, CLF, journalctl, Apache error, etc.) are comparable regardless of their original format.
Field Filters
Field filters let you narrow the log view by the value of a specific parsed field rather than matching against the raw line text. This is useful when you want to, for example, show only error-level lines without accidentally matching the word “error” in a message body.
Syntax
:filter --field <key>=<value>
:exclude --field <key>=<value>
The --field flag tells logana to treat the pattern as a key=value pair. The value is matched as a substring of the named field.
:filter --field level=error # show only lines where level contains "error"
:filter --field component=auth # show only lines from the auth component
:exclude --field level=debug # hide all debug-level lines
--field can be repeated within a single command to require several fields at once, and combined with trailing free text that must also match — all AND’d together in one filter:
:filter --field level=INFO --field component=Draco Power measurements:
# shows only lines where level contains "INFO" AND component contains "Draco"
# AND the line contains "Power measurements:"
Field Name Aliases
The following short aliases are recognised regardless of how the field is named in the raw log:
| Alias(es) | Field |
|---|---|
level, lvl | log level |
timestamp, ts, time | timestamp |
target | logger / target name |
message, msg | log message body |
| anything else | looked up by exact key in extra fields |
For example, :filter --field lvl=warn and :filter --field level=warn are equivalent.
Combining Field Filters
There are two distinct ways to combine field conditions, with different logic:
Multiple --field flags in one command — AND logic. Every condition (and any trailing text) must match:
:filter --field level=error --field component=auth
# only lines where level contains "error" AND component contains "auth"
Multiple separate :filter commands — OR logic, same as any other include filters. Each broadens what’s visible:
:filter --field level=error
:filter --field level=warn
# shows lines where level contains "error" OR level contains "warn"
Exclude field filters — hide any line where the field matches:
:exclude --field level=debug
Mixed include and exclude — exclude takes priority. A line that satisfies an include filter but also matches an exclude filter is hidden.
Pass-Through Behaviour
Lines that cannot be parsed (e.g. plain-text lines in an otherwise structured file) are always shown — they are not hidden by field filters. The same applies when the named field is absent from an otherwise parseable line.
This matches the behaviour of date filters for lines without timestamps.
Sidebar Display
Field filters appear in the filter manager sidebar with a [field] tag. A filter with multiple --field conditions and/or trailing text shows all of them, comma-separated:
[x] In: level=error [field]
[x] Out: level=debug [field]
[x] In: level=INFO, component=Draco, Power measurements: [field]
Group-Scoped Fields
A custom schema can declare a repeating group of sub-records (e.g. a batch job’s workers). Filter on a field inside any item of the group with <group>.<field>=<value> — it matches if any item in the group has that field:
:filter --field workers.hostname=worker-3
# shows records where any worker's hostname contains "worker-3"
This is “any item matches” semantics, independent from plain field lookup — an indexed path like workers.0.hostname (as shown in the structured fields columns) is display-only and can’t be used as a filter path.
Requires a Detected Format
Field filters only have an effect when logana has detected a structured log format (JSON, logfmt, syslog, etc.). On plain-text files with no detected format, all lines pass through field filters unchanged.
See Log Formats for the list of supported formats.
Search
Search operates on visible lines only — it respects active filters and only scans lines that are currently shown.
Keybindings
| Key | Action |
|---|---|
/ | Search forward |
? | Search backward |
n | Jump to next match |
N | Jump to previous match |
Usage
Press / or ? to open the search bar at the bottom of the screen. Type your query and press Enter. logana highlights all matches on visible lines and scrolls to the first match.
nwraps around to the first match after the last line.Nwraps around to the last match before the first line.
Pattern Syntax
Search uses full regex syntax. Examples:
/NullPointerException plain text
/user 42 .* failed regex — activity for a specific user
/POST /api/orders.* 5\d\d regex — failed order requests
/^2024-06-15T14 lines from a specific hour
Case Sensitivity
By default, search is case-sensitive. Case sensitivity can be toggled programmatically via the Search API (no UI toggle yet — the default behavior is case-sensitive matching).
Match Highlighting
Matched byte spans are highlighted with the search style (distinct from filter highlight colors). Search highlights take priority over filter highlights — if a search match overlaps a filter-colored span, the search color wins.
The current match (the one n/N is positioned on) is rendered with a distinct highlight color to distinguish it from other occurrences on screen.
When wrap is disabled, navigating to a match with n or N also adjusts the horizontal scroll to center the matched span in the viewport.
Search vs. Filters
| Search | Filter | |
|---|---|---|
| Persisted | No | Yes |
| Affects visible lines | No | Yes |
| Highlighted | Yes | Yes |
| Navigation (n/N) | Yes | No |
| Regex support | Yes | Yes |
Use filters to permanently narrow the view. Use search to navigate through specific patterns within the already-filtered view.
Structured Fields
When logana detects a structured log format (JSON, logfmt, syslog, tracing-subscriber, etc.), it parses each line into named columns: timestamp, level, target, span, and message, plus any extra fields specific to the format.
Columns
| Column | Description |
|---|---|
timestamp | Parsed log timestamp |
level | Normalized log level (TRACE, DEBUG, INFO, WARN, ERROR, FATAL) |
target | Logger name, module path, or source identifier |
span | Tracing span context (name + fields), if present |
message | The log message body |
| extra fields | Format-specific extras (e.g. pid, thread, hostname, request_id) |
Showing and Hiding Columns
Use :select-fields to open an interactive column picker:
j/k— navigateSpace— toggle column on/offJ/K— reorder columnsa— enable alln— disable allr— reset to the default order with everything visible (clears any reorder and any hidden fields — still requiresEnterto apply, likea/n)Enter— applyEsc— cancel
Or use commands directly:
:hide-field span # hide a single column
:show-field span # show a previously hidden column
:show-all-fields # reset to default display — clears both hidden fields and any custom column order
Use :select-fields for reordering columns or changing several at once.
Field Key Display
Extra fields and span fields carry both a key and a value. By default logana shows only the values to keep lines compact. Use :show-keys to include the key names:
:show-keys # request_id=abc123 status=200 request: method=GET uri=/api/users
:hide-keys # abc123 200 request: GET /api/users (default)
This applies to all structured formats — JSON extra fields, logfmt pairs, syslog structured data, span fields, and any other key-value extras that don’t map to a canonical column (timestamp, level, target, message). This setting is persisted per file in the session database.
Span Fields
Span context is parsed from formats that carry it (tracing-subscriber JSON, tracing-subscriber fmt text, and others). The span column shows the span name followed by its fields:
request: GET /api/users # hide-keys (default)
request: method=GET uri=/api/users # show-keys
Span sub-fields can also be selected as individual columns:
:fields timestamp level span.method span.uri message
Repeating Groups
A custom schema can declare a repeating group of sub-records inside a multiline entry (e.g. a batch job’s workers). Each item shows as its own indexed column, e.g. workers.0.hostname, workers.1.hostname. Filter across every item in a group at once with a group-scoped field filter — see Field Filters.
Value Coloring
Even within structured columns, known value patterns are colored automatically:
- HTTP methods — GET (green), POST (yellow), PUT (blue), DELETE (red), PATCH (magenta)
- HTTP status codes — 2xx (green), 3xx (cyan), 4xx (yellow), 5xx (red)
- IP addresses — IPv4 and IPv6
- UUIDs
Configure which categories are colored via :value-colors.
Tab Completion for Field Names
The :hide-field / :show-field commands complete against the field names discovered from the first 200 visible log lines, so you don’t need to remember exact field names.
Annotations & Export
Annotations let you attach multiline comments to log lines and export an analysis report. This is useful for incident investigations, code reviews, and sharing findings with your team.
Visual Selection
Use Visual Line Mode (V) to select whole lines, or Visual Character Mode (v) to select text within a line. From either mode you can attach a comment, mark lines, copy to clipboard, or build a filter.
Adding a Comment
With lines selected in visual mode, press c to open the comment editor:
- Type your multiline comment
Enter— insert new lineBackspace— delete character / merge linesLeft/Right— move cursor (wraps between lines)Up/Down— move between rowsCtrl+s— save the commentEsc— cancel without saving
After saving, annotated lines show a ◆ marker in the gutter.
Editing and Deleting Comments
In normal mode, move to an annotated line and:
| Key | Action |
|---|---|
r | Open the comment editor pre-filled with the existing text |
d | Delete the comment on the current line |
Inside the editor, Ctrl+D also deletes the comment.
In normal mode, c opens the comment editor for the current line directly (without entering visual mode first).
Press C in normal mode to clear all marks and comments for the current tab.
Marks
Press m to mark the current line. Marked lines are included in exports even without a comment attached. Press M to toggle a marks-only view.
Exporting
Export all annotations and marked lines to a file:
:export report.md # Markdown (default)
:export report.md -t jira # Jira wiki markup
:export report.md -t <template> # custom template
The export includes:
- A header with the filename and export date
- Each comment group with the commented log lines and the comment text
- Any standalone marked lines (without a comment) grouped consecutively
Export Window
When the selected template’s footer section contains any {{placeholder}} variables, :export opens a window before writing the file so you can fill in those sections interactively. Both bundled templates (markdown and jira) include {{conclusion}} and {{next_steps}} by default, and any custom placeholder name works the same way.
| Key | Action |
|---|---|
Tab / Shift+Tab | Switch between Conclusion and Next Steps |
Enter | Insert a new line |
Backspace | Delete character before cursor / merge lines |
Delete | Delete character at cursor / merge next line |
Left / Right | Move cursor (wraps between lines) |
Up / Down | Move between rows |
Ctrl+S | Write the file |
Esc | Cancel without writing |
The active field scrolls to keep the cursor visible when content exceeds the window height.
Export Templates
Two templates are bundled: markdown and jira. Custom templates can be placed in ~/.config/logana/templates/.
Template syntax:
{{#header}}
# Analysis: {{filename}}
Date: {{date}}
{{/header}}
{{#comment_group}}
{{lines}}
{{commentary}}
{{/comment_group}}
Available placeholders:
| Placeholder | Content |
|---|---|
{{filename}} | Source file name |
{{date}} | Export date |
{{lines}} | The annotated log lines, each prefixed with its 1-based line number |
{{commentary}} | The comment text |
{{conclusion}} | Conclusion text (footer) |
{{next_steps}} | Next steps text (footer) |
Any {{custom_name}} placeholder you add to the footer section becomes an editable field in the export window. Use underscores for multi-word names ({{root_cause}} → “Root Cause”).
Template sections: header (rendered once), comment_group (rendered per annotation/mark group), footer (optional, rendered once at the end).
User templates in ~/.config/logana/templates/ shadow bundled ones by name. Tab completion lists all available templates.
Docker Logs
logana can stream logs from any running Docker container directly in the terminal, with the same filtering, search, and annotation features available for file-based logs.
Opening a Container Stream
From normal mode, type:
:docker
A picker lists all running containers. Navigate with j / k and press Enter to attach. The stream opens in a new tab.
| Key | Action |
|---|---|
j / k | Navigate container list |
Enter | Attach to selected container |
Esc | Cancel |
Auto-Reconnect
If the connection to a Docker container fails or drops, logana retries automatically with increasing backoff. The tab name shows [RETRY #N] while reconnecting.
Session Persistence
Docker tabs are persisted across sessions. When you reopen logana, it automatically re-attaches to any Docker containers that were open in the previous session, by container name. The source identifier stored in the session database is docker:<container-name>.
Tail Mode
Docker tabs benefit from tail mode — when enabled, the view auto-scrolls to show new log entries as they arrive:
:tail # toggle tail mode on/off
When tail mode is active, [TAIL] appears in the log panel title.
Filtering and Annotations
All filter, search, and annotation features work identically for Docker streams. Filters are persisted per container name, just like file-based logs.
Piping Docker Compose Logs
You can also pipe docker compose logs directly into logana:
docker compose logs -f 2>&1 | logana
The 2>&1 redirect is important — without it, Docker’s warnings (e.g. unset variable notices) go straight to the terminal and corrupt the TUI display. Merging stderr into stdout ensures everything flows through the pipe and appears as log entries inside logana, where you can filter them as needed.
To suppress the warnings entirely instead:
docker compose logs -f 2>/dev/null | logana
Requirements
- Docker must be installed and accessible via
dockerinPATH. - The
docker pscommand must return running containers. - Logs are streamed via
docker logs -f <container-id>, with stdout and stderr merged.
DLT Streaming
logana can connect to a running DLT daemon over TCP and stream log messages in real time, with the same filtering, search, and annotation features available for file-based logs.
Opening a DLT Stream
From normal mode, type:
:dlt
A picker lists configured DLT devices. Navigate with j / k and press Enter to connect. The stream opens in a new tab.
| Key | Action |
|---|---|
j / k | Navigate device list |
Enter | Connect to selected device |
a | Add a new device inline |
Esc | Cancel |
Configuring Devices
DLT devices can be configured in ~/.config/logana/config.json:
{
"dlt_devices": [
{ "name": "local", "host": "127.0.0.1", "port": 3490 },
{ "name": "target-ecu", "host": "192.168.1.100", "port": 3490 }
]
}
The default port is 3490. Devices can also be added from the selection panel by pressing a.
Opening DLT Binary Files
DLT binary files (.dlt) are opened like any other log file:
logana trace.dlt
Three binary layouts are detected automatically: storage format (with DLT\x01 magic), wire format (concatenated messages without storage headers), and simplified format.
Auto-Reconnect
If the connection to the DLT daemon fails or drops, logana retries automatically with increasing backoff (0s, 2s, 5s, 10s). The tab name shows [RETRY #N] while reconnecting, and the error details appear in the status bar. Once the connection is re-established, streaming resumes normally.
Session-restored DLT tabs also reconnect automatically without blocking the UI.
Session Persistence
DLT tabs are persisted across sessions. When you reopen logana, it reconnects to any DLT daemons that were open in the previous session. The source identifier stored in the session database is dlt://host:port.
Tail Mode
DLT streams benefit from tail mode — when enabled, the view auto-scrolls to show new log entries as they arrive:
:tail
Fields
DLT messages expose the following fields for filtering and display:
| Field | Description |
|---|---|
timestamp | Wall-clock time (streaming) or relative time (file) |
hw_ts | Hardware timestamp counter |
mcnt | Message counter (0-255) |
ecu | ECU identifier |
apid | Application ID (shown as target) |
ctid | Context ID |
type | Message type (log, trace, network, control) |
subtype | Sub-type (fatal, error, warn, info, debug, verbose) |
mode | Verbose or non-verbose |
OTel Collector
logana can receive OpenTelemetry logs in real time over gRPC or HTTP/JSON, turning it into a live OTel log viewer with the same filtering, search, and annotation features available for file-based logs.
Starting a Receiver
From normal mode, type:
:otel # gRPC on port 4317 (default — matches OTel SDK defaults)
:otel --http # HTTP/JSON on port 4318
:otel 4317 # gRPC on a custom port
:otel --http 4318 # HTTP/JSON on a custom port
The receiver opens in a new tab and listens for incoming log export requests. Logs appear as they arrive.
Transport Modes
| Mode | Command | Default Port | Protocol |
|---|---|---|---|
| gRPC | :otel | 4317 | OTLP/gRPC (protobuf) |
| HTTP/JSON | :otel --http | 4318 | OTLP/HTTP (JSON or protobuf) |
gRPC (default)
The gRPC receiver accepts ExportLogsServiceRequest messages on port 4317. This matches the default export protocol used by most OTel SDKs.
The server runs in plaintext mode (no TLS). Configure your SDK to use an insecure connection:
# Environment variable (works for all OTel SDKs)
OTEL_EXPORTER_OTLP_INSECURE=true
# Or use the http:// scheme in the endpoint URL
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
HTTP/JSON
The HTTP receiver accepts POST /v1/logs with application/json or application/x-protobuf content types, and handles gzip-compressed request bodies.
Auto-Reconnect
If the receiver encounters an error on startup (e.g. port already in use), logana reports the error in the tab. Fix the conflict and reopen with :otel again.
Session Persistence
OTel collector tabs are persisted across sessions. When you reopen logana, it automatically restarts the receiver on the same port. The source identifier stored in the session database is otlp-grpc://<port> (gRPC) or otlp://<port> (HTTP).
Parsed Fields
Logs received over OTLP are parsed with the same OTel parser used for file-based OTLP logs:
| Field | Source |
|---|---|
| Timestamp | timeUnixNano |
| Level | severityNumber / severityText |
| Message | body.stringValue |
| Target | service.name, code.namespace, logger (from resource or log attributes) |
| Extra fields | All other resource attributes and log attributes |
Multi-Tab
logana supports multiple tabs, each showing an independent log file, directory, stdin stream, or Docker container.
Tab Keybindings
| Key | Action |
|---|---|
Tab | Switch to next tab |
Shift+Tab | Switch to previous tab |
Ctrl+t | Open a new (empty) tab |
Ctrl+w | Close the current tab |
Ctrl+p | Open a searchable popup to switch between open files |
A tab can also be clicked directly in the tab bar to switch to it.
Opening Files in Tabs
From the command line, a file argument opens in its own tab; a directory argument shows a picker to choose which files to open (not yet supported for multiple positional args):
logana /var/log/ # shows a picker — pick which files to open, each in its own tab
From within logana, use the :open command:
:open app.log # opens in the current tab
:open /var/log/ # shows the same picker (directory)
Tab State
Each tab maintains completely independent state:
- Scroll position and viewport
- Active filters (with their colors and enabled/disabled states)
- Search query
- Marks and annotations
- Detected log format
- Field layout (visible columns and order)
- Display flags (wrap, sidebar, tail mode, show-keys)
Session Restore
When you close logana and reopen it without arguments, it prompts to restore the previous session — reopening all tabs that were open at exit, with their per-tab state restored. Docker tabs are re-attached by container name.
Merged View
:merge opens a source-selection popup where you can choose any combination of open tabs. Confirming creates a new merged(N) tab that interleaves all selected sources sorted by timestamp — no data is copied.
:merge # open source-selection popup
In the merged tab each line is prefixed with the title of the tab it came from.
The merged tab stays live: as the source tabs receive new lines, the merged index is extended and re-sorted automatically. You can pause or stop updates with the usual commands:
:pause # pause live updates for the merged tab
:resume # resume live updates
:stop # stop live updates permanently for the merged tab
Filters, search, marks, and annotations all work the same as on any other tab.
Files inside a .zip/.tar.gz/etc archive can be merged directly without opening them as separate tabs first — mark them with m in the archive picker instead of Space. See Opening Compressed and Archive Files. Unlike this live merged(N) tab, an archive-picker merge is a one-shot snapshot of the extracted files (there’s no live source tab to poll for new lines).
Tail Mode Per Tab
Each tab can independently have tail mode enabled or disabled:
:tail # toggle tail mode for the current tab
When tail is active for a tab, [TAIL] appears in that tab’s log panel title.
MCP Server
logana includes an embedded Model Context Protocol (MCP) server. When enabled, it exposes marked lines and annotations as MCP resources and provides tools so AI assistants can interact with your log analysis session in real time.
Starting the Server
On launch
logana app.log --mcp # default port 9876
logana app.log --mcp 8080 # custom port
From inside the TUI
:enable-mcp # default port 9876
:enable-mcp --port 8080 # custom port
:disable-mcp # stop the server
The server listens at http://localhost:<port>/mcp using the Streamable HTTP transport.
Default Port in Config
Set a persistent default port in ~/.config/logana/config.json:
{
"mcp_port": 9876
}
When both the config and a --port flag are present, the config value takes precedence.
Resources
| URI | Description |
|---|---|
logana://marks | All marked lines — one entry per line formatted as <line_number>: <text> |
logana://annotations | All annotations — each block shows the 1-based line numbers and the comment text |
Resources are updated every render frame so the MCP client always sees the current state of the active tab.
Tools
| Tool | Parameters | Description |
|---|---|---|
toggle_mark | line_index (1-based) | Mark or unmark a log line |
add_annotation | text, line_indices (1-based list) | Attach a comment to one or more lines |
remove_annotation | index (0-based) | Remove an annotation by its position in the list |
Tool calls are applied to the active tab and immediately reflected in the TUI.
Connecting an AI Assistant
Point your MCP client at the server endpoint. For example, to use it with Claude Desktop, add an entry to your claude_desktop_config.json:
{
"mcpServers": {
"logana": {
"url": "http://localhost:9876/mcp"
}
}
}
Once connected, the assistant can read your marked lines and annotations and call tools to mark or annotate lines on your behalf.
Commands
CLI Flags
These flags are passed when launching logana from the shell:
| Flag | Description |
|---|---|
<file> | File or directory to open. Omit to read from stdin. |
-f, --filters <path> | Preload a saved filter set (JSON). Filters are applied in a single pass during indexing and remain active for interactive use. |
-i, --include <args> | Add an include filter. Accepts the same arguments as :filter. May be repeated. Examples: -i "error", -i "--field level=ERROR" |
-o, --exclude <args> | Add an exclude filter. Accepts the same arguments as :exclude. May be repeated. Examples: -o "debug", -o "--field level=debug" |
-t, --timestamp <args> | Add a date/time range filter. Accepts the same arguments as :date-filter. May be repeated. |
--tail | Start at the end of the file and enable tail mode. Combined with --filters, the last matching line is available immediately after loading. |
--mcp [PORT] | Start the embedded MCP server on launch. Port defaults to 9876. See MCP Server. |
--headless | Run without TUI — apply filters and write matching lines to stdout or --output. |
--output <path> | Write headless output to a file instead of stdout. Requires --headless. |
In-App Commands
Press : in normal mode to open command mode. Tab completes commands, flags, colors, themes, and file paths. Command history is navigable with Up / Down.
Filtering
| Command | Description |
|---|---|
:filter [--regex|-r] [--ignore-case|-i] [-l] [--fg COLOR] [--bg COLOR] <pattern> | Add an include filter (show only matching lines) |
:filter --field <key>=<value> | Add a field-scoped include filter (e.g. level=error); repeat to require several fields at once |
:filter --group|-g <name> <pattern> | Assign the filter to a named group, toggleable together via :toggle-group |
:filter --auto|-a <pattern> | Add an include filter with a randomly generated, readable fg/bg color pair instead of specifying --fg/--bg |
:exclude [--regex|-r] [--ignore-case|-i] <pattern> | Add an exclude filter (hide matching lines) |
:exclude --field <key>=<value> | Add a field-scoped exclude filter (e.g. level=debug) |
:exclude --group|-g <name> <pattern> | Assign the exclude filter to a named group |
:highlight [--regex|-r] [--ignore-case|-i] [-l] [--fg COLOR] [--bg COLOR] <pattern> (alias :h) | Add a highlight filter — colors matches without affecting visibility |
:highlight --auto|-a <pattern> | Add a highlight filter with a randomly generated, readable fg/bg color pair |
:date-filter <expr> | Add a date/time range filter |
:set-color [--fg COLOR] [--bg COLOR] | Set highlight color for the selected filter |
:toggle-group <name> | Toggle every filter in a named group on/off together |
:group <name> [--fg COLOR] [--bg COLOR] [-l] [--auto] [--clear] | Set, update, or clear a group’s predefined color style, used by filters in the group with no color of their own |
:filtering | Toggle all filtering on/off (bypass every filter) |
:clear-filters | Remove all filter definitions |
:disable-filters | Disable all filters without removing them |
:enable-filters | Enable all disabled filters |
:save-filters <file> | Save current filters to a JSON file |
:load-filters <file> | Load filters from a JSON file |
:import-filters <file> [-a|--append] | Import a Notepad++ Analyze-plugin or User-Defined-Language XML config as Include filters. Replaces current filters by default; --append merges instead |
Flag ordering: All options (
--regex,--fg,--bg,-l,--field,--group/-g,--ignore-case/-i,--auto) must appear before the pattern. Everything after the first pattern word is treated as part of the pattern text.--autocannot be combined with--fg/--bg.
See Filter Groups for the Groups sidebar and group management mode (Ctrl+g).
See Filtering, Date & Time Filters, and Field Filters for full details.
Navigation
| Command | Description |
|---|---|
:<N> | Jump to line N (e.g. :500) |
Files and Tabs
| Command | Description |
|---|---|
:open <path> | Open a file, directory, or compressed/archive file. Directories and archives both show the same contents picker first — see Quick Start |
:close-tab | Close the current tab (quits if it’s the last tab) |
:save <path> | Save the currently visible (filtered) lines to a file in raw format |
:export-marked <path> | Export marked lines to a file |
:run <program> [args...] | Execute a command and stream its output to a new tab; stderr lines show as errors |
:schema [name] | Show the active schema, or switch this tab to a named custom or built-in schema. Use :schema none to treat the file as plain text |
:default-filters [format] [path] | Configure a filter file to auto-load whenever a format is assigned to a tab with no filters yet. No args opens a popup listing every format |
Display
| Command | Description |
|---|---|
:wrap | Toggle line wrap on/off (persisted across sessions) |
:line-numbers | Toggle the line number gutter on/off (persisted across sessions) |
:relative-line-numbers | Toggle relative line numbers — other rows show their distance from the selected row (persisted across sessions) |
:tail | Toggle tail mode (auto-scroll on new content) |
:raw | Toggle raw mode — bypass the format parser and show unformatted log lines; title shows [RAW] when active |
:collapse | Hide continuation lines file-wide, showing only each entry’s first line |
:expand | Reveal continuation lines previously hidden by :collapse |
:level-colors | Open the level colors dialog — toggle coloring per level (TRACE, DEBUG, INFO, NOTICE, WARNING, ERROR, FATAL); INFO/TRACE/DEBUG/NOTICE are off by default |
:value-colors | Open the value colors dialog — toggle coloring for HTTP methods, status codes, IPs, UUIDs, and process/logger names |
:set-theme <name> | Switch the color theme (persisted across sessions) |
:theme | Open a searchable picker to browse themes with live preview; Enter applies and persists, Esc restores the previous theme |
:sidebar-position left|right | Move the filter sidebar to the left or right of the log panel (persisted across sessions) |
OTel Collector
| Command | Description |
|---|---|
:otel [port] | Open an OTLP gRPC receiver tab (default port 4317) |
:otel --http [port] | Open an OTLP HTTP/JSON receiver tab (default port 4318) |
See OTel Collector for full details.
MCP Server
| Command | Description |
|---|---|
:enable-mcp [--port N] | Start the embedded MCP server (default port 9876) |
:disable-mcp | Stop the MCP server |
See MCP Server for full details.
Live Data
These commands control how the current tab handles incoming data from a file watcher or stream (stdin, Docker).
| Command | Description |
|---|---|
:stop | Permanently stop all incoming data for the current tab — drops the file watcher and/or stream |
:pause | Freeze the view; the background watcher/stream keeps running. Title shows [PAUSED] |
:resume | Resume applying incoming data; the latest snapshot is applied immediately |
Note:
:pause/:resumeare non-destructive — no data is lost while paused.:stopis permanent; to resume watching a file after stopping, reopen it with:open.
Structured Fields
| Command | Description |
|---|---|
:hide-field <col> | Hide a single column |
:show-field <col> | Show a previously hidden column |
:show-all-fields | Reset to default column display |
:select-fields | Open an interactive column picker |
:show-keys | Show field keys alongside values (e.g. method=GET) |
:hide-keys | Show only values, hiding field keys (default) |
Merged View
| Command | Description |
|---|---|
:merge | Open a source-selection popup, then create a new tab interleaving the selected tabs sorted by timestamp |
See Multi-Tab for full details.
Export and Streaming
| Command | Description |
|---|---|
:export <file> [-t <template>] | Export annotations to a file (default template: markdown) |
:docker | Pick and stream a running Docker container |
:dlt | Pick and stream from a DLT daemon over TCP |
Session
| Command | Description |
|---|---|
:reset | Restore all settings to defaults and clear all persisted state |
Tab Completion
Command mode supports multi-tier tab completion:
- Color names — after
--fgor--bgflags - Template names — after
-t/--templateflags in:export - File paths — for
:open,:save-filters,:load-filters,:import-filters,:export - Theme names — for
:set-theme - Command names — for everything else
Press Tab / Shift+Tab to cycle through completions. A highlighted suggestion appears in the hint area; Space accepts it.
Configuration
logana is configured via a config.json file. The file is entirely optional — all settings have sensible defaults and logana starts normally even if the file is missing. If the file exists but cannot be read or contains invalid JSON or unknown keys, a warning is shown in the notification area on startup.
How the Config File Works
logana never writes to the config file. Any settings defined there are applied on startup and take precedence over the values stored in the database.
Many settings can also be changed at runtime — UI toggles via the UI options menu (u) and display commands (:wrap, :line-numbers, :relative-line-numbers, :set-theme, :sidebar-position). When changed at runtime the new value is saved to the database and restored on the next session, unless the setting is also defined in the config file, in which case the config file value always wins.
Schema Validation
logana publishes a JSON Schema for config.json. Add a $schema line to enable validation and autocomplete in VS Code and other JSON-aware editors:
{
"$schema": "https://raw.githubusercontent.com/pauloremoli/logana/main/schema/config.schema.json",
"theme": "dracula"
}
VS Code will highlight unknown fields, suggest valid values for enums such as restore_session, and show inline documentation for each option.
Config File Location
The path depends on the operating system:
| OS | Path |
|---|---|
| Linux | ~/.config/logana/config.json |
| macOS | ~/Library/Application Support/logana/config.json |
| Windows | %APPDATA%\logana\config.json |
Full Example
{
"theme": "dracula",
"show_mode_bar": true,
"show_borders": true,
"show_sidebar": true,
"show_line_numbers": true,
"relative_line_numbers": false,
"wrap": false,
"sidebar_side": "right",
"preview_bytes": 16777216,
"restore_session": "always",
"restore_file_context": "always",
"mcp_port": 9876,
"dlt_devices": [
{ "name": "my-ecu", "host": "192.168.1.100", "port": 3490 }
],
"keybindings": {
"navigation": {
"scroll_down": ["j", "Down"],
"scroll_up": ["k", "Up"],
"half_page_down": "Ctrl+d",
"half_page_up": "Ctrl+u",
"page_down": "PageDown",
"page_up": "PageUp"
},
"normal": {
"filter_include": "i",
"filter_exclude": "o",
"filter_mode": "f",
"toggle_filtering": "F",
"mark_line": "m",
"toggle_marks_only": "M",
"visual_mode": "V",
"enter_ui_mode": "u",
"show_keybindings": "F1",
"scroll_left": "h",
"scroll_right": "l"
},
"global": {
"quit": "q"
}
}
}
Top-level Options
| Key | Type | Default | Description |
|---|---|---|---|
theme | string | "github-dark" | Active color theme name (without .json extension) |
show_mode_bar | bool | true | Show the bottom status/mode bar on startup |
show_borders | bool | true | Show panel borders on startup |
show_sidebar | bool | true | Show the filter sidebar on startup |
show_line_numbers | bool | true | Show the line number gutter |
relative_line_numbers | bool | false | Show line numbers relative to the selected row instead of absolute |
wrap | bool | false | Wrap long lines |
sidebar_side | string | "right" | Pin the filter sidebar to "left" or "right" of the log panel |
preview_bytes | number | 16777216 | Bytes read for the instant preview shown while the full file index is built in the background (16 MiB) |
restore_session | string | "always" | Whether to reopen tabs from the previous session ("always", "ask", "never") |
restore_file_context | string | "always" | Whether to restore per-file state (scroll, marks, search) when reopening a file ("always", "ask", "never") |
mcp_port | number | 9876 | Default port for the embedded MCP server when started via :enable-mcp |
dlt_devices | array | [] | Pre-configured DLT daemon connections; each entry has name, host, and optional port (default 3490) |
Sections
- Keybindings — remapping all keyboard shortcuts
- Themes — built-in themes and creating custom themes
Keybindings
All keybindings are configurable via ~/.config/logana/config.json. Only the keys you want to change need to be specified — all others retain their defaults.
Key Syntax
Each binding is a string (or array of strings for multiple alternatives):
| Syntax | Example | Description |
|---|---|---|
| Single character | "j" | A printable key |
| Modified | "Ctrl+d", "Shift+Tab" | Modifier + key |
| Special keys | "Enter", "Esc", "Space", "Backspace" | Named keys |
| Function keys | "F1", "F12" | Function row keys |
| Navigation keys | "Up", "Down", "Left", "Right", "PageUp", "PageDown", "Home", "End" | Arrow/navigation keys |
Multiple alternatives:
"scroll_down": ["j", "Down"]
Navigation (shared across all modes)
"navigation": {
"scroll_down": ["j", "Down"],
"scroll_up": ["k", "Up"],
"half_page_down": "Ctrl+d",
"half_page_up": "Ctrl+u",
"page_down": "PageDown",
"page_up": "PageUp"
}
Normal Mode
"normal": {
"scroll_left": ["h", "Left"],
"scroll_right": ["l", "Right"],
"start_of_line": "0",
"end_of_line": "$",
"command_mode": ":",
"filter_mode": "f",
"group_mode": "Ctrl+g",
"toggle_filtering": "F",
"toggle_highlight_mode": "H",
"go_to_top_chord": "g",
"go_to_bottom": "G",
"mark_line": "m",
"expand_continuation": ">",
"collapse_continuation": "<",
"search_forward": "/",
"search_backward": "?",
"next_match": "n",
"prev_match": "N",
"visual_mode": "V",
"visual_char": "v",
"toggle_marks_only": "M",
"yank_line": "y",
"yank_marked": "Y",
"show_keybindings": "F1",
"clear_all": "C",
"edit_comment": "r",
"delete_comment": "d",
"comment_line": "c",
"next_error": "e",
"prev_error": "E",
"next_warning": "w",
"prev_warning": "W",
"filter_include": "i",
"filter_include_auto": "a",
"filter_exclude": "o",
"enter_ui_mode": "u",
"clear_search": "Esc"
}
filter_mode opens the filter manager; group_mode opens group management scoped to the selected group. go_to_top_chord is the first key of the gg chord — pressing it twice jumps to the first line, matching go_to_bottom’s single-press jump to the last line. clear_search clears an active search highlight when nothing else is open.
Global (always active)
"global": {
"quit": "q",
"next_tab": "Tab",
"prev_tab": "Shift+Tab",
"new_tab": "Ctrl+t",
"close_tab": "Ctrl+w",
"file_switcher": "Ctrl+p"
}
Filter Manager
"filter": {
"toggle_filter": "Space",
"edit_filter": "e",
"delete_filter": "d",
"set_color": "c",
"add_include_filter": "i",
"add_include_filter_auto": "a",
"add_exclude_filter": "o",
"add_date_filter": "t",
"add_highlight_filter": "h",
"search": "/",
"move_filter_down": "J",
"move_filter_up": "K",
"toggle_all_filters": "A",
"clear_all_filters": "C",
"exit_mode": "Esc",
"sidebar_grow": ">",
"sidebar_shrink": "<"
}
The filter manager also reuses the shared navigation group above (scroll_down/scroll_up/half_page_down/half_page_up/page_down/page_up) and, for jump-to-top/bottom, the normal.go_to_top_chord/normal.go_to_bottom bindings — no separate fields needed for those. sidebar_grow/sidebar_shrink resize the sidebar while it’s focused.
Group Mode
"group": {
"clear_group_style": "x",
"add_group": "a"
}
Group management (Ctrl+g) reuses several filter/navigation bindings rather than duplicating them: filter.toggle_all_filters/filter.toggle_filter to toggle the selected group, filter.edit_filter to edit its style, filter.exit_mode to exit, and navigation.scroll_down/scroll_up to move between groups. clear_group_style has no sensible existing key to borrow, so it gets this dedicated field.
Search / Filter Edit / Command Confirm & Cancel
"search": {
"confirm": "Enter",
"cancel": "Esc"
},
"filter_edit": {
"confirm": "Enter",
"cancel": "Esc"
},
"command": {
"confirm": "Enter",
"cancel": "Esc"
}
search confirms or cancels the log panel’s //? search or the filter manager’s / search. filter_edit confirms or cancels an in-place filter pattern edit. command confirms or cancels the : command line.
Visual Line Mode
"visual_line": {
"comment": "c",
"mark": "m",
"yank": "y",
"search": "/",
"exit": "Esc"
}
Visual Char Mode
"visual": {
"move_left": ["h", "Left"],
"move_right": ["l", "Right"],
"word_forward": "w",
"word_backward": "b",
"word_end": "e",
"word_forward_big": "W",
"word_backward_big": "B",
"word_end_big": "E",
"start_of_line": "0",
"first_nonblank": "^",
"end_of_line": "$",
"find_forward": "f",
"find_backward": "F",
"till_forward": "t",
"till_backward": "T",
"repeat_motion": ";",
"repeat_motion_rev": ",",
"start_selection": "v",
"filter_include": "i",
"filter_exclude": "o",
"search": "/",
"yank": "y",
"exit": "Esc"
}
Comment (Annotation) Mode
"comment": {
"newline": "Enter",
"save": "Ctrl+s",
"cancel": "Esc",
"delete": "Ctrl+d"
}
Confirm Dialogs
"confirm": {
"yes": ["y", "Enter"],
"no": ["n", "Esc"],
"always": "Shift+Y",
"never": "Shift+N"
}
always/never apply the same choice automatically to every future prompt of that kind, where the dialog supports it (e.g. session restore).
UI Options Mode
"ui": {
"toggle_sidebar": "s",
"toggle_mode_bar": "b",
"toggle_borders": "B",
"toggle_wrap": "w",
"toggle_relative_line_numbers": "r",
"toggle_groups_panel": "g",
"exit": "Esc"
}
Select Fields Mode
"select_fields": {
"toggle": "Space",
"move_down": "J",
"move_up": "K",
"all": "a",
"none": "n",
"reset": "r",
"apply": "Enter",
"cancel": "Esc",
"search": "/"
}
reset restores the popup’s staged fields to the format’s default order with everything visible — clearing both any J/K reorder and any hidden fields in one step. Like all/none, it only changes what’s staged; apply still commits it.
Archive Picker Mode
"archive_picker": {
"toggle": "Space",
"merge_toggle": "m",
"expand": "Right",
"collapse": "Left",
"all": "a",
"none": "n",
"apply": "Enter",
"cancel": "Esc",
"search": "/",
"search_toggle": "Ctrl+e",
"search_merge_toggle": "Alt+m",
"search_select_all": "Ctrl+a",
"search_merge_all": "Ctrl+Alt+m"
}
toggle marks a file (or a container’s whole subtree) for extraction — each
toggled file opens as its own tab on apply. merge_toggle marks a file
independently for merging instead: every merge-marked file is extracted and
combined into one timestamp-sorted tab on apply, rather than opening
separately. A file can be toggled, merge_toggled, both, or neither, and
apply performs both actions together in one press. If a merge-marked
file’s format can’t be recognized, only the merge is skipped (with an error
naming the file) — toggled files still extract and open normally.
Archive listing only auto-decompresses one nested level; a nested archive
found any deeper shows as a collapsed row instead of being read upfront.
expand reads and reveals it on demand (or, on an already-fetched row
that’s merely folded shut, just reveals its children again with no
re-fetch); collapse folds an expanded container’s children back out of
view without discarding the already-fetched data.
search opens a live regex query that narrows the file tree to matching
files (keeping their containing archive visible for context) — Enter
confirms and un-narrows the list, Esc cancels back to the pre-search
selection. An invalid/incomplete regex (e.g. while still typing) falls back
to a plain substring match rather than matching nothing.
While searching, toggle/merge_toggle/all/none are unavailable —
their keys are needed as literal query text. search_toggle and
search_merge_toggle take their place, marking the selected row for
extraction/merging without leaving search, so several files sharing a
prefix can be selected from one query instead of re-searching for each.
search_select_all/search_merge_all go further and mark every row
whose name currently matches the query in one press, rather than just
the selected row.
search_merge_toggle defaults to Alt+m rather than a Ctrl-chord at
all: outside an enhanced keyboard protocol, Ctrl+M and a plain Enter
keypress send the exact same byte, so the terminal reports Ctrl+M as
Enter — a bare Ctrl+m binding would silently never fire, and apply’s
Enter binding would consume the keypress instead. Alt+m sidesteps the
ambiguity entirely.
search_merge_all defaults to Ctrl+Alt+m — a superset of
search_merge_toggle’s Alt+m, keeping the two mnemonically paired
(m for merge, an extra modifier for “all”). It doesn’t collide with
Enter either, since Alt alone isn’t held.
Row navigation reuses the same keys as the filter sidebar and log panel
(from the navigation group) rather than duplicating them here: j/k
(optionally count-prefixed, e.g. 12j), Ctrl+d/Ctrl+u for a half page,
PageDown/PageUp for a full page, and gg/G (optionally
count-prefixed, e.g. 25G) to jump to the first/last or a specific row.
Docker Select Mode
"docker_select": {
"confirm": "Enter",
"cancel": "Esc"
}
DLT Select Mode
"dlt_select": {
"confirm": "Enter",
"cancel": "Esc",
"delete": "d",
"next_field": "Tab",
"prev_field": "Shift+Tab"
}
next_field/prev_field move between the connection form’s input fields (host, port, …) before confirming.
Value Colors Mode
"value_colors": {
"toggle": "Space",
"all": "a",
"none": "n",
"apply": "Enter",
"cancel": "Esc"
}
Keybindings Help
"help": {
"close": ["Esc", "q", "F1"]
}
Custom Commands
Bind a key to run a fixed command line, checked in Normal Mode ahead of every built-in action:
"custom": [
{
"key": "F2",
"command": "load-filters ~/logs/filters/draco-mars.json"
}
]
command is whatever you’d type after : in command mode — no leading :. key accepts any binding from the Key Syntax above, including an array of alternatives. Add as many entries as you like; each one gets its own row in :show-keybindings.
A custom binding that reuses a built-in action’s key wins over it (deliberately — see Conflict Validation below for how you’ll be warned about the collision, not blocked from making it).
Conflict Validation
At startup, logana validates all configured keybindings for conflicts within each mode scope. Conflicts are printed to stderr with a description of the overlapping bindings, but do not prevent startup.
Themes
logana ships with 22 bundled themes and supports fully custom themes via JSON files.
Switching Themes
:set-theme catppuccin-mocha
Tab completes theme names. The chosen theme is persisted to the database and restored on the next session.
To pin a theme permanently (overriding any runtime changes), add it to ~/.config/logana/config.json:
{ "theme": "catppuccin-mocha" }
Bundled Themes
Dark
| Name | Description |
|---|---|
atomic | Vibrant, high-saturation |
catppuccin-macchiato | Pastel purple, slightly lighter than mocha |
catppuccin-mocha | Pastel purple, the most popular Catppuccin variant |
dracula | Purple, default theme |
everforest-dark | Earthy green, easy on the eyes |
github-dark | GitHub dark — deep navy with blue accents |
github-dark-dimmed | GitHub dark dimmed — softer navy variant |
gruvbox-dark | Warm retro browns and yellows |
jandedobbeleer | Colorful, high contrast |
kanagawa | Japanese ink — deep blues and warm golds |
monokai | Classic dark with vivid accents |
nord | Cool blue-grey Arctic palette |
onedark | Atom-inspired, muted cool colors |
paradox | High contrast |
rose-pine | Muted roses and purples |
solarized | Classic muted palette |
tokyonight | Deep blue, inspired by Tokyo at night |
Light
| Name | Description |
|---|---|
catppuccin-latte | Pastel, warm cream background |
everforest-light | Earthy green, warm paper background |
github-light | GitHub light — clean white with blue accents |
onelight | Atom-inspired, clean white background |
rose-pine-dawn | Warm rose tones on a parchment background |
Custom Themes
Place .json files in ~/.config/logana/themes/. A user theme with the same name as a bundled one takes priority.
Minimal example
Only five fields are required — everything else falls back to built-in defaults:
{
"root_bg": "#1e1e2e",
"border": "#6272a4",
"border_title": "#f8f8f2",
"text": "#f8f8f2",
"error_fg": "#ff5555",
"warning_fg": "#f1fa8c",
"process_colors": ["#ff5555", "#50fa7b", "#ffb86c", "#bd93f9", "#ff79c6", "#8be9fd"]
}
Full example (Dracula)
{
"root_bg": "#282a36",
"border": "#6272a4",
"cursor_bg": "#6272a4",
"border_title": "#f8f8f2",
"text": "#f8f8f2",
"text_highlight_fg": "#ffb86c",
"text_highlight_bg": "#7a4a10",
"cursor_fg": "#1c1c1c",
"trace_fg": "#6272a4",
"debug_fg": "#8be9fd",
"notice_fg": "#f8f8f2",
"warning_fg": "#f1fa8c",
"error_fg": "#ff5555",
"fatal_fg": "#ff5555",
"search_fg": "#1c1c1c",
"visual_select_bg": "#44475a",
"visual_select_fg": "#f8f8f2",
"mark_bg": "#463c0f",
"mark_fg": "#f8f8f2",
"process_colors": ["#ff5555", "#50fa7b", "#ffb86c", "#bd93f9", "#ff79c6", "#8be9fd"],
"value_colors": {
"http_get": "#50fa7b",
"http_post": "#8be9fd",
"http_put": "#ffb86c",
"http_delete": "#ff5555",
"http_patch": "#bd93f9",
"http_other": "#6272a4",
"status_2xx": "#50fa7b",
"status_3xx": "#8be9fd",
"status_4xx": "#ffb86c",
"status_5xx": "#ff5555",
"ip_address": "#bd93f9",
"uuid": "#6c71c4"
}
}
Color Formats
All color values accept:
- Hex string:
"#RRGGBB" - RGB array:
[r, g, b](each 0–255)
Fields Reference
Required
| Field | Used for |
|---|---|
root_bg | Main background |
border | Panel border lines and dimmed decorator text |
border_title | Panel title text |
text | Default log line text |
error_fg | ERROR level lines |
warning_fg | WARN/WARNING level lines |
process_colors | Array of colors cycled across process/logger name columns (can be toggled via :value-colors) |
Optional (with defaults)
| Field | Default | Used for |
|---|---|---|
cursor_bg | = border | Background of the cursor line, command bar, and search bar |
text_highlight_fg | #ffb86c | Search match background; also the cursor for the current match |
text_highlight_bg | #7a4a10 | Background behind search highlight |
cursor_fg | #1c1c1c | Text color on the cursor line (sits on cursor_bg) |
trace_fg | #6272a4 | TRACE level lines |
debug_fg | #8be9fd | DEBUG level lines |
info_fg | = text | INFO level lines (disabled by default; enable via :level-colors) |
notice_fg | #f8f8f2 | NOTICE level lines |
fatal_fg | #ff5555 | FATAL/CRITICAL level lines |
search_fg | #1c1c1c | Foreground of search match highlights |
visual_select_bg | #44475a | Visual line selection background |
visual_select_fg | #f8f8f2 | Visual line selection foreground |
mark_bg | #463c0f | Marked line background |
mark_fg | #f8f8f2 | Marked line foreground |
value_colors | see below | Per-token HTTP/IP/UUID colors |
value_colors sub-object
All fields are optional and fall back to Dracula-palette defaults.
| Field | Default | Token type |
|---|---|---|
http_get | #50fa7b | GET |
http_post | #8be9fd | POST |
http_put | #ffb86c | PUT |
http_delete | #ff5555 | DELETE |
http_patch | #bd93f9 | PATCH |
http_other | #6272a4 | HEAD, OPTIONS, and others |
status_2xx | #50fa7b | 2xx success codes |
status_3xx | #8be9fd | 3xx redirect codes |
status_4xx | #ffb86c | 4xx client error codes |
status_5xx | #ff5555 | 5xx server error codes |
ip_address | #bd93f9 | IPv4 and IPv6 addresses |
uuid | #6c71c4 | UUID strings |
Toggling token and level colors at runtime
Use :value-colors to open an interactive dialog where you can enable or disable individual token types — including Process / logger colors — without editing the theme file.
Use :level-colors to open a similar dialog for log levels. Each level (TRACE, DEBUG, NOTICE, WARNING, ERROR, FATAL) can be toggled independently. The choices are saved per-file across sessions.
Tips for Light Themes
Set cursor_bg to a color that is noticeably darker than root_bg so the cursor line and command bar are clearly visible. Keep border as a subtle separator — it can be close to root_bg if you prefer minimal panel borders.
Set cursor_fg and search_fg to a dark color — they appear as text on the cursor_bg background and must contrast against it.
{
"root_bg": "#fafafa",
"border": "#d0d0d0",
"cursor_bg": "#aaaaaa",
"cursor_fg": "#383a42",
"search_fg": "#383a42"
}
Log Formats
logana detects the log format automatically by sampling the first lines of the file. No flags or configuration are required.
Supported Formats
| Format | Examples |
|---|---|
| OpenTelemetry (OTLP) | OTLP/JSON protobuf-JSON encoding, OTel SDK JSON |
| DLT | AUTOSAR binary (storage, wire, simplified) and dlt-convert -a text |
| JSON | tracing-subscriber JSON, bunyan, pino, any structured JSON logger |
| Syslog | RFC 3164 (BSD), RFC 5424 |
| Journalctl | short, short-iso, short-precise, short-full, short-monotonic, short-unix, json-sse, json-seq |
| Common / Combined Log | Apache access, nginx access |
| Logfmt | Go slog, Heroku, Grafana Loki |
| Common log family | env_logger, tracing-subscriber fmt (with/without spans), logback, log4j2, Spring Boot, Python logging, loguru, structlog |
Detection
All registered parsers score a confidence value against the first 200 lines of the file. The parser with the highest score above 0.0 is selected. More specific parsers naturally score higher on their format; the common log parser applies a 0.95× penalty to yield to more specific parsers on ties. The OTLP parser scores up to 1.5 (above the 1.0 maximum for plain JSON) so it wins when OpenTelemetry fields are present.
User-defined schemas (see Custom Schemas below) are always evaluated first, before any built-in parser.
The detected format name is shown in the status bar. Run :schema to show the active one, or :schema <name> to force a specific format for the current tab — typing :schema shows every custom and built-in schema in the autocomplete list (custom ones first, alphabetically, each in its own color).
Format Details
DLT (AUTOSAR Diagnostic Log and Trace)
Three binary layouts are supported and converted to text at load time:
- Storage format — standard AUTOSAR DLT files with 16-byte storage headers (magic bytes
DLT\x01) - Wire format — concatenated DLT messages without storage headers, as received from a
dlt-daemonTCP connection - Simplified format — compact
DLT\x01+ ECU + APID + CTID + timestamp + payload
The text output produced by dlt-convert -a is also parsed directly.
Fields extracted: timestamp, hw_ts (hardware timestamp), mcnt (message counter), ecu, apid (application ID), ctid (context ID), type, subtype, mode (verbose/non-verbose).
Verbose payloads are decoded (strings, integers, floats, booleans, raw data). Non-verbose payloads are shown as hex.
OpenTelemetry (OTLP)
Two JSON-based OTel log formats are supported for file-based parsing. logana also accepts live OTLP streams over gRPC (:otel, port 4317) and HTTP/JSON (:otel --http, port 4318) — see OTel Collector.
OTLP/JSON (protobuf-JSON encoding — exported by collectors):
{"timeUnixNano":"1700000000000000000","severityNumber":9,"severityText":"INFO","body":{"stringValue":"request received"},"attributes":[{"key":"service.name","value":{"stringValue":"my-svc"}}]}
- Timestamp:
timeUnixNano(nanosecond epoch string) - Severity:
severityNumber(1–4=TRACE, 5–8=DEBUG, 9–12=INFO, 13–16=WARN, 17–20=ERROR, 21–24=FATAL) and/orseverityText - Body:
body.stringValue(AnyValue object encoding) - Attributes: array of
{key, value}objects
OTel SDK JSON (emitted directly by SDKs):
{"timestamp":"2024-01-01T00:00:00.000Z","severity_text":"INFO","severity_number":9,"body":"request received","attributes":{"service.name":"my-svc"}}
- Timestamp:
timestamp(ISO 8601) - Severity:
severity_textand/orseverity_number - Body: direct string value
- Attributes: flat
{key: value}dict
Both formats surface service.name, code.namespace, logger, and similar target attributes as the target column.
JSON
Structured JSON logs, one JSON object per line. Supports:
- tracing-subscriber JSON —
{"timestamp":...,"level":...,"target":...,"span":{...},"fields":{"message":...}} - bunyan —
{"time":...,"level":...,"name":...,"msg":...} - pino —
{"time":...,"level":...,"msg":...} - Any structured JSON log with recognizable timestamp/level/message keys
Span sub-fields (e.g. span.name, span.id, fields.request_id) are discoverable and selectable as columns.
Syslog
- RFC 3164 (BSD):
<PRI>Mmm DD HH:MM:SS hostname app[pid]: message - RFC 5424:
<PRI>VER TIMESTAMP HOSTNAME APP PROCID MSGID [SD] MSG
Priority is decoded to a log level; facility is exposed as an extra field.
Journalctl
Text output from journalctl in several formats:
- short:
Mmm DD HH:MM:SS hostname unit[pid]: message - short-iso:
YYYY-MM-DDTHH:MM:SS±ZZZZ hostname unit[pid]: message - short-precise:
Mmm DD HH:MM:SS.FFFFFF hostname unit[pid]: message - short-full:
Www YYYY-MM-DD HH:MM:SS TZ hostname unit[pid]: message - short-monotonic:
[SSSSS.FFFFFF] hostname unit[pid]: message - short-unix:
[EPOCH.FFFFFF] hostname unit[pid]: message - json-sse: server-sent events wrapping JSON journal entries (
data: {...}) - json-seq: RFC 7464 JSON sequence (
\x1e{...}\n)
Header/footer lines (-- Journal begins..., -- No entries --) are silently skipped.
Common / Combined Log Format
Apache and nginx access logs:
- CLF:
host ident authuser [dd/Mmm/yyyy:HH:MM:SS ±ZZZZ] "request" status bytes - Combined: CLF +
"referer" "user-agent"
Fields with value - are omitted.
Logfmt
Space-separated key=value pairs. Used by Go slog, Heroku, Grafana Loki, and many 12-factor apps. Quoted values (key="value with spaces") are supported.
Requires at least 3 key=value pairs per line to distinguish from plain text.
Common Log Family
A broad family sharing the TIMESTAMP LEVEL TARGET MESSAGE structure, with several sub-strategies:
- env_logger:
[ISO LEVEL target] msgor[LEVEL target] msg - logback / log4j2:
DATETIME [thread] LEVEL target - msg - Spring Boot:
DATETIME LEVEL PID --- [thread] target : msg - Python basic:
LEVEL:target:msg - Python prod:
DATETIME - target - LEVEL - msg - loguru:
DATETIME | LEVEL | location - msg - structlog:
DATETIME [level] msg key=val... - tracing-subscriber fmt with spans:
TIMESTAMP LEVEL span_name{k=v ...}: target: msg— span context is parsed and available as thespancolumn - Generic fallback:
TIMESTAMP LEVEL rest-as-message— any timestamp + level keyword combination
Custom Schemas
If none of the built-in parsers match your log format, you can define your own schema. Each schema lives in its own JSON file inside a schema/ directory next to config.json:
| OS | Path |
|---|---|
| Linux | ~/.config/logana/schema/<name>.json |
| macOS | ~/Library/Application Support/logana/schema/<name>.json |
| Windows | %APPDATA%\logana\schema\<name>.json |
On Windows, %APPDATA% resolves to C:\Users\<username>\AppData\Roaming, e.g. C:\Users\<username>\AppData\Roaming\logana\schema\<name>.json.
Template syntax
The easiest way to describe a format is with a template — write the literal shape of a log line with {field} placeholders where fields appear:
{id} {service} <{timestamp}> {pid} {level}/{component}/{feature}, {message}
logana compiles the template to a regex automatically:
{name}— matches non-whitespace characters, stopping at the next literal delimiter or whitespace{name}when adjacent to a literal character (e.g.<{timestamp}>) — stops at that character- The last placeholder always captures the rest of the line
Alternatively, supply a raw pattern with named capture groups for formats the template language cannot express.
Field roles
Placeholder names that match a known semantic are mapped automatically:
| Name | Semantic |
|---|---|
timestamp | Timestamp column |
level | Level column (normalized: INF→Info, ERR→Error, etc.) |
message | Message column |
target | Target column |
component | Component field |
feature | Feature field |
hostname | Hostname field |
pid | PID field |
thread | Thread field |
facility | Facility field |
Any other placeholder name defaults to an extra field. Use the fields map to assign a different role to a non-standard name.
Example
Acme node log line:
04 LINUX-0-syscon <2035-04-04T21:54:53.283856Z> 62A INF/Syscon/StartupMgr, StateChange: dirtyrfservice::instance1 state=CONNECTED
Schema file at ~/.config/logana/schema/acme.json:
{
"name": "acme",
"description": "Acme node log format",
"template": "{id} {service} <{timestamp}> {pid} {level}/{component}/{feature}, {message}",
"fields": {
"id": "extra",
"service": "target"
}
}
service (LINUX-0-syscon) is mapped to target because it identifies the producing service. id is mapped to extra since no built-in semantic fits a hex sequence number.
Critical fields
Three fields unlock core logana features. Map them correctly if your format contains them:
| Field | Features that depend on it |
|---|---|
timestamp | Date & time filters (:date-filter, -t) — without a timestamp field, date filters are silently skipped |
level | Error/warning navigation (e/w) and level-based coloring — both are disabled when no level field is present |
target | Field coloring by originating component in the structured view |
Forcing a schema
:schema — show the active schema
:schema acme — force the acme schema for this tab
Default filter files per format
:default-filters — open a popup listing every format and its configured filter file
:default-filters acme ~/logs/filters/acme.json — set acme's default filter file
:default-filters acme — clear acme's default filter file mapping
When a tab’s format becomes acme — auto-detected on open, or via :schema acme — and the tab has no filters yet, its configured default filter file loads automatically, same effect as :load-filters. Setting or clearing a mapping never retroactively affects the tab you’re currently on — it only applies the next time a tab’s format is assigned.
tracing-subscriber fmt (Rust / Axum)
Rust applications using tracing-subscriber’s default fmt output produce lines like:
Startup (no span):
2024-02-21T10:00:00.123456Z INFO app::server: listening on 0.0.0.0:3000
Runtime (with span):
2024-02-21T10:00:01.234Z INFO request{method=GET uri=/api/users id="0.5"}: app::handler: processing request
Both forms are handled: span lines are parsed into a span column with name and fields; non-span lines fall through to the generic fallback.
Custom Schemas
If none of the built-in parsers match your log format, define your own schema. logana loads every .json file from the schema/ directory next to config.json and includes them in format detection automatically — no restart required after adding a file.
If a schema file fails to load (malformed JSON) or fails to compile (an invalid template/pattern), logana shows a startup warning naming the file or schema and the problem, instead of silently dropping it from format detection.
Schema directory
| OS | Path |
|---|---|
| Linux | ~/.config/logana/schema/ |
| macOS | ~/Library/Application Support/logana/schema/ |
| Windows | %APPDATA%\logana\schema\ |
On Windows, %APPDATA% resolves to C:\Users\<username>\AppData\Roaming, e.g. C:\Users\<username>\AppData\Roaming\logana\schema.
Each file describes one format. The filename is arbitrary; the name field inside the JSON is what identifies the schema at runtime.
Schema Validation
logana publishes a JSON Schema for custom schema files. Add a $schema line to enable validation and autocomplete in VS Code and other JSON-aware editors:
{
"$schema": "https://raw.githubusercontent.com/pauloremoli/logana/main/schema/custom-schema.schema.json",
"name": "my-format",
"template": "{level} {message}"
}
An unrecognized key (e.g. a typo like "templte") is rejected at load time — logana’s startup warning names the file and the bad key instead of silently ignoring it.
Schema file structure
{
"name": "my-format",
"description": "Optional human-readable description",
"template": "{field1} <{timestamp}> {level}/{component}, {message}",
"fields": {
"field1": "extra"
}
}
| Key | Required | Description |
|---|---|---|
name | yes | Identifier used by :schema <name> and shown in the status bar |
description | no | Free-form description; not used by logana |
template | one of template / pattern | A single-line template string, or an array describing a full multi-line record — see Multiline records |
pattern | one of template / pattern | Raw regex with named capture groups |
fields | no | Overrides the automatic role for named placeholders/groups |
multiline | no | When true (and template is a plain string), folds continuation lines into the record’s message field — see Folding continuation text into message |
Template syntax
A template describes the literal shape of a log line. Write the fixed characters exactly as they appear and mark variable parts with {name}:
{id} {service} <{timestamp}> {pid} {level}/{component}/{feature}, {message}
Compilation rules:
- A placeholder adjacent to a literal delimiter (e.g.
<{ts}>,{level}/) stops at that character —<{ts}>becomes<(?P<ts>[^>]+)>. - A placeholder followed by whitespace or another placeholder matches
\S+. - The last placeholder always captures the rest of the line, regardless of what follows it.
Raw regex
Use pattern instead of template when the template language is not expressive enough:
{
"name": "my-format",
"pattern": "^(?P<ts>\\S+)\\s+(?P<level>\\w+)\\s+(?P<message>.*)$",
"fields": { "ts": "timestamp" }
}
The regex must use named capture groups ((?P<name>...)). Group names are resolved to field roles using the same rules as template placeholders.
Field roles
Placeholder or capture group names that match a known semantic are mapped automatically:
| Name | Role |
|---|---|
timestamp | Timestamp column |
level | Level column — normalized automatically (INF→Info, ERR→Error, WRN→Warning, etc.) |
message | Message column |
target | Target column |
component | Component field |
feature | Feature field |
hostname | Hostname field |
pid | PID field |
thread | Thread field |
facility | Facility field |
Any name not in the table above defaults to extra — it appears in the structured fields sidebar with its original name.
Use the fields map to assign a different role to a non-standard name:
"fields": {
"service": "target",
"seq": "extra",
"thr": "thread"
}
The value can also be "ignored" to capture a group in the regex without displaying it.
Critical fields
Three fields unlock core logana features. If your format contains them, map them correctly:
| Field | Features that depend on it |
|---|---|
timestamp | Date & time filters (:date-filter, -t on the CLI) — without a timestamp field, date filters have nothing to match against and are silently skipped |
level | Error/warning navigation (e/w keys to jump between errors and warnings) and level-based field coloring — without a level field both are disabled for that tab. If your level values aren’t error/warn/etc., see Level values below to map them. |
target | Field coloring by target — used to color-code log lines by their originating component in the structured view |
If your format uses non-standard names for these fields, always map them explicitly:
"fields": {
"sev": "level",
"ts": "timestamp",
"subsys": "target"
}
Level values
level is normalized automatically against a built-in set of keywords (error/err, warn/warning/wrn, info/inf, …). If your format’s level values don’t match any of them — e.g. severity codes like SEV1/SEV2 — level coloring and e/w error/warning navigation won’t recognize them by default. Declare which raw values (case-insensitive) mean error and warning with levels:
{
"name": "sev",
"template": "{level}: {message}",
"levels": {
"error": ["SEV1"],
"warning": ["SEV2"]
}
}
A value can only be declared for one of error/warning — declaring the same value in both is a schema error, reported the same way as an invalid template/pattern (see the startup warning at the top of this page). Values not covered by levels still fall back to the built-in keyword matching.
Rendering
When a template-based schema matches a line, the log panel renders it using the template’s own field order and literal separators — so {level}/{component}/{feature} shows as INF/Syscon/StartupMgr, not space-joined columns. Hiding a field with :select-fields keeps this: the hidden field’s value drops and its adjacent separator collapses with it, so hiding component renders INF/StartupMgr rather than a dangling INF//StartupMgr. Only genuinely reordering columns (moving a field with J/K in :select-fields) falls back to the standard space-joined column layout, since an arbitrary new order can’t be expressed with the template’s fixed separators. This only applies to template-defined schemas; a pattern-defined schema (raw regex) always uses the column layout, since a regex has no literal skeleton to reconstruct from.
The :select-fields popup lists a template-defined schema’s fields in the template’s own order (e.g. id, target, timestamp, pid, level, component, feature, message for the acme example below) rather than the generic timestamp/level/target/extras/message grouping used for other formats — matching the order the line actually renders in by default.
Detection
Custom schemas are evaluated before all built-in parsers. When a schema matches ≥ 50% of the sampled lines, it wins the detection competition and is used for all subsequent parsing.
Run :schema to show the current tab’s active schema, or :schema <name> to force a specific custom schema for the current tab — start typing a name to see every custom and built-in schema in the autocomplete list.
A default filter file can also be configured per schema — see Default filter files per format.
Multiline records
A line that doesn’t match the schema’s template/pattern is already treated as a continuation of the previous matched line — it’s shown right below its parent and inherits the parent’s level and visibility. This covers most multiline formats (stack traces, wrapped messages) automatically, with no schema changes needed.
By default, continuation lines stay unstructured: their content isn’t part of any parsed field, so field filters can’t see it. Two independent ways to give them structure:
"multiline": true— fold all continuation text into the record’smessagefield as one blob. Simplest option; use it when you just want the continuation text searchable, not broken into individual fields.- An array
template— describe the continuation lines’ own shape, extracting each into its own field (or a repeating group of sub-records). Use this when continuation lines carry structuredkey: valuedata you want to filter on individually.
The two can be combined: an array template structures what it recognizes, and multiline still folds any unrecognized trailing continuation lines into message.
Folding continuation text into message
Set "multiline": true to fold continuation lines into the record’s message field:
{
"name": "journalctl-verbose",
"template": "{weekday} {timestamp} {host} [{cursor}]",
"multiline": true,
"fields": {
"cursor": "extra"
}
}
Given journalctl --output=verbose output like:
Tue 2024-01-01 10:15:30.123456 UTC myhost [s=abc;i=1;b=def;t=2;x=3]
MESSAGE=disk usage at 92%
_PID=1234
_COMM=diskmond
Only the first line matches this schema’s template — the two indented _KEY=VALUE lines become its continuation. With multiline enabled, the record’s message field becomes the continuation lines’ raw text (joined by their original newlines), so :filter --field message disk usage matches the record even though the header line itself carries no message text at all. If the schema’s template does capture a message field, continuation text is appended after it instead of replacing it.
This only changes what field filters and the structured fields panel see for the record — each physical line still renders as its own row in the log panel, exactly as before.
Structured continuation lines
For formats where each continuation line carries its own field — or where the record has an explicit terminator instead of just running until the next header — make template an array instead of a single string. Each element describes one line of the record, in order:
- Element 0 is always the header (the line that starts a new record) — same rules as a plain string
template. - A plain string or object after the header is a flat continuation line, extracting its own fields.
{"vec": "<name>", "template": ..., "fields": [...], "auto_fields": ...}declares a repeating group — see Repeating groups below.- If the last element is a plain line (not a group), it’s the record’s terminator: extraction stops once that line is seen, purely by its position as the last array element — no separate marker needed. Without one, the record runs until the next line matching the header.
{
"name": "transaction",
"template": [
"### Start transaction {id}",
"field1: {field1}",
"field2: {field2}",
{ "template": "Object {payload}", "json": true },
"### End transaction"
],
"fields": { "id": "extra" }
}
Each flat line accepts the same shorthand as the top-level schema: a bare string (template syntax), or an object with template/pattern/fields/json for more control. A continuation field can’t be mapped to timestamp, level, target, or message — those belong to the header alone; map it to extra (the default) or any other field role instead. Set "json": true on an entry to treat its single placeholder as an embedded JSON object instead of a plain string — each key in the object becomes its own extra field, using the JSON key’s own name.
Given:
### Start transaction 42
field1: 10
field2: 3
Object { "user": "alice", "amount": 99 }
### End transaction
the record is parsed with extra fields id=42, field1=10, field2=3, user=alice, amount=99. The terminator ("### End transaction") also bounds where extraction stops — any lines between it and the next ### Start transaction (stray blank lines, unrelated output, etc.) aren’t scanned for fields.
Repeating groups
Use a {"vec": ...} entry when a record contains a variable number of nested sub-records — e.g. a batch job with any number of workers. Its template matcher opens a new item in the group (and finalizes the previous one, if any); its optional fields are matchers tried in order against subsequent lines while that item is open.
{
"name": "batch-job",
"template": [
"### Job {job_id} started",
"Owner: {owner}",
{ "vec": "workers", "template": "worker: {hostname}" },
{
"vec": "checkpoints",
"template": "checkpoint: {name}, status: {status}",
"auto_fields": false
},
"### Job {job_id} finished"
],
"fields": { "job_id": "extra" }
}
A group with only template (no fields) is a single-line-item group — every matching line closes the previous item and opens a new one from its own captures (checkpoints above: each line is a complete record). A group with fields opens an item on template and keeps extracting fields from subsequent lines into that same item until the next line that matches template (a new item), a different group’s template, or the terminator.
auto_fields (default true) captures any continuation line inside a group’s open item that looks like key: value (or key: "value") but wasn’t matched by a declared fields entry — useful when a nested block’s field set varies too much to enumerate. Lines with no key: value shape are still ignored. Set it to false (as checkpoints does above) to only extract explicitly declared fields. workers above relies entirely on auto_fields: any key: value line following a worker: {hostname} line (e.g. cpu: 87%, status: running) is captured automatically into that worker’s item.
A group’s name becomes the dotted-index column prefix in rendering (workers.0.hostname, workers.1.hostname, …) and the group-scoped path for field filters (workers.hostname — see Field Filters).
Full example — Acme node log
Log line:
04 LINUX-0-syscon <2035-04-04T21:54:53.283856Z> 62A INF/Syscon/StartupMgr, StateChange: dirtyrfservice::instance1 state=CONNECTED
~/.config/logana/schema/acme.json:
{
"name": "acme",
"description": "Acme node log: id service <timestamp> pid level/component/feature, message",
"template": "{id} {service} <{timestamp}> {pid} {level}/{component}/{feature}, {message}",
"fields": {
"id": "extra",
"service": "target"
}
}
Parsed fields:
| Field | Value |
|---|---|
| timestamp | 2035-04-04T21:54:53.283856Z |
| level | INF → Info |
| target | LINUX-0-syscon |
| component | Syscon |
| feature | StartupMgr |
| message | StateChange: dirtyrfservice::instance1 state=CONNECTED |
| id (extra) | 04 |
| pid (extra) | 62A |
Data Locations
Runtime Files
Paths depend on the operating system:
| Location | Linux | macOS | Windows |
|---|---|---|---|
| Database | ~/.local/share/logana/logana.db | ~/Library/Application Support/logana/logana.db | %APPDATA%\logana\logana.db |
| Config file | ~/.config/logana/config.json | ~/Library/Application Support/logana/config.json | %APPDATA%\logana\config.json |
| Themes dir | ~/.config/logana/themes/ | ~/Library/Application Support/logana/themes/ | %APPDATA%\logana\themes\ |
| Templates dir | ~/.config/logana/templates/ | ~/Library/Application Support/logana/templates/ | %APPDATA%\logana\templates\ |
| Schema dir | ~/.config/logana/schema/ | ~/Library/Application Support/logana/schema/ | %APPDATA%\logana\schema\ |
On Windows, %APPDATA% resolves to C:\Users\<username>\AppData\Roaming, e.g. C:\Users\<username>\AppData\Roaming\logana\schema.
Database
The SQLite database stores:
- Filters — include/exclude patterns and date filters, per source file
- File context — per-file session state: scroll position, search query, wrap, sidebar visibility, marked lines, field layout, show-keys preference, and more
- Session tabs — the ordered list of files/Docker streams open when logana last exited (used for session restore)
The database is created automatically on first run. Schema migrations run on startup — no manual setup needed.
Config File
The config file is optional. If it is absent, logana starts with all defaults. If the file exists but cannot be read, contains invalid JSON, or has unknown keys, a warning is shown in the notification area on startup. Partial configs are valid — only specified keys override defaults.
See Configuration for the full schema.
Custom Themes
Place .json files in ~/.config/logana/themes/. Files here shadow bundled themes of the same name. See Themes for the theme JSON format.
Custom Export Templates
Place .txt template files in ~/.config/logana/templates/. Files here shadow bundled templates (markdown, jira) of the same name. See Annotations & Export for the template format.
Custom Schemas
Place .json schema files in ~/.config/logana/schema/. Each file is loaded automatically and included in format detection — no restart required. See Log Formats for the schema file format.