Skip to content

feat(logging): Add debug logging with tray menu level selector#64

Merged
robinebers merged 4 commits into
mainfrom
feat/add-debug-logger
Feb 3, 2026
Merged

feat(logging): Add debug logging with tray menu level selector#64
robinebers merged 4 commits into
mainfrom
feat/add-debug-logger

Conversation

@robinebers
Copy link
Copy Markdown
Owner

@robinebers robinebers commented Feb 3, 2026

TLDR

Adds persistent debug logging with a tray menu selector for log level, enabling easier debugging for users experiencing issues.

Description

  • File logging: Logs to ~/Library/Logs/com.sunstory.openusage/ with 10MB max size
  • Tray menu selector: Right-click tray → "Debug Level" submenu with Error/Warn/Info/Debug/Trace options (persisted to settings)
  • PII redaction: Sensitive data (user_id, email, tokens, API keys, JWTs) shown as first4...last4 format in logs
  • Frontend forwarding: console.error and console.warn are forwarded to the log file

Made with Cursor


Note

Medium Risk
Expands logging across backend, frontend, and plugin HTTP calls (including response previews), which increases the chance of leaking sensitive data if redaction misses edge cases; also changes runtime log filtering via tray settings.

Overview
Adds persistent, configurable logging: the Tauri log plugin is reconfigured to write to stdout and the OS log directory with rotation, startup/probe lifecycle logs are added, and a new get_log_path command exposes the on-disk log location.

Introduces a tray “Debug Level” submenu that sets log::set_max_level and persists the chosen level to settings.json. Plugin probes (claude, codex, cursor) gain detailed status/error logs around credential loading, token refresh, and usage fetch.

Adds HTTP request/response logging for plugin runtime with PII/token redaction (URL query params + body patterns via regex-lite) and unit tests for the redaction helpers. The frontend now forwards console.error/console.warn to the Tauri log file via @tauri-apps/plugin-log.

Written by Cursor Bugbot for commit 4589541. This will update automatically on new commits. Configure here.

- File logging to ~/Library/Logs with 10MB max size
- Tray menu "Debug Level" submenu (Error/Warn/Info/Debug/Trace), persisted
- HTTP request/response logging with PII redaction (first4...last4)
- Forward frontend console.error/warn to log file

Co-authored-by: Cursor <cursoragent@cursor.com>
@macroscopeapp
Copy link
Copy Markdown

macroscopeapp Bot commented Feb 3, 2026

Add tray menu log level selector and route app/debug logs to stdout and rotating files using tauri_plugin_log

Add a tray Debug Level submenu with persisted levels; configure tauri_plugin_log to write to Stdout and LogDir (10 MB rotation, global Trace, crate filters); forward console.warn/console.error to host logs; emit redacted HTTP request/response previews and new debug/info/warn/error logs across plugin auth/probe flows; expose get_log_path Tauri command; add redaction helpers with tests. Key entry points: src-tauri/src/tray.rs, src-tauri/src/lib.rs, src-tauri/src/plugin_engine/host_api.rs, src/main.tsx.

📍Where to Start

Start with the logging setup in run and new get_log_path command in src-tauri/src/lib.rs, then review tray log level handling in src-tauri/src/tray.rs, and the HTTP redaction/logging in src-tauri/src/plugin_engine/host_api.rs.


Macroscope summarized 4589541.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 865b983bda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/plugin_engine/host_api.rs Outdated
Comment on lines +5 to +9
fn redact_value(value: &str) -> String {
if value.len() <= 12 {
"[REDACTED]".to_string()
} else {
format!("{}...{}", &value[..4], &value[value.len() - 4..])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid UTF-8 slicing panic in redaction

The redaction helper slices by byte index (&value[..4], value.len() - 4), which panics on non-ASCII UTF-8 boundaries. This path is used when logging HTTP URLs/bodies, so any plugin response or query parameter containing multibyte characters (e.g., localized IDs, emails with Unicode) can crash the app during logging. Use chars()/graphemes or byte-safe truncation to avoid runtime panics.

Useful? React with 👍 / 👎.

Comment thread src-tauri/src/lib.rs
Comment on lines +198 to +202
// macOS log directory: ~/Library/Logs/{bundleIdentifier}
let home = dirs::home_dir().ok_or("no home dir")?;
let bundle_id = app_handle.config().identifier.clone();
let log_dir = home.join("Library").join("Logs").join(&bundle_id);
let log_file = log_dir.join(format!("{}.log", app_handle.package_info().name));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return platform log dir instead of macOS-only path

This command hardcodes ~/Library/Logs/{bundle} and always builds a macOS path. The app bundles for all targets (tauri.conf.json has bundle.targets: "all"), so on Windows/Linux this returns a path that does not exist and does not match tauri_plugin_log’s log directory, breaking any UI that opens logs. Prefer app_handle.path().app_log_dir() or the log plugin’s path helpers.

Useful? React with 👍 / 👎.

Comment on lines +20 to +22
if let Some(query_start) = url.find('?') {
let (base, query) = url.split_at(query_start + 1);
let redacted_params: Vec<String> = query
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Low

src-tauri/src/plugin_engine/host_api.rs:20

URL fragments (e.g., #anchor) get included in the last parameter's value. Consider splitting off the fragment before processing query parameters, then re-appending it.

-    if let Some(query_start) = url.find('?') {
-        let (base, query) = url.split_at(query_start + 1);
+    if let Some(query_start) = url.find('?') {
+        let (base, rest) = url.split_at(query_start + 1);
+        let (query, fragment) = rest.split_once('#').map_or((rest, ""), |(q, f)| (q, f));
         let redacted_params: Vec<String> = query

🚀 Want me to fix this? Reply ex: "fix it for me".

Comment thread src-tauri/src/lib.rs
}

#[tauri::command]
fn get_log_path(app_handle: tauri::AppHandle) -> Result<String, String> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Low

src-tauri/src/lib.rs:197

This hardcodes a macOS-specific path (Library/Logs) but compiles on all platforms. Consider using #[cfg(target_os = "macos")] or returning an error on unsupported platforms.

🚀 Want me to fix this? Reply ex: "fix it for me".

Comment thread src-tauri/src/plugin_engine/host_api.rs Outdated
Comment on lines +61 to +65
let api_key_pattern = regex_lite::Regex::new(r#"["']?(sk-|pk-|api_|key_|secret_)[A-Za-z0-9_-]{12,}["']?"#).unwrap();
result = api_key_pattern.replace_all(&result, |caps: &regex_lite::Captures| {
let key = caps[0].trim_matches(|c| c == '"' || c == '\'');
if key.len() > 12 {
format!("{}...{}", &key[..4], &key[key.len() - 4..])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Low

src-tauri/src/plugin_engine/host_api.rs:61

The regex captures optional quotes, and trim_matches removes them, but the replacement doesn't re-add them. This produces invalid JSON like {"key": sk-1...ghij}. Consider preserving the original quotes in the replacement.

-    let api_key_pattern = regex_lite::Regex::new(r#"["']?(sk-|pk-|api_|key_|secret_)[A-Za-z0-9_-]{12,}["']?"#).unwrap();
-    result = api_key_pattern.replace_all(&result, |caps: ®ex_lite::Captures| {
-        let key = caps[0].trim_matches(|c| c == '"' || c == '\'');
-        if key.len() > 12 {
-            format!("{}...{}", &key[..4], &key[key.len() - 4..])
+    let api_key_pattern = regex_lite::Regex::new(r#"(["']?)(sk-|pk-|api_|key_|secret_)[A-Za-z0-9_-]{12,}(["']?)"#).unwrap();
+    result = api_key_pattern.replace_all(&result, |caps: ®ex_lite::Captures| {
+        let open_quote = &caps[1];
+        let close_quote = &caps[3];
+        let key = caps[0].trim_matches(|c| c == '"' || c == '\'');
+        if key.len() > 12 {
+            format!("{}{}...{}{}"open_quote, &key[..4], &key[key.len() - 4..], close_quote)

🚀 Want me to fix this? Reply ex: "fix it for me".

Comment thread src-tauri/src/tray.rs Outdated
- Log credential loading (source, success/failure)
- Log token refresh attempts with status codes and error codes
- Log usage API call results and auth failures
- Helps debug "Token expired" errors reported by users

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread src-tauri/src/plugin_engine/host_api.rs Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 8 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/main.tsx">

<violation number="1" location="src/main.tsx:11">
P2: Object arguments will be logged as `[object Object]`, making debug logs unhelpful. Consider using a serialization function that handles objects properly (e.g., `JSON.stringify` with a try-catch fallback).</violation>
</file>

<file name="src-tauri/src/tray.rs">

<violation number="1" location="src-tauri/src/tray.rs:26">
P2: Missing `"off"` case in `get_stored_log_level` creates asymmetry with `set_stored_log_level`. If the store contains `"off"`, it will incorrectly be read as `Error` instead of `Off`.</violation>
</file>

<file name="src-tauri/src/plugin_engine/host_api.rs">

<violation number="1" location="src-tauri/src/plugin_engine/host_api.rs:9">
P1: String slicing by byte index can panic on multi-byte UTF-8 characters. Use character-aware slicing with `.chars()` to safely get the first and last 4 characters.</violation>

<violation number="2" location="src-tauri/src/plugin_engine/host_api.rs:287">
P1: String slicing by byte index can panic if byte 500 falls in the middle of a multi-byte UTF-8 character. Use `body.get(..500)` or `body.char_indices()` for safe truncation.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src-tauri/src/plugin_engine/host_api.rs Outdated
Comment thread src-tauri/src/plugin_engine/host_api.rs Outdated
Comment thread src/main.tsx Outdated
Comment thread src-tauri/src/tray.rs
Comment thread src-tauri/src/plugin_engine/host_api.rs
Comment thread src-tauri/src/tray.rs Outdated
- Use UTF-8 safe char iteration for redaction (prevents panic on multi-byte)
- Add missing "off" case in get_stored_log_level
- Log level change message before set_max_level (so it's visible)
- Improve frontend logging to serialize objects properly (JSON.stringify)

Co-authored-by: Cursor <cursoragent@cursor.com>
@robinebers robinebers force-pushed the feat/add-debug-logger branch from a847a0a to a1ae335 Compare February 3, 2026 14:41
Comment thread src-tauri/src/plugin_engine/host_api.rs Outdated
Comment thread src-tauri/src/plugin_engine/host_api.rs
Comment thread src-tauri/src/lib.rs
…response handling

- Added trace and debug logging functions to the test helpers for improved diagnostics.
- Implemented body redaction before truncation in HTTP response logging to ensure sensitive information is protected.
- Updated logging to use the redacted body preview for better security and clarity in logs.
@robinebers robinebers merged commit 03757a6 into main Feb 3, 2026
2 checks passed
@robinebers robinebers deleted the feat/add-debug-logger branch February 3, 2026 14:59
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src-tauri/src/tray.rs">

<violation number="1" location="src-tauri/src/tray.rs:39">
P2: Log message may be silently dropped when changing from a restrictive log level. The `log::info!` is called before `log::set_max_level(level)`, so if the current level is `Error` and user changes to `Debug`, this message won't be logged since it's filtered by the old level. Move this after `set_max_level` to ensure visibility.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src-tauri/src/tray.rs
log::LevelFilter::Trace => "trace",
log::LevelFilter::Off => "off",
};
log::info!("Log level changing to {:?}", level);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Feb 3, 2026

Choose a reason for hiding this comment

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

P2: Log message may be silently dropped when changing from a restrictive log level. The log::info! is called before log::set_max_level(level), so if the current level is Error and user changes to Debug, this message won't be logged since it's filtered by the old level. Move this after set_max_level to ensure visibility.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/src/tray.rs, line 39:

<comment>Log message may be silently dropped when changing from a restrictive log level. The `log::info!` is called before `log::set_max_level(level)`, so if the current level is `Error` and user changes to `Debug`, this message won't be logged since it's filtered by the old level. Move this after `set_max_level` to ensure visibility.</comment>

<file context>
@@ -36,30 +36,14 @@ fn set_stored_log_level(app_handle: &AppHandle, level: log::LevelFilter) {
         log::LevelFilter::Trace => "trace",
         log::LevelFilter::Off => "off",
     };
+    log::info!("Log level changing to {:?}", level);
     if let Ok(store) = app_handle.store("settings.json") {
         store.set(LOG_LEVEL_STORE_KEY, serde_json::json!(level_str));
</file context>
Fix with Cubic

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Comment thread src-tauri/src/tray.rs
store.set(LOG_LEVEL_STORE_KEY, serde_json::json!(level_str));
let _ = store.save();
}
log::set_max_level(level);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Log level change message filtered when relaxing level

Low Severity

The log::info! call at line 39 executes before log::set_max_level(level) at line 44. When changing from a more restrictive level (like Error) to a less restrictive level (like Info), the "Log level changing to..." message is filtered out because the max level is still at the old restrictive setting when the info message is emitted. The confirmation message only appears when going from less restrictive to more restrictive levels.

Fix in Cursor Fix in Web

"api_key", "apiKey", "authorization", "bearer", "credential",
"session_token", "sessionToken", "auth_token", "authToken",
"user_id", "account_id", "email",
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing camelCase token keys in PII redaction list

Low Severity

The sensitive_keys list includes some camelCase variants (apiKey, sessionToken, authToken) but is missing common ones like accessToken, refreshToken, userId, and accountId. If any API responses contain these camelCase key names, the associated values won't be redacted by the JSON key pattern. The JWT and API key prefix patterns provide partial defense-in-depth, but non-JWT tokens using camelCase keys could leak in logs.

Fix in Cursor Fix in Web

Comment thread plugins/test-helpers.js
},
log: {
trace: vi.fn(),
debug: vi.fn(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test mock exposes log methods not in production API

Low Severity

The test mock's log object now includes trace and debug methods, but the production inject_log function in host_api.rs only exposes info, warn, and error. Plugin code calling ctx.host.log.trace() or ctx.host.log.debug() would pass tests but fail at runtime. While current plugin code doesn't use these methods, this mismatch could mask bugs in future plugin development.

Fix in Cursor Fix in Web

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