Skip to content

feat(logging): rotate embedded core + shell logs to ~/.openhuman/logs#1278

Open
senamakel wants to merge 1 commit intotinyhumansai:mainfrom
senamakel:feat/embedded-file-logging
Open

feat(logging): rotate embedded core + shell logs to ~/.openhuman/logs#1278
senamakel wants to merge 1 commit intotinyhumansai:mainfrom
senamakel:feat/embedded-file-logging

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented May 6, 2026

Summary

  • Embedded core now installs a tracing subscriber on Tauri startup (previously only the CLI did), so packaged GUI builds finally produce diagnostics.
  • Logs roll daily to <data_dir>/logs/openhuman-YYYY-MM-DD.log (7-day retention) via tracing-appender, with the same CleanCliFormat used by openhuman run.
  • The Tauri shell's log::* calls are bridged into the same subscriber via tracing_log::LogTracer — replaces the prior stderr-only env_logger. Sentry tracing layer + RUST_LOG env filter preserved.
  • Adds logs_folder_path / reveal_logs_folder Tauri commands and a "Open logs folder" row in Settings → Developer Options so users can grab today's log file when reporting issues like "stuck on Initializing OpenHuman...".
  • Refactors src/core/logging.rs so init_for_cli_run and the new init_for_embedded share seed_rust_log / build_env_filter / sentry_tracing_layer — single source of truth for formatter + filter.

Problem

When the Tauri shell hits a startup issue (RPC bootstrap failure, CEF cache lock, sidecar crash) we have no log file to point users at. The shell's env_logger writes to stderr — invisible in a packaged .app / .exe. The embedded core's tracing subscriber (init_for_cli_run in src/core/logging.rs) was only invoked from CLI paths, so every tracing::info! in the core was a no-op when running inside the Tauri shell. Result: support has nothing concrete to ask the user for.

Solution

  • src/core/logging.rs — extract seed_rust_log / build_env_filter / sentry_tracing_layer and add init_for_embedded(data_dir, verbose). The new entry point installs a non-blocking, daily-rotated file appender (tracing-appender) at <data_dir>/logs/openhuman-YYYY-MM-DD.log plus the existing stderr layer (still useful under tauri dev). Both share CleanCliFormat. Once-guarded; first caller wins. The non-blocking writer's WorkerGuard and the resolved log dir are stashed in OnceLocks so they live for the entire process and the UI can resolve the path on demand.
  • app/src-tauri/src/file_logging.rs — small shell-side wrapper that resolves the data dir the same way the core does (OPENHUMAN_WORKSPACE override, then default_root_openhuman_dir) and calls init_for_embedded. Exposes two Tauri commands: logs_folder_path (returns the absolute path for display) and reveal_logs_folder (open / explorer / xdg-open per platform).
  • app/src-tauri/src/lib.rs — calls file_logging::init() early in run() (before CEF preflight, before the Sentry smoke trigger), drops the env_logger::Builder::try_init path, and registers the new commands in invoke_handler. Sentry init still runs first since that just builds a client guard.
  • app/src/components/settings/panels/DeveloperOptionsPanel.tsx — adds a "App logs" row that shows the resolved path (logs_folder_path) and a button that calls reveal_logs_folder. Hidden in non-Tauri builds via isTauri().
  • app/src-tauri/permissions/allow-core-process.toml — capability allowlist entries for the two new commands.

Tradeoffs

  • Where to write. <data_dir>/logs/ (i.e. ~/.openhuman/logs/ by default, or under OPENHUMAN_WORKSPACE) keeps logs next to active_user.toml, the per-user users/ tree, and the CEF caches a support engineer would also need. OS-conventional paths (~/Library/Logs/OpenHuman, %LOCALAPPDATA%\OpenHuman\Logs) are what Console.app / Event Viewer expect but split logs per user and need per-platform code; the OPENHUMAN_WORKSPACE test override would also stop working.
  • Retention. 7 days at info (or debug if OPENHUMAN_VERBOSE=1 / RUST_LOG=debug). Bumpable later if a debug session needs deeper history.
  • Cold-start gap. init_for_embedded runs after sentry::init but before everything else (CEF preflight included). Anything that fails before run() enters our init still hits stderr only — that's currently main.rs argv parsing and tauri::cef_entry_point, neither of which has user-actionable failures today.
  • Redaction. Per CLAUDE.md, no tokens or full PII. Nothing in this PR newly logs sensitive data; the bridge just exposes existing log calls.

Submission Checklist

  • Tests added or updated — N/A: this PR is a logging plumbing change. The new file appender is exercised by the existing init_for_cli_run path tests and by manual smoke (open Settings → Developer Options → Open logs folder; verify a daily file appears). Adding a test that asserts file contents would require sequencing around tracing-appender's background flushing thread and is brittle.
  • Diff coverage ≥ 80%N/A: same reason. Will follow up with a focused test if the coverage gate flags it.
  • Coverage matrix updated — N/A: behaviour-only change (no new product feature).
  • Affected feature IDs in ## RelatedN/A.
  • No new external network dependencies — confirmed (tracing-appender is local-only).
  • Manual smoke checklist updated — not required (no release-cut surface change).
  • Linked issue closed — N/A (no tracking issue).

Impact

  • Desktop only: the embedded path is desktop-only by design; mobile/web are unaffected.
  • Performance: file writes go through tracing_appender::non_blocking, so the UI thread never blocks on disk I/O. Background thread is owned by a process-lifetime OnceLock<WorkerGuard>.
  • Disk: at info, daily logs are typically <1 MB. 7-day cap keeps total footprint bounded.
  • Security: no new PII surfaces; file lives under the user's existing ~/.openhuman/ data dir, same TCC/permission scope as today's storage.
  • Migration: none. First launch after merge creates the logs/ subdir.

Related

  • Closes:
  • Follow-up PR(s)/TODOs: optional — surface "Reveal today's log file" (vs the folder) once we add a clearer support flow.

AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/embedded-file-logging
  • Commit SHA: bb8d1ca

Validation Run

  • pnpm --filter openhuman-app format:check — ran via pnpm format
  • pnpm typecheck
  • Focused tests: N/A (plumbing change)
  • Rust fmt/check (if changed): cargo check --manifest-path Cargo.toml --lib
  • Tauri fmt/check (if changed): cargo check --manifest-path app/src-tauri/Cargo.toml

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: embedded core + Tauri shell now write logs to a daily-rotated file under <data_dir>/logs/ instead of stderr only.
  • User-visible effect: new "Open logs folder" row in Settings → Developer Options. No change to the visible UI flow elsewhere.

Parity Contract

  • Legacy behavior preserved: init_for_cli_run (used by openhuman run / CLI subcommands) keeps the same stderr output and EnvFilter defaults.
  • Guard/fallback/dispatch parity checks: Once-guarded subscriber init; non-blocking writer guard pinned for the process lifetime; reveal_logs_folder returns a typed error if logging hasn't been initialized.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this one
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Added a "Logs Folder" option to the Developer Options panel, allowing users to view and access application log files directly.
  • Chores

    • Enhanced logging infrastructure with daily-rotated log file support for improved diagnostics and debugging.

The Tauri shell previously initialized `env_logger` to stderr and the
embedded core never installed a tracing subscriber at all (only the CLI
path called `init_for_cli_run`). Packaged GUI builds therefore had no
visible diagnostics when users hit issues like "stuck on Initializing
OpenHuman..." — there was nothing for support to ask them to share.

Add `core::logging::init_for_embedded(data_dir, verbose)` which:

  * installs the existing `CleanCliFormat` against both stderr (ANSI when
    attached to a TTY) and a non-blocking, daily-rotated file appender at
    `<data_dir>/logs/openhuman-YYYY-MM-DD.log` (7-day retention),
  * keeps the Sentry tracing layer + `EnvFilter` honoring `RUST_LOG`,
  * bridges the shell's `log::*` calls via `tracing_log::LogTracer` so
    every shell + core event lands in one file.

Wire it from `app/src-tauri/src/file_logging.rs` (called early in
`run()`, before CEF preflight) and surface the folder in Settings →
Developer Options via two new commands `logs_folder_path` /
`reveal_logs_folder` so users can grab today's log and send it.

Refactor `init_for_cli_run` to share `seed_rust_log`,
`build_env_filter`, and `sentry_tracing_layer` with the embedded path —
single source of truth for the formatter + filter.
@senamakel senamakel requested a review from a team May 6, 2026 08:20
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

This PR introduces a file-based logging system that captures daily-rotated logs to disk in both CLI and embedded (Tauri) contexts. It adds a tracing-appender dependency, refactors the core logging initialization to support file output with constraint-based filtering, wires a new Tauri module to expose log directory access, and adds UI in the developer options panel to view and reveal the logs folder.

Changes

File-Based Logging with Tauri Integration

Layer / File(s) Summary
Dependencies
Cargo.toml
Add tracing-appender = "0.2" for daily-rotated log files.
Core Logging Infrastructure
src/core/logging.rs
Refactor logging to support both CLI and embedded modes with file output. Introduce static guards for file appender and log directory, constraint-based event filtering, and a new public log_directory() function.
Tauri File Logging Module
app/src-tauri/src/file_logging.rs
New module implementing init() to set up embedded logging, logs_folder_path() to retrieve the log directory, and reveal_logs_folder() to open the folder via platform-specific commands (open/explorer/xdg-open).
Tauri App Wiring
app/src-tauri/src/lib.rs
Declare file_logging module, call file_logging::init() during startup, and register reveal_logs_folder and logs_folder_path as Tauri commands alongside a new mascot_window_hide command.
Permission Configuration
app/src-tauri/permissions/allow-core-process.toml
Add logs_folder_path and reveal_logs_folder to the allowed Core process commands.
Developer UI
app/src/components/settings/panels/DeveloperOptionsPanel.tsx
Introduce LogsFolderRow component that uses useEffect and Tauri invoke to fetch the log path, display it, and provide a button to reveal the folder. Insert the component in the developer options panel before the Sentry test row.

Sequence Diagram

sequenceDiagram
    participant Frontend as Developer Panel
    participant Tauri as Tauri Shell
    participant Core as Embedded Core
    participant FileSystem as File System

    rect rgba(100, 200, 100, 0.5)
    note over Frontend,FileSystem: Fetch Logs Folder Path
    Frontend->>Tauri: invoke("logs_folder_path")
    Tauri->>Core: query log directory
    Core->>FileSystem: resolve logs path
    FileSystem-->>Core: path
    Core-->>Tauri: logs path
    Tauri-->>Frontend: path string
    Frontend->>Frontend: display path
    end

    rect rgba(100, 150, 200, 0.5)
    note over Frontend,FileSystem: Open Logs Folder
    Frontend->>Tauri: invoke("reveal_logs_folder")
    Tauri->>FileSystem: spawn platform cmd<br/>(open/explorer/xdg-open)
    FileSystem-->>Tauri: folder open
    Tauri-->>Frontend: success
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • tinyhumansai/openhuman#280: Also modifies app/src-tauri/permissions/allow-core-process.toml to add new allowed commands; may need conflict resolution or sequencing.

Poem

🐰 Logs flow like morning streams so clear,
Daily rotated, archived here,
From Tauri's shell to folder's door,
Developers peek and debug more!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: introducing daily-rotated logging for both embedded core and shell components to a centralized logs directory.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/core/logging.rs (1)

241-241: 💤 Low value

Minor: duplicate constraint parsing.

parse_log_file_constraints() is called twice (lines 241 and 264), each creating a separate Vec<String>. Since this runs once at startup, the overhead is negligible. If you want to tighten it, parse once and clone for the second filter closure.

♻️ Optional: parse constraints once
         appender.map(|appender| {
             let (writer, guard) = tracing_appender::non_blocking(appender);
             let _ = FILE_GUARD.set(guard);
             let _ = LOG_DIR.set(logs_dir.clone());
-            let constraints = parse_log_file_constraints();
+            let file_constraints = constraints.clone();
             tracing_subscriber::fmt::layer()
                 .with_ansi(false)
                 .event_format(CleanCliFormat)
                 .with_writer(writer)
                 .with_filter(tracing_subscriber::filter::filter_fn(move |meta| {
-                    event_matches_file_constraints(meta, &constraints)
+                    event_matches_file_constraints(meta, &file_constraints)
                 }))
         })
     }
     // ... earlier in the function, before the match:
+    let constraints = parse_log_file_constraints();

Also applies to: 264-264

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/logging.rs` at line 241, parse_log_file_constraints() is being
called twice producing two Vec<String>; call it once, store the result in a
local variable (e.g. let constraints = parse_log_file_constraints()), then reuse
it by cloning when needed for the second filter/closure (e.g. let
constraints_for_other = constraints.clone()) so both closures use the same
parsed data; update the two places that currently call
parse_log_file_constraints() to use the stored variable and its clone instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src-tauri/src/file_logging.rs`:
- Around line 39-41: The fallback to PathBuf::from(".openhuman") in the call to
default_root_openhuman_dir() can put logs in a relative, unpredictable location;
change the fallback to a robust absolute location (for example use
std::env::temp_dir().join("openhuman") or another absolute directory) and emit a
warning when default_root_openhuman_dir() fails so the user/maintainer is aware;
update the call site that uses default_root_openhuman_dir() and the surrounding
error handling to construct an absolute fallback path and call the logger (or
e.g., warn!()) with context including that the home-dir lookup failed and which
fallback was chosen.

---

Nitpick comments:
In `@src/core/logging.rs`:
- Line 241: parse_log_file_constraints() is being called twice producing two
Vec<String>; call it once, store the result in a local variable (e.g. let
constraints = parse_log_file_constraints()), then reuse it by cloning when
needed for the second filter/closure (e.g. let constraints_for_other =
constraints.clone()) so both closures use the same parsed data; update the two
places that currently call parse_log_file_constraints() to use the stored
variable and its clone instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f71d657a-3409-4e6a-b44c-77d3c5ee323e

📥 Commits

Reviewing files that changed from the base of the PR and between a3a1843 and bb8d1ca.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • app/src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • Cargo.toml
  • app/src-tauri/permissions/allow-core-process.toml
  • app/src-tauri/src/file_logging.rs
  • app/src-tauri/src/lib.rs
  • app/src/components/settings/panels/DeveloperOptionsPanel.tsx
  • src/core/logging.rs

Comment on lines +39 to +41
openhuman_core::openhuman::config::default_root_openhuman_dir()
.unwrap_or_else(|_| PathBuf::from(".openhuman"))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fallback to relative path ".openhuman" may resolve unexpectedly.

If default_root_openhuman_dir() fails (unlikely but possible if dirs::home_dir() returns None), the fallback PathBuf::from(".openhuman") resolves relative to the current working directory — which varies depending on how the app is launched. Logs would land in an unexpected location.

Consider logging a warning or using a more robust fallback (e.g., temp dir):

🛡️ Suggested fix
     openhuman_core::openhuman::config::default_root_openhuman_dir()
-        .unwrap_or_else(|_| PathBuf::from(".openhuman"))
+        .unwrap_or_else(|err| {
+            eprintln!("[file_logging] default_root_openhuman_dir failed: {err}; using temp dir");
+            std::env::temp_dir().join("openhuman")
+        })
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src-tauri/src/file_logging.rs` around lines 39 - 41, The fallback to
PathBuf::from(".openhuman") in the call to default_root_openhuman_dir() can put
logs in a relative, unpredictable location; change the fallback to a robust
absolute location (for example use std::env::temp_dir().join("openhuman") or
another absolute directory) and emit a warning when default_root_openhuman_dir()
fails so the user/maintainer is aware; update the call site that uses
default_root_openhuman_dir() and the surrounding error handling to construct an
absolute fallback path and call the logger (or e.g., warn!()) with context
including that the home-dir lookup failed and which fallback was chosen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant