logger is a Bun-first TUI for browsing mixed server log streams.
It is optimized for:
- JSONL most of the time
- plain text sometimes
- files, stdin, HTTP streams, or spawned commands
- a compact virtualized list on the left
- a JSON tree or raw text details view on the right
bun install
bun run dev -- examples/mixed.logFrom the repo root:
cd /usr/local/personal/logger-cli
bun install
bun run dev -- examples/mixed.logThat starts the interactive TUI immediately.
Open multiple files as tabs:
bun run dev -- access.log app.logRead from stdin:
cat examples/mixed.log | bun run dev --Stream from a URL:
bun run dev -- --url https://example.com/logsStream from a command:
bun run dev -- --cmd "docker logs -f my-container 2>&1"Load config from an explicit path:
bun run dev -- --config ./logger.jsonc examples/mixed.logMerge multiple live sources:
bun run dev -- --merge --merge-sort time app.log --url https://example.com/logs --cmd "docker logs -f app 2>&1"Use config-driven columns:
cp examples/.logger.jsonc ./.logger.jsonc
bun run dev -- examples/mixed.logKeybinding remaps and merge-sort remap example:
Supported keybinding action ids:
openHelp
openFilter
toggleReverse
nextTab
prevTab
moveUp
moveDown
pageUp
pageDown
jumpTop
jumpBottom
enterDetail
leaveDetail
toggleFold
detailSearch
repeatSearchNext
repeatSearchPrev
copyValue
copyPath
toggleAnsi
cycleMergeSort
Config lookup order:
--config path./.logger.jsonc$HOME/.config/logger/config.jsonc
Built CLI:
bun run build
bun run start -- examples/mixed.log
./bin/logger examples/mixed.logCompiled executable:
bun run compile:exe
./bin/logger examples/mixed.logQuery Mode is inspired by jnv.
Mode switching:
Shift+Down: enter Query ModeShift+Up: return to Browse Mode
In Query Mode:
- a query editor appears at the top
- a result pane appears underneath
- if
queryMode.noHintis not true, a hint line is shown
Behavior:
- if the selected entry is JSON, the query runs against that JSON value
- if
Ais toggled in Query Mode, the query is applied to all JSON entries in the active source and results are shown as a list
Query execution:
- if
jqis onPATH,loggerwill try to evaluate the query withjq - otherwise a minimal fallback is supported:
..foo.foo[0]
Autocomplete:
- suggestions are derived from the currently selected JSON object
Tabaccepts the current suggestion
Query Mode keybindings:
Ctrl+Q: copy queryCtrl+O: copy current resultTab: accept autocompleteEnter: toggle fold when result is JSONCtrl+P: expand all JSON result foldsCtrl+N: collapse all JSON result foldsEsc: leave Query Mode
Example queries:
.
.request
.request.method
.items[0]
For/: open advanced filter bar1trace toggle2debug toggle3info toggle4warn toggle5error toggle6fatal toggleUp/Downorj/k: move selectionPgUp/PgDn: pageHome/End: jump beginning/endg/G: jump top/bottomTab: next source tabShift+Tab: previous source tabEnter: toggle detail focusEsc: leave detail focus / close help/: open filter modeSpace: fold/unfold current JSON node in detail paneR: reverse orderM: cycle merge sort in merged modeF1or?: helpq: quit
Examples:
request.method = "GET"
request.method != "POST"
latency >= 100 and latency < 500
message ~= "timeout"
message !~= "healthcheck"
message like "err*"
message ~~= "request\\s+failed"
exists(.request.method)
not exists(.request.user)
level in ("warn","error","fatal")
service not in ("metrics","health")
.request.user? = "alice"
(request.method = "GET" and level = "warn") or message ~= "panic"
span.[].name = "db"
span.[1].name = "http"
Supported operators:
=!=>>=<<=~=!~=like~~=in (...)not in (...)exists(...)not exists(...)andornot- parentheses
Notes:
- Dot paths target structured JSON fields, for example
request.method - Leading-dot paths like
.fieldalways start from the parsed JSON object field? = valuemeans the field is optional: absent is treated as a match- Text entries can still be filtered through built-in fields like
message
Use mainLineTemplate in config to control the compact list row format.
Available variables:
timestamplevelmessageprefixjsonraw
Available helpers:
boldredyellowgreencyanbluepurpleuppercasefixed_sizemin_sizelevel_style
Example:
{
"mainLineTemplate": "{{timestamp}} {{level_style (min_size level 5)}} {{prefix}}{{message}}",
"placeholderFormat": "#{key}",
"contextPath": "extra_data"
}Placeholder substitution example:
If message is:
hello #{user}
and json.extra_data.user is alice, the rendered message becomes:
hello alice
If NO_COLOR is set, the template helpers return plain text without ANSI styling.
bun run typecheck
bun test
bun run build
bun run compile:exesrc/main.tsx: launch spinesrc/replLauncher.tsx: shell handoffsrc/components/App.tsx: providerssrc/screens/REPL.tsx: TUI kernelsrc/QueryEngine.ts: source/session facadesrc/query.ts: view transform helperssrc/lib/ingest: sources and ring buffersrc/lib/parse: mixed JSON/text parsingsrc/lib/query: interactive filter logicsrc/lib/config: config loading
{ "columns": [ { "key": "client", "path": "$.client" } ], "keybindings": { "toggleReverse": ["v"], "cycleMergeSort": ["S"] } }