Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: Address PR review feedback
- 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>
  • Loading branch information
robinebers and cursoragent committed Feb 3, 2026
commit a1ae335e0bf3626d97c9bc39f144a8da0a15d944
29 changes: 14 additions & 15 deletions src-tauri/src/plugin_engine/host_api.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use rquickjs::{Ctx, Exception, Function, Object};
use std::path::PathBuf;

/// Redact sensitive value to first4...last4 format
/// Redact sensitive value to first4...last4 format (UTF-8 safe)
fn redact_value(value: &str) -> String {
if value.len() <= 12 {
let chars: Vec<char> = value.chars().collect();
if chars.len() <= 12 {
"[REDACTED]".to_string()
} else {
format!("{}...{}", &value[..4], &value[value.len() - 4..])
let first4: String = chars.iter().take(4).collect();
let last4: String = chars.iter().rev().take(4).collect::<Vec<_>>().into_iter().rev().collect();
format!("{}...{}", first4, last4)
}
}
Comment thread
cursor[bot] marked this conversation as resolved.

Expand Down Expand Up @@ -49,23 +52,14 @@ fn redact_body(body: &str) -> String {
// Redact JWTs (eyJ... pattern with dots)
let jwt_pattern = regex_lite::Regex::new(r"eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+").unwrap();
result = jwt_pattern.replace_all(&result, |caps: &regex_lite::Captures| {
let jwt = &caps[0];
if jwt.len() > 12 {
format!("{}...{}", &jwt[..4], &jwt[jwt.len() - 4..])
} else {
"[JWT]".to_string()
}
redact_value(&caps[0])
}).to_string();

// Redact common API key patterns (sk-xxx, pk-xxx, api_xxx, etc.)
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..])
} else {
"[KEY]".to_string()
}
redact_value(key)
}).to_string();
Comment thread
cursor[bot] marked this conversation as resolved.

// Redact JSON values for sensitive keys
Expand Down Expand Up @@ -284,7 +278,12 @@ fn inject_http<'js>(ctx: &Ctx<'js>, host: &Object<'js>, plugin_id: &str) -> rqui
.map_err(|e| Exception::throw_message(&ctx_inner, &e.to_string()))?;

let body_preview = if body.len() > 500 {
format!("{}... ({} bytes total)", &body[..500], body.len())
// UTF-8 safe truncation: find valid char boundary at or before 500
let truncated: String = body.char_indices()
.take_while(|(i, _)| *i < 500)
.map(|(_, c)| c)
.collect();
format!("{}... ({} bytes total)", truncated, body.len())
} else {
body.clone()
};
Expand Down
64 changes: 26 additions & 38 deletions src-tauri/src/tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
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

if let Ok(store) = app_handle.store("settings.json") {
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

log::info!("Log level changed to {:?}", level);
}

fn update_log_level_menu_checks(app_handle: &AppHandle, level: log::LevelFilter) {
let levels = [
("log_error", log::LevelFilter::Error),
("log_warn", log::LevelFilter::Warn),
("log_info", log::LevelFilter::Info),
("log_debug", log::LevelFilter::Debug),
("log_trace", log::LevelFilter::Trace),
];
for (id, lvl) in levels {
if let Some(item) = app_handle.menu().and_then(|m| m.get(id)) {
if let Some(check) = item.as_check_menuitem() {
let _ = check.set_checked(lvl == level);
}
}
}
}

macro_rules! get_or_init_panel {
($app_handle:expr) => {
Expand Down Expand Up @@ -102,7 +86,7 @@ pub fn create(app_handle: &AppHandle) -> tauri::Result<()> {
let show_stats = MenuItem::with_id(app_handle, "show_stats", "Show Stats", true, None::<&str>)?;
let go_to_settings = MenuItem::with_id(app_handle, "go_to_settings", "Go to Settings", true, None::<&str>)?;

// Log level submenu
// Log level submenu - clone items for use in event handler
let log_error = CheckMenuItem::with_id(app_handle, "log_error", "Error", true, current_level == log::LevelFilter::Error, None::<&str>)?;
let log_warn = CheckMenuItem::with_id(app_handle, "log_warn", "Warn", true, current_level == log::LevelFilter::Warn, None::<&str>)?;
let log_info = CheckMenuItem::with_id(app_handle, "log_info", "Info", true, current_level == log::LevelFilter::Info, None::<&str>)?;
Expand All @@ -115,6 +99,15 @@ pub fn create(app_handle: &AppHandle) -> tauri::Result<()> {
&[&log_error, &log_warn, &log_info, &log_debug, &log_trace],
)?;

// Clone for capture in event handler
let log_items = [
(log_error.clone(), log::LevelFilter::Error),
(log_warn.clone(), log::LevelFilter::Warn),
(log_info.clone(), log::LevelFilter::Info),
(log_debug.clone(), log::LevelFilter::Debug),
(log_trace.clone(), log::LevelFilter::Trace),
];

let separator = PredefinedMenuItem::separator(app_handle)?;
let about = MenuItem::with_id(app_handle, "about", "About OpenUsage", true, None::<&str>)?;
let quit = MenuItem::with_id(app_handle, "quit", "Quit", true, None::<&str>)?;
Expand All @@ -127,7 +120,7 @@ pub fn create(app_handle: &AppHandle) -> tauri::Result<()> {
.tooltip("OpenUsage")
.menu(&menu)
.show_menu_on_left_click(false)
.on_menu_event(|app_handle, event| {
.on_menu_event(move |app_handle, event| {
log::debug!("tray menu: {}", event.id.as_ref());
match event.id.as_ref() {
"show_stats" => {
Expand All @@ -146,25 +139,20 @@ pub fn create(app_handle: &AppHandle) -> tauri::Result<()> {
log::info!("quit requested via tray");
app_handle.exit(0);
}
"log_error" => {
set_stored_log_level(app_handle, log::LevelFilter::Error);
update_log_level_menu_checks(app_handle, log::LevelFilter::Error);
}
"log_warn" => {
set_stored_log_level(app_handle, log::LevelFilter::Warn);
update_log_level_menu_checks(app_handle, log::LevelFilter::Warn);
}
"log_info" => {
set_stored_log_level(app_handle, log::LevelFilter::Info);
update_log_level_menu_checks(app_handle, log::LevelFilter::Info);
}
"log_debug" => {
set_stored_log_level(app_handle, log::LevelFilter::Debug);
update_log_level_menu_checks(app_handle, log::LevelFilter::Debug);
}
"log_trace" => {
set_stored_log_level(app_handle, log::LevelFilter::Trace);
update_log_level_menu_checks(app_handle, log::LevelFilter::Trace);
"log_error" | "log_warn" | "log_info" | "log_debug" | "log_trace" => {
let selected_level = match event.id.as_ref() {
"log_error" => log::LevelFilter::Error,
"log_warn" => log::LevelFilter::Warn,
"log_info" => log::LevelFilter::Info,
"log_debug" => log::LevelFilter::Debug,
"log_trace" => log::LevelFilter::Trace,
_ => unreachable!(),
};
set_stored_log_level(app_handle, selected_level);
// Update all checkmarks - only the selected level should be checked
for (item, level) in &log_items {
let _ = item.set_checked(*level == selected_level);
}
}
_ => {}
}
Expand Down
16 changes: 14 additions & 2 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,28 @@ import App from "./App";
import "./index.css";

// Forward console.error and console.warn to Tauri log file
function stringify(arg: unknown): string {
if (arg === null) return "null";
if (arg === undefined) return "undefined";
if (typeof arg === "string") return arg;
if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
try {
return JSON.stringify(arg);
} catch {
return String(arg);
}
}

const originalError = console.error;
console.error = (...args: unknown[]) => {
originalError(...args);
logError(args.map(String).join(" ")).catch(() => {});
logError(args.map(stringify).join(" ")).catch(() => {});
};

const originalWarn = console.warn;
console.warn = (...args: unknown[]) => {
originalWarn(...args);
logWarn(args.map(String).join(" ")).catch(() => {});
logWarn(args.map(stringify).join(" ")).catch(() => {});
};

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
Expand Down