13 KiB
13 KiB
sessionId
| sessionId |
|---|
| session-260605-123454-ub3w |
Requirements
Overview & Goals
Add a "main log output" viewer so users can inspect the application's runtime logs. Logs must still be written to stdout, must also be captured to a persistent single file in the app data directory, must be bounded to the last ~2 MB, and must be accessible via a dedicated window opened from the main UI.
Scope
In Scope:
- Capture
log::andtracing::output to a persistent log file in the app data directory. - Enforce ~2 MB size limit on the stored log (keep the most recent data).
- Expose a Tauri command to retrieve the current log content.
- Add a new companion window (
?view=main-logs) with a viewer UI modeled on the existing unpack logs feature. - Support live tailing of the logs, displaying new log entries as they are emitted from the backend in real time.
- Support dynamic log level filtering in the UI to toggle/filter visible logs by log level (TRACE, DEBUG, INFO, WARN, ERROR).
- Add "Application logs" entry to the main window kebab menu that opens/focuses the logs window.
Out of Scope:
- Configurable log levels or per-module filters in the backend.
- Multi-file rotation or archival of old logs.
- Persisting logs across app reinstalls or explicit export.
User Stories
- As a user troubleshooting an issue, I want to open the application logs from the menu so that I can see recent log output without needing external tools.
- As a power user, I want the log file to stay bounded (~2 MB) so that it does not grow unbounded on disk.
- As a developer or user experiencing an application crash, I want the logs to be persistently saved to a file on disk so that I can inspect them externally even if the application UI cannot be opened or crashes.
Functional Requirements
- Logger writes to stdout, app-data file target (
lanspread.log), and a Tauri event stream for real-time UI tailing. - Log file is kept at ~2 MB; older content is dropped to keep the tail.
get_main_logscommand returns up to the last 2 MB of log text.- Main logs window renders logs in real-time, automatically appending new log lines.
- Auto-scroll to the bottom of the log viewer container when new lines are appended (active by default, can be toggled).
- Pause/Resume control to freeze the log stream so the user can easily scroll, select, and read without being interrupted by new lines.
- Case-insensitive regex filtering of the displayed lines, using the exact same regex input validation, compilation, and error styling (red input border and error label) as the unpack logs window.
- Support dynamic log level filtering in the UI via a
SegmentedRadiofilter with options: "All", "Debug+", "Info+", "Warn+", "Error only". - Support copying all/filtered logs to the clipboard and clearing the active log window's in-memory rows. Clearing the viewer does not delete the persisted log file.
- Window title: "Application Logs"; label: "main-logs".
- Menu item appears in the same kebab menu as "Unpack logs".
Technical Design
Current Implementation
- Logging setup:
crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs:2223createstauri_plugin_log::Builderwith onlyTargetKind::Stdout+LevelFilter::Info(plus mdns off). Plugin registered at 2237. - State & persistence pattern:
LanSpreadStateholdsunpack_logs: Arc<RwLock<Vec<UnpackLogEntry>>>,state_dir: OnceLock<PathBuf>;load_unpack_logs/persist_unpack_logs+trim_unpack_logs(MAX=20) in the same file; exposed byget_unpack_logs(153). - UI pattern:
src/windows/MainWindow.tsx:23definesopenLogsWindowusingWebviewWindowwith labelunpack-logsand?view=unpack-logs; menu item at 110;src/App.tsx:9dispatches viaisUnpackLogsView; full viewer insrc/UnpackLogsWindow.tsx(filtering, list+detail, regex). - The repo uses mostly
log::in the Tauri and peer crates, with sometracing::in shared crates. The current Tauri app does not bridge or subscribe totracing::output.
Key Decisions
- Store the main log as a single plain-text file at
app.path().app_data_dir()?.join("lanspread.log"). - Do not use
TargetKind::LogDirfor this feature. It writes to the OS log directory and uses file replacement rotation, which does not match the requested app-data location or "keep the latest tail" behavior. - Replace the current
tauri_plugin_loglogger setup with an app-owned logging setup initialized during.setupafter the app data directory has been resolved.- Install a
tracing_subscribersubscriber as the single fan-out path for stdout, persistent file writes, and live UI events. - Install
tracing_log::LogTracerso existinglog::calls are captured by the same subscriber astracing::events. - If
tauri-plugin-logis kept registered for future frontend log commands, configure it withskip_logger()so it does not compete for the global logger.
- Install a
- Use one backend formatter for both file and live-event output. Format lines as:
[YYYY-MM-DD][HH:MM:SS][target][LEVEL] message- This makes historical and live rows comparable and makes client-side level parsing deterministic.
- Implement a shared
MainLogSinkthat:- Appends formatted lines to
lanspread.log. - Emits the same formatted line to the frontend via a Tauri event, e.g.
main-log-line. - Bounds the file to the latest ~2 MB by rewriting the tail when the file exceeds the configured limit.
- Never logs from inside the logging path; write/emit failures are ignored or recorded without recursive logging.
- Appends formatted lines to
- Handle the transition from loading history to receiving real-time logs in
MainLogsWindow.tsxby using a buffering and deduplication strategy:- Register the
main-log-linelistener before requesting history. - Buffer incoming live rows while the initial
get_main_logsfile history is being requested. - Once history is loaded, append buffered rows after removing any rows already present in the loaded history by exact line match.
- Transition to direct real-time appending with auto-scrolling to the bottom (if auto-scroll is enabled).
- Register the
- Implement dynamic log level filtering client-side using the existing
SegmentedRadiocomponent.- For live-streamed logs, use the structured event payload's level field.
- For historical log files loaded via
get_main_logs, parse the level from the controlled[LEVEL]field, falling back toInfo.
- Provide UI controls for "Pause / Resume", "Auto-scroll", and "Log Level Filter" to ensure high-quality user experience during busy logging periods.
- Model the new viewer on the unpack-logs style, retaining the identical case-insensitive regex filter ability and regex error reporting UI, but customize it for linear line streaming (sans split-pane list+detail structure, since main logs are a single continuous log flow).
Proposed Changes
- Rust dependencies: add
tracing-subscriberandtracing-logto the workspace/Tauri crate. - Rust (lib.rs): initialize the custom logging pipeline in
.setup, add boundedMainLogSink, addget_main_logs, emitmain-log-line, and register the command. - Tauri capabilities: add a
main-logscapability file for the new window label withcore:default. - Frontend (MainLogsWindow.tsx): implement the scrollable log viewport using
listen('main-log-line')for live tailing, supporting buffering/deduplication, pause/resume, auto-scroll, regex filtering, copying, and clearing. - Frontend (App.tsx & MainWindow.tsx): dispatch the new companion view and add the kebab menu entry to open the window.
- No changes to peer crate, db, or protocol.
File Structure
- Modified:
Cargo.toml,Cargo.lock,crates/lanspread-tauri-deno-ts/src-tauri/Cargo.toml,crates/lanspread-tauri-deno-ts/src-tauri/Cargo.lock,crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs,crates/lanspread-tauri-deno-ts/src/windows/MainWindow.tsx,crates/lanspread-tauri-deno-ts/src/App.tsx - Added:
crates/lanspread-tauri-deno-ts/src/MainLogsWindow.tsx(and.cssif custom styles needed beyond shared) - Added:
crates/lanspread-tauri-deno-ts/src-tauri/capabilities/main-logs.json
Risks
- Trimming on every write would be expensive; mitigate by trimming only when the file exceeds the 2 MB limit plus a small slack threshold, then rewriting the latest tail.
- The logging path is synchronous and global; keep it small, lock-protected, and non-recursive.
- Log lines may contain very long messages; UI should handle wrapping/horizontal scrolling with stable dimensions.
- On Windows release builds the console is hidden, so file becomes the only log source — acceptable.
- Installing
LogTracerplus atracing_subscribersubscriber must happen once. Tests should avoid double-initializing global logging.
Delivery Steps
Step 1: extend-logger-and-add-get-main-logs-command
Logger writes to stdout, persistent app-data lanspread.log, and a live UI event stream. A bounded sink enforces the 2 MB tail.
- Add workspace/Tauri dependencies for
tracing-subscriberandtracing-log. - Add constants
MAIN_LOG_FILE_NAME: &str = "lanspread.log"andMAX_MAIN_LOG_BYTES: u64 = 2 * 1024 * 1024near the unpack log consts. - Implement
main_log_path(state_dir: &Path) -> PathBuf. - Implement
trim_main_log_file(path: &Path)that checks file size and rewrites only the last ~2 MB if exceeded, preserving valid UTF-8 by trimming to a character boundary when needed. - Implement a
MainLogSinkor equivalent shared target that formats each log record once, appends it tolanspread.log, emitsmain-log-line, and trims when the file grows past the limit plus slack. - Initialize logging during
.setupafter resolvinglet state_dir = app.path().app_data_dir()?and creating the app data directory.- Install
LogTracerforlog::macros. - Install a
tracing_subscribersubscriber/layer that writes to stdout and the shared main-log sink. - Preserve the current global level behavior:
Infoby default andmdns_sd::service_daemonoff.
- Install
- Implement
#[tauri::command] async fn get_main_logs(app_handle: tauri::AppHandle) -> tauri::Result<String>that resolvesapp_handle.path().app_data_dir(), trimslanspread.logif needed, reads the file (or returns empty string), and returns the content. - Add
get_main_logsto theinvoke_handler!macro list. - Add focused unit tests for tail trimming and level parsing/formatting helpers where practical.
Step 2: implement-main-logs-frontend-window-and-dispatch
Implement MainLogsWindow with loading + real-time streaming and handle routing.
- Create
crates/lanspread-tauri-deno-ts/src/MainLogsWindow.tsx:- Fetch existing logs via
invoke('get_main_logs')on load and split into an array of lines. - Register
listen<MainLogLinePayload>('main-log-line', ...)from@tauri-apps/api/eventbefore fetching history. - Buffer incoming logs while loading history, and append/deduplicate them once loaded.
- Store logs as objects containing the log line text and its associated
LogLevel. - Extract
LogLevelfor live logs from the event payload. For historical logs, parseLogLevelfrom the controlled[LEVEL]field (default to Info). - Cap in-memory rows/bytes so a long-running logs window does not grow without bound.
- Render the log lines in a scrollable view.
- Implement the identical case-insensitive regex filter ability on lines (compiling regex with error catching, displaying error strings below header, and toggling an invalid input class name).
- Implement dynamic log level filtering in the UI using the shared
SegmentedRadiocomponent with options: "All", "Debug+", "Info+", "Warn+", "Error only". - Implement UI controls: "Auto-scroll" checkbox (scrolls viewport to bottom when new logs arrive, active by default), "Pause / Resume" toggle (buffers/pauses incoming stream rendering), "Clear" button, "Copy to clipboard" button.
- Fetch existing logs via
- Implement
isMainLogsView()helper that checks theview=main-logsquery param (export it). - Update
crates/lanspread-tauri-deno-ts/src/App.tsxto importMainLogsWindow, isMainLogsViewand dispatch to it when the query matches (before falling back to MainWindow). - Create
MainLogsWindow.cssfor styling the viewer, search bars, controls, and scrollable log pane.
Step 3: add-main-logs-capability
The new companion window can invoke commands and listen for log events.
- Add
crates/lanspread-tauri-deno-ts/src-tauri/capabilities/main-logs.json. - Set
"windows": ["main-logs"]. - Grant
"permissions": ["core:default"]. - No
log:defaultpermission is needed because the UI listens to the app-ownedmain-log-lineevent via Tauri core events.
Step 4: add-menu-item-and-window-opener-in-mainwindow
Users can open the main logs viewer from the existing kebab menu.
- In
crates/lanspread-tauri-deno-ts/src/windows/MainWindow.tsx, add anopenMainLogsWindowasync function (copy ofopenLogsWindowbut with label'main-logs', title'Application Logs', url'/?view=main-logs'). - Add a new
KebabItemfor'Application logs'that callsopenMainLogsWindow, placed near the existing'Unpack logs'entry in thekebabItemsarray. - Verify that opening the window focuses an existing instance if already open (same pattern as unpack logs).
Step 5: verify
Use the repo's just commands.
just fmtjust clippyjust frontend-testjust test- Manual smoke test with
just run: open "Application logs", verify history loads from app-datalanspread.log, new backend logs append live, filters work, pause/resume works, copy works, and the file remains bounded near 2 MB.