feat(logging): capture frontend errors to disk with structured redaction

Frontend half of the logging overhaul.

Capture:
- Add a global error boundary plus window error/unhandledrejection
  handlers that persist renderer errors via tauri-plugin-log (new
  @tauri-apps/plugin-log dependency + log:default capability), so a
  white-screen crash leaves a trace.

Redaction (two layers over every logged value):
- Structured serializer: redact by sensitive property name (arrays and
  nested values included) and by opaque value shape.
- Text layer, the universal final pass: redact URL credentials/query,
  auth schemes, known secret shapes, and named-key array/object values.
  The container rule closes prefix+JSON, double-encoded, and POJO-error
  bypasses regardless of which entrypoint the structured data came from.
- Render Errors as redacted message + native stack across V8 and WebKit
  (macOS/Linux) without depending on a single stack format.
- Bound all inputs and drop oversized JSON to keep the UI responsive.

i18n:
- zh / en / ja / zh-TW for the new log settings and error text.
This commit is contained in:
Jason
2026-07-16 10:21:00 +08:00
parent 22d2872c33
commit 6274705848
12 changed files with 853 additions and 46 deletions
+10 -7
View File
@@ -397,10 +397,10 @@
"cacheInjectionDescription": "Automatically inject standard 5-minute cache breakpoints at key positions to reduce duplicate token billing"
},
"logConfig": {
"title": "Log Management",
"description": "Control log output level",
"enabled": "Enable Logging",
"enabledDescription": "Master switch, all logging will be disabled when turned off",
"title": "Application Diagnostic Logs",
"description": "Control the output level for application diagnostic logs",
"enabled": "Enable Diagnostic Logs",
"enabledDescription": "Controls cc-switch.log; request usage records and crash.log are unaffected",
"level": "Log Level",
"levelDescription": "Set the minimum log level to output",
"levels": {
@@ -1701,7 +1701,10 @@
"errors": {
"usage_query_failed": "Usage query failed",
"configLoadFailedTitle": "Configuration Load Failed",
"configLoadFailedMessage": "Unable to read configuration file:\n{{path}}\n\nError details:\n{{detail}}\n\nPlease check if the JSON is valid, or restore from a backup file (e.g., config.json.bak) in the same directory.\n\nThe app will exit so you can fix this."
"configLoadFailedMessage": "Unable to read configuration file:\n{{path}}\n\nError details:\n{{detail}}\n\nPlease check if the JSON is valid, or restore from a backup file (e.g., config.json.bak) in the same directory.\n\nThe app will exit so you can fix this.",
"frontendCrashTitle": "Something went wrong in the interface",
"frontendCrashMessage": "An attempt was made to write the error to the application diagnostic log. Reload the interface, and attach the log when opening an issue if the problem continues.",
"reloadInterface": "Reload interface"
},
"presetSelector": {
"title": "Select Configuration Type",
@@ -2533,8 +2536,8 @@
"description": "Maximum wait time for a single request (0 = unlimited, or 10 ~ 600 seconds)"
},
"enableLogging": {
"label": "Enable Logging",
"description": "Log all routing requests for troubleshooting"
"label": "Record Request Usage",
"description": "Write routing request usage and status to the local statistics database"
},
"streamingFirstByteTimeout": {
"label": "Streaming First Byte Timeout (sec)",
+10 -7
View File
@@ -397,10 +397,10 @@
"cacheInjectionDescription": "リクエストの重要な位置に標準の 5 分間 Cache ブレークポイントを自動注入し、重複トークンの課金を削減"
},
"logConfig": {
"title": "ログ管理",
"description": "ログ出力レベルを制御",
"enabled": "ログを有効化",
"enabledDescription": "マスタースイッチ、オフにするとすべてのログが無効になります",
"title": "アプリ診断ログ",
"description": "アプリ診断ログ出力レベルを制御",
"enabled": "診断ログを有効化",
"enabledDescription": "cc-switch.log を制御します。リクエスト使用量記録と crash.log には影響しません",
"level": "ログレベル",
"levelDescription": "出力する最小ログレベルを設定",
"levels": {
@@ -1701,7 +1701,10 @@
"errors": {
"usage_query_failed": "利用状況の取得に失敗しました",
"configLoadFailedTitle": "設定の読み込みに失敗しました",
"configLoadFailedMessage": "設定ファイルを読み込めません:\n{{path}}\n\nエラー詳細:\n{{detail}}\n\nJSON が正しいか確認するか、同じディレクトリのバックアップファイル(config.json.bak など)から復元してください。\n\nアプリを終了して修正してください。"
"configLoadFailedMessage": "設定ファイルを読み込めません:\n{{path}}\n\nエラー詳細:\n{{detail}}\n\nJSON が正しいか確認するか、同じディレクトリのバックアップファイル(config.json.bak など)から復元してください。\n\nアプリを終了して修正してください。",
"frontendCrashTitle": "画面で問題が発生しました",
"frontendCrashMessage": "エラー情報をアプリ診断ログに保存しようとしました。画面を再読み込みし、問題が続く場合は Issue にログを添付してください。",
"reloadInterface": "画面を再読み込み"
},
"presetSelector": {
"title": "設定タイプを選択",
@@ -2533,8 +2536,8 @@
"description": "単一リクエストの最大待機時間(0 = 無制限、または 10 ~ 600 秒)"
},
"enableLogging": {
"label": "ログ記録を有効化",
"description": "トラブルシューティングのためにすべてのルーティングリクエストを記録"
"label": "リクエスト使用量を記録",
"description": "ルーティングリクエストの使用量と状態をローカル統計データベースに保存"
},
"streamingFirstByteTimeout": {
"label": "ストリーミング初回バイトタイムアウト(秒)",
+10 -7
View File
@@ -397,10 +397,10 @@
"cacheInjectionDescription": "自動在請求關鍵位置注入標準 5 分鐘 Cache 斷點,減少重複 token 計費"
},
"logConfig": {
"title": "日誌管理",
"description": "控制日誌輸出層級",
"enabled": "啟用日誌",
"enabledDescription": "總開關,關閉後所有日誌將被停用",
"title": "應用程式診斷日誌",
"description": "控制應用程式診斷日誌輸出層級",
"enabled": "啟用應用程式診斷日誌",
"enabledDescription": "控制 cc-switch.log;不影響請求用量記錄和 crash.log",
"level": "日誌層級",
"levelDescription": "設定輸出的最低日誌層級",
"levels": {
@@ -1673,7 +1673,10 @@
"errors": {
"usage_query_failed": "用量查詢失敗",
"configLoadFailedTitle": "設定載入失敗",
"configLoadFailedMessage": "無法讀取設定檔:\n{{path}}\n\n錯誤詳情:\n{{detail}}\n\n請手動檢查 JSON 是否有效,或從同目錄的備份檔案(如 config.json.bak)還原。\n\n應用程式將退出以便您進行修復。"
"configLoadFailedMessage": "無法讀取設定檔:\n{{path}}\n\n錯誤詳情:\n{{detail}}\n\n請手動檢查 JSON 是否有效,或從同目錄的備份檔案(如 config.json.bak)還原。\n\n應用程式將退出以便您進行修復。",
"frontendCrashTitle": "介面遇到了問題",
"frontendCrashMessage": "已嘗試將錯誤資訊寫入應用程式診斷日誌。請重新載入介面;如果問題持續,請在提交 Issue 時附上日誌。",
"reloadInterface": "重新載入介面"
},
"presetSelector": {
"title": "選擇設定類型",
@@ -2505,8 +2508,8 @@
"description": "單個請求的最大等待時間(0 表示不限制,或設定 10 ~ 600 秒)"
},
"enableLogging": {
"label": "啟用日誌紀錄",
"description": "紀錄所有路由請求,便於排查問題"
"label": "記錄請求用量",
"description": "將路由請求的用量與狀態寫入本機統計資料庫"
},
"streamingFirstByteTimeout": {
"label": "串流首位元組逾時(秒)",
+10 -7
View File
@@ -397,10 +397,10 @@
"cacheInjectionDescription": "自动在请求关键位置注入标准 5 分钟 Cache 断点,减少重复 token 计费"
},
"logConfig": {
"title": "日志管理",
"description": "控制日志输出级别",
"enabled": "启用日志",
"enabledDescription": "总开关,关闭后所有日志将被禁用",
"title": "应用诊断日志",
"description": "控制应用诊断日志输出级别",
"enabled": "启用应用诊断日志",
"enabledDescription": "控制 cc-switch.log;不影响请求用量记录和 crash.log",
"level": "日志级别",
"levelDescription": "设置输出的最低日志级别",
"levels": {
@@ -1701,7 +1701,10 @@
"errors": {
"usage_query_failed": "用量查询失败",
"configLoadFailedTitle": "配置加载失败",
"configLoadFailedMessage": "无法读取配置文件:\n{{path}}\n\n错误详情:\n{{detail}}\n\n请手动检查 JSON 是否有效,或从同目录的备份文件(如 config.json.bak)恢复。\n\n应用将退出以便您进行修复。"
"configLoadFailedMessage": "无法读取配置文件:\n{{path}}\n\n错误详情:\n{{detail}}\n\n请手动检查 JSON 是否有效,或从同目录的备份文件(如 config.json.bak)恢复。\n\n应用将退出以便您进行修复。",
"frontendCrashTitle": "界面遇到了问题",
"frontendCrashMessage": "已尝试将错误信息写入应用诊断日志。请重新加载界面;如果问题持续,请在提交 Issue 时附上日志。",
"reloadInterface": "重新加载界面"
},
"presetSelector": {
"title": "选择配置类型",
@@ -2533,8 +2536,8 @@
"description": "单个请求的最大等待时间(0 表示不限制,或设置 10 ~ 600 秒)"
},
"enableLogging": {
"label": "启用日志记录",
"description": "记录所有路由请求,便于排查问题"
"label": "记录请求用量",
"description": "将路由请求的用量与状态写入本地统计数据库"
},
"streamingFirstByteTimeout": {
"label": "流式首字超时(秒)",