feat(logs): add Logs pillar with bounded log tails - #64
Conversation
Add a new Logs page that reads the last chunk of the selected node's log files, the web equivalent of observer_cli's log tail pane. ObserverWeb.Logs restricts sources to the file-backed :logger handlers configured on the target node - free-form paths are never accepted, so the dashboard cannot read arbitrary files. The tail itself is a single stdlib-only RPC: a pre-parsed :erl_eval expression preads at most max_bytes (capped at 1 MB) from the end of the file on the remote node, so nothing is required on the target beyond OTP itself. Truncated chunks drop the partial first line. Risk assessment: - Impact: new read-only pillar (nav entry, page, context module); no changes to existing pages. - Blast radius: additive - new files plus three small wiring edits (index resolve_page, nav list, icon). - Regression risk: low - reads are bounded, restricted to logger handler files and go through the existing Rpc adapter; suite green (408 tests, 95.8% coverage). The single intermittent failure seen locally reproduces on main (version/tracing tests) and is unrelated; a separate fix will follow. - Rollback plan: revert this commit; no config or data involved.
When the selected node exposes no file-backed logger handlers, the Log
File select renders empty and the phx-change payload arrives without a
"file" key at all, so the %{map | key} update in the :logs_refresh
handler raised KeyError and crashed the LiveView. Build the form params
with Map.put instead (same for the nodedown service fallback).
Also document how to make logs visible: new "Logs" section in the
installation guide plus a ObserverWeb.Logs moduledoc section with the
Elixir 1.15+ logger file-handler configuration and the runtime
:logger.add_handler alternative.
Risk assessment:
- Impact: fixes a LiveView crash on the new Logs page; no behavior
change when file handlers exist.
- Blast radius: two form-params call sites in the Logs page plus docs.
- Regression risk: low - regression test reproduces the exact KeyError
without the fix and passes with it; suite green (409 tests, 95.8%
coverage).
- Rollback plan: revert the commit.
|
Pushed a fix for the crash reported while testing: selecting a service with no file-backed logger handlers rendered an empty Log File select, so the change payload had no Also added the missing documentation: an installation-guide "Logs" section and a |
…ogs page Attach a file-backed :logger handler in dev.exs so the standalone dev server always has something for the Logs pillar to tail. Each instance writes to /tmp/observer_web_dev_<node>_<random>.log - node slug plus a random suffix - so the multi-node workflows from the README (observer + broadcast side by side) never collide. A heartbeat appends info/warning/ error lines every 5s so REFRESH always shows fresh content. Toggles: OBSERVER_WEB_DEV_LOG_FILE=false skips the handler, OBSERVER_WEB_DEV_LOG_HEARTBEAT_MS tunes or silences the heartbeat. Risk assessment: - Impact: dev.exs only; the published library is untouched. - Blast radius: standalone dev server boot path. - Regression risk: low - verified by booting the dev server and tailing the generated /tmp file through the Logs page. - Rollback plan: revert the commit or set OBSERVER_WEB_DEV_LOG_FILE=false.
|
Added a dev.exs setup for local testing: every instance attaches a file-backed logger handler writing to |
The dev.exs file handler used Logger.Formatter defaults, which enable ANSI colors when the server runs in a terminal - landing escape codes ([33m, [0m, ...) in the log file. Disable colors for the file formatter, and defensively strip ANSI sequences in the Logs pane since any real-world log written by a color-enabled formatter has the same problem. Risk assessment: - Impact: cleaner dev log files; the pane now renders colored logs legibly instead of showing raw escape codes. - Blast radius: dev.exs formatter plus one display-time transform in the Logs page; ObserverWeb.Logs still returns raw bytes. - Regression risk: low - covered by a page test with ANSI content and verified against a running dev server (zero escape bytes in file). - Rollback plan: revert the commit.
|
Follow-up from local testing: the dev file handler was writing ANSI color codes ( |
Render the tail as parsed log entries instead of a raw pre dump. A new entry starts at a line that looks like a log head (time, date or an Erlang report banner); stack traces and wrapped output attach to the entry above. Each entry renders as a single truncated line with a triangle marker: filled and clickable when there is more to expand (multi-line entries, or single lines beyond 160 chars that truncation clips), hollow and inert when the line is all there is. Multi-line entries also advertise hidden content with a dimmed ellipsis, and error/warning summaries are color-coded. The marker pattern lives in a reusable Core.disclosure component (summary/body slots plus an expandable? flag) so other tables can adopt the same visual language later. The dev.exs heartbeat now exercises all of it: a multi-line fake crash report every 10th beat and a deliberately long single-line message every 7th. Also fix a silent filter: config.exs pins the primary logger level to :warning, which dropped info entries before any handler saw them - the dev server now lowers the primary level to :info while pinning the console handler back to :warning, so the terminal stays as quiet as before while the file gets the full mix. Risk assessment: - Impact: Logs page rendering only, plus dev-server-only logger tweaks; ObserverWeb.Logs still returns raw bytes. - Blast radius: Logs page, one new Core component (used only by Logs), dev.exs. - Regression risk: low - entry parsing is display-time only and covered by tests for grouping, markers, long lines, ANSI and levels; suite green (412 tests, 95.8% coverage), credo/sobelow/dialyzer clean. - Rollback plan: revert the commit.
|
Logs pane UX rework after visual testing:
|
Add a Refresh interval selector (Paused/2s/5s/10s, default 5s) using the same generation-counted tick chain as the Network pillar: changing any control or pressing REFRESH restarts the chain and stale in-flight ticks are ignored. The entries pane reuses the existing ScrollBottom hook, so it follows the newest entries after each refresh but stays pinned when the user scrolls away from the bottom. Risk assessment: - Impact: Logs page gains periodic refresh; manual REFRESH behavior unchanged apart from also resetting the timer cadence. - Blast radius: Logs page only; the tick pattern and scroll hook are reused as-is from Network/tracing. - Regression risk: low - tick chain covered by an interval test and a stale-generation test; suite green (414 tests, 95.9% coverage), credo/sobelow/dialyzer/format clean. - Rollback plan: revert the commit.
|
Added auto refresh: a Refresh interval selector (Paused/2s/5s/10s, default 5s) driven by the same generation-counted tick chain the Network pillar uses, and the entries pane now follows the newest entries via the existing |
What
Adds a Logs pillar: a bounded, read-only tail of the selected node's log files during an incident - the web equivalent of observer_cli's log tail pane.
ObserverWeb.Logs.list_handlers/1discovers the file-backed:loggerhandlers on the target node (:logger.get_handler_config/0); console handlers are skipped.ObserverWeb.Logs.tail/3reads at most the selected tail size (16 KB to 1 MB, hard-capped) from the end of the file via a single stdlib-only RPC - a pre-parsed:erl_evalexpression opens/preads/closes on the remote node, so it works against any OTP node regardless of the observer_web version there (same principle asObserverWeb.SystemInfo).Security notes
selected_file/2falls back to the first known handler, andObserverWeb.Logs.tail/3re-checks server-side), so free-form paths from the browser are never opened.:rawmode, keeping the descriptor inside the rpc process.Why
Part of the roadmap derived from comparing ObserverWeb against OTP observer, observer_cli and Phoenix LiveDashboard: observer_cli proved how useful a safe bounded log view is during incidents, and it sits naturally next to the Crashdump pillar.
Risk assessment
main; a separate fix PR will follow.Checklist
mix testgreen (408 tests)mix coveralls95.8% (threshold 95%)mix credo --strict,mix sobelow,mix dialyzer,mix format --check-formattedclean🤖 Generated with Claude Code