A terminal-based Android logcat viewer written in Rust.
Illustrative demo rendered in tlog's display format (colors and bottom filter bar match the real TUI).
- Real-time log stream — reads and parses
adb logcat -v threadtimeoutput - Android Studio-style filter — supports
tag:,level:,package:,message:,age:,is:key-value filters with regex, negation, and boolean operators - Package name resolution — auto-resolves PID to package name from ActivityManager lifecycle events (inspired by pidcat)
- Color highlighting — color-coded by log level (V=gray D=cyan I=green W=yellow E=red F=red-bg), fully customizable in Settings
- Display options — set which fields show (timestamp, PID, TID, tag, level, color) in Settings (
o) - Pause/resume — freeze log output for careful inspection
- Filter bypass — temporarily disable the active filter with
g, keeping the input intact - Search & highlight —
/opens a search box; matching tag/package/message text is highlighted in the current filter view - Quick level filters —
v/d/i/w/eaddlevel:filters instantly - Filter editor upgrades — readline-style
Ctrl+A/E/W, applied-filter history (↑/↓), and saved named filters (Ctrl+S, then usename:<name>) - Export & offline playback —
Ewrites the current filtered view to a timestamped file;tlog --file <log>replays a log file with the same UI pipeline - Multi-language — UI strings in English and Chinese, auto-detected from
LANGor set via--lang - Multi-device — select a device with
-s/--serialorANDROID_SERIAL, and choose log buffers with-b/--buffer - Resilient feed —
--clearflushes the device log first,--exit-on-eofquits when logcat stops, and--reconnectrestarts it with exponential backoff - Opt-in diagnostics — unparsed lines go to
tlog-rejected.login the config directory only when--debug(orTLOG_DEBUG) is set, with automatic rotation - Segmented replay — filter changes repaint the 10k-line replay in small chunks, so key handling and incoming logs stay responsive on a full buffer
- Ring buffer — 100,000 entries hard cap, memory-safe
From source:
cargo install --path .Prebuilt binaries are attached to every GitHub Release. On the release tag the
artifacts also use tlog-<target>.tar.gz / .zip names, so
cargo-binstall can install
them directly:
cargo binstall --repo ryans233/tlog tlogSee packaging/ for Homebrew, Scoop, and AUR package templates.
Or run directly:
cargo run --releasePrerequisite: Android SDK command-line tools must be installed and adb available on PATH.
# Default: adb logcat -v threadtime
tlog
# Start with a pre-applied filter
tlog --filter 'tag:MainActivity & level:ERROR'
# Custom command (e.g. Termux)
tlog --cmd logcat,-v,threadtime
# `--cmd` is a full override and cannot be combined with -s/-b/-c.
# Select a device and buffers, clearing the device log first
tlog -s emulator-5554 -b main,system -c
# Exit when logcat stops / keep reconnecting when it drops
tlog --exit-on-eof
tlog --reconnect
# Write rejected lines and reader diagnostics to the config directory
tlog --debug
# Offline playback of a saved/exported log file (exits at EOF, no adb needed)
tlog --file crash.log
tlog --file crash.log --filter 'level:ERROR'
# Force language
tlog --lang en
tlog --lang zh$ tlog --help
Android Logcat TUI viewer with inline terminal scrollback
Usage: tlog [OPTIONS]
Options:
--cmd <CMD>
Override the logcat command (comma-separated args)
-s, --serial <SERIAL>
Target a device by serial number (passed as `adb -s <serial>`)
-b, --buffer <BUFFER>
Log buffer(s) to pass to `adb logcat -b` (repeatable or comma-separated)
-c, --clear
Clear the device log before streaming (`adb logcat -c`)
--exit-on-eof
Exit when the logcat feed ends instead of leaving the UI open
--reconnect
Reconnect with exponential backoff when the logcat feed ends
--debug
Write rejected lines and reader diagnostics to the config-dir rejected log
--file <FILE>
Play back a log file instead of streaming from `adb logcat`
--filter <FILTER>
Pre-populate and apply a filter on startup
--lang <LANG>
UI language (en / zh). Auto-detected from LANG if not set
-h, --help
Print help
-V, --version
Print version
| Key | Action |
|---|---|
q / Ctrl+C |
Quit |
p / Space |
Toggle pause/resume |
C |
Clear log buffer |
g |
Toggle filter bypass (keeps input, press again to restore) |
o |
Open settings (display options + colors) |
h |
Show keybindings help |
/ |
Search and highlight matches in the current filter view |
E |
Export the current filtered view to tlog-export-<timestamp>.log |
v / d / i / w / e |
Add level:V / level:D / level:I / level:W / level:E to the filter |
Tab |
Switch focus to filter input |
Esc |
Return to log view (when editing filter / search) |
Enter |
Apply filter or search (when editing) |
← / → |
Move the filter cursor by character (when editing filter) |
Home / Ctrl+A |
Jump to the start of the filter input |
End / Ctrl+E |
Jump to the end of the filter input |
↑ / ↓ |
Previous / next applied filter (history; restores the draft after the newest entry) |
Backspace / Delete |
Delete before / at the cursor (when editing filter) |
Ctrl+W |
Delete the word before the cursor (when editing filter) |
Ctrl+U |
Clear the filter input (when editing filter) |
Ctrl+S |
Save the current filter under a name; use it later with name:<name> |
While paused, incoming log lines are not buffered and are discarded. Search is a case-insensitive literal match against the displayed tag/package and message text; press
/again and submit an empty query to clear it.
| Key | Action |
|---|---|
Tab / Shift+Tab |
Switch category (Display / Colors) |
1–6 |
Toggle display options (Display category) |
1–8 |
Edit an item's color (type hex, Enter commits; Colors category) |
[ / ] |
Cycle color presets |
Esc / o |
Close settings |
# Key-value filter (substring match)
tag:MainActivity
level:ERROR # >= semantics, matches ERROR and FATAL
# Regex (~ modifier)
tag~:My.*Tag
# Negation
-tag:Debug
# Boolean logic
tag:foo & level:ERROR # AND (& binds tighter than |)
tag:foo | tag:bar # OR
tag:foo tag:bar # Same-key implicit OR
tag:foo level:ERROR # Different-key implicit AND
# Special filters
age:5m # Last 5 minutes (window fixed at filter compile time)
age:1h # Last 1 hour (window fixed at filter compile time)
is:crash # FATAL EXCEPTION
is:stacktrace # Stack trace continuation lines
package:com.example # Package name filter (requires resolved PID)
package:mine # Always true (no project context)
name:errors # Saved filter (created with Ctrl+S; quote names with `-`)
/enters search mode.Enterapplies a case-insensitive literal search; all matching entries in the current filtered view are highlighted (tag, package, and message text). New entries are highlighted as they arrive. Submitting an empty query clears the highlight.Ewrites every entry in the current filtered view (up to the 100k ring buffer) totlog-export-<timestamp>.login the working directory, in the canonical-v threadtimeformat.tlog --file <log>replays an existing log file through the same parser and UI. It does not requireadband exits automatically at EOF, which is handy for CI demos and recordings.- Terminal resize bursts are debounced for 75 ms before the scrollback is cleared and replayed.
┌─ main.rs ─── Event loop (tokio::select!) ─────┐
│ ├─ crossterm keyboard events │
│ ├─ logcat child process stdout → channel │
│ └─ 250ms tick (periodic frame rendering) │
├─ logcat.rs ─ Log parsing + process lifecycle ─┤
├─ filter.rs ─ pest grammar → AST → evaluation ─┤
├─ buffer.rs ─ Ring buffer (100k hard cap) ─────┤
├─ app.rs ──── Global state + message dispatch ─┤
├─ config.rs ── Colors + display persistence ───┤
├─ named_filters.rs ─ Saved filter persistence ─┤
├─ export.rs ── Filtered-view export ───────────┤
├─ ui.rs ───── ratatui rendering ───────────────┤
├─ i18n.rs ─── Multi-language messages ─────────┤
├─ scrollback.rs ─ Scrollback buffer ───────────┤
└─ viewport.rs ── Bottom viewport management ───┘
| Mechanism | Detail |
|---|---|
| Ring buffer | 100,000 entries hard cap, evicts oldest 20,000 when full |
| Bounded channel | mpsc::channel(1024), drops on overflow instead of queuing |
| Message truncation | Single message ≤ 4096 bytes, tag ≤ 256 bytes |
| Zero-copy view | filtered: Vec<usize> stores indices, formatting is done on-the-fly |
| Periodic shrink | shrink_to_fit() after eviction |
Replays are segmented into 128-line chunks and yielded to the event loop, so a
full 10k-line repaint never blocks keys or incoming logs. Regression scenarios
for the hot paths live in benches/replay.rs:
cargo bench --bench replay # normal run
cargo bench --bench replay -- --quick # one iteration (used by CI)The benchmark fixture covers 100k-entry pushes, eviction, filter rebuilds, and 10k-entry replay formatting (plain and search-highlighted).
| Purpose | Crate |
|---|---|
| TUI | ratatui 0.30 + crossterm 0.28 |
| Async | tokio 1.x |
| Filter grammar | pest 2.x |
| CLI | clap 4.x |
| Time | chrono 0.4 |
| Regex | regex 1.x |
| Error | color-eyre 0.6 |
| Binary lookup | which 7 |
Package name resolution inspired by JakeWharton/pidcat.
Settings are saved to config.conf on every change and loaded on startup. Colors
(6 log levels, tag, timestamp), the active preset, and the display options are
stored as key = value lines, e.g. preset = default, verbose = #808080,
show_pid = true.
The file location depends on the platform:
| Platform | Path |
|---|---|
Any (if XDG_CONFIG_HOME is set) |
$XDG_CONFIG_HOME/tlog/config.conf |
| Linux / BSD | ~/.config/tlog/config.conf |
| macOS | ~/Library/Application Support/tlog/config.conf |
| Windows | %APPDATA%\tlog\config.conf (falls back to %USERPROFILE%\AppData\Roaming\tlog\config.conf) |
Unknown or malformed lines are ignored per-key; missing keys fall back to defaults.
Saved filters live next to config.conf in filters.conf, one
tab-separated name<TAB>expression line per filter.
- CI quality gates run on every push/PR; tag releases run the same gates before the release build starts.
- Release assets include the raw binary, a SHA256 checksum file, and standard
tlog-<target>.tar.gz/.ziparchives forcargo-binstall. - Ready-to-publish Homebrew, Scoop, and AUR templates live under
packaging/with per-platform checksum placeholders.
See CHANGELOGS.md for release notes.
MIT