feat(logging): Add debug logging with tray menu level selector#64
Conversation
- 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>
Add tray menu log level selector and route app/debug logs to stdout and rotating files using
|
There was a problem hiding this comment.
💡 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".
| fn redact_value(value: &str) -> String { | ||
| if value.len() <= 12 { | ||
| "[REDACTED]".to_string() | ||
| } else { | ||
| format!("{}...{}", &value[..4], &value[value.len() - 4..]) |
There was a problem hiding this comment.
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 👍 / 👎.
| // 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)); |
There was a problem hiding this comment.
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 👍 / 👎.
| if let Some(query_start) = url.find('?') { | ||
| let (base, query) = url.split_at(query_start + 1); | ||
| let redacted_params: Vec<String> = query |
There was a problem hiding this comment.
🟢 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".
| } | ||
|
|
||
| #[tauri::command] | ||
| fn get_log_path(app_handle: tauri::AppHandle) -> Result<String, String> { |
There was a problem hiding this comment.
🟢 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".
| 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..]) |
There was a problem hiding this comment.
🟢 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".
- 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>
There was a problem hiding this comment.
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.
- 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>
a847a0a to
a1ae335
Compare
…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.
There was a problem hiding this comment.
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.
| log::LevelFilter::Trace => "trace", | ||
| log::LevelFilter::Off => "off", | ||
| }; | ||
| log::info!("Log level changing to {:?}", level); |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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.
| store.set(LOG_LEVEL_STORE_KEY, serde_json::json!(level_str)); | ||
| let _ = store.save(); | ||
| } | ||
| log::set_max_level(level); |
There was a problem hiding this comment.
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.
| "api_key", "apiKey", "authorization", "bearer", "credential", | ||
| "session_token", "sessionToken", "auth_token", "authToken", | ||
| "user_id", "account_id", "email", | ||
| ]; |
There was a problem hiding this comment.
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.
| }, | ||
| log: { | ||
| trace: vi.fn(), | ||
| debug: vi.fn(), |
There was a problem hiding this comment.
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.


TLDR
Adds persistent debug logging with a tray menu selector for log level, enabling easier debugging for users experiencing issues.
Description
~/Library/Logs/com.sunstory.openusage/with 10MB max sizefirst4...last4format in logsconsole.errorandconsole.warnare forwarded to the log fileMade 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_pathcommand exposes the on-disk log location.Introduces a tray “Debug Level” submenu that sets
log::set_max_leveland persists the chosen level tosettings.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 forwardsconsole.error/console.warnto 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.