Skip to content

Add:redacted diagnostic logs#173

Closed
hych0317 wants to merge 1 commit into
AmintaCCCP:mainfrom
hych0317:codex/add-diagnostic-logs
Closed

Add:redacted diagnostic logs#173
hych0317 wants to merge 1 commit into
AmintaCCCP:mainfrom
hych0317:codex/add-diagnostic-logs

Conversation

@hych0317

@hych0317 hych0317 commented May 29, 2026

Copy link
Copy Markdown
Contributor
image

概述

  • 新增基于 IndexedDB 的本地诊断日志服务。
  • 在设置页新增“日志/诊断日志”面板,支持查看、筛选、导出和清空日志。
  • 为 GitHub 请求、后端代理请求、AI 请求、仓库 AI 分析流程增加状态、耗时和错误信息记录。

隐私与安全

  • 日志不会保存 API Key、Authorization 请求头、完整 prompt 或完整 AI 回复。
  • 日志只保存脱敏后的请求元信息、模型/API 类型、状态码、耗时、错误信息、请求/响应长度摘要。
  • 日志仅保存在本地,不会同步到后端,也不会进入普通数据备份流程。

验证

  • npm run build 通过,本次改动不涉及 Electron 主进程或打包配置。
  • git diff --check 通过。

Summary by CodeRabbit

  • New Features
    • Added a new Logs tab in Settings that displays diagnostic logs with real-time updates, client-side filtering, and search functionality.
    • Users can filter logs by severity level and source, view timestamps and operation details, export logs as JSON files, and clear log history.
    • Enhanced diagnostic logging throughout the application for AI, backend, and GitHub API requests.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR adds a complete diagnostic logging system to capture service lifecycle events across the application, store them locally in IndexedDB with privacy-aware sanitization, and expose them through a new "Logs" tab in the settings panel with filtering and export capabilities.

Changes

Diagnostic Logging Feature

Layer / File(s) Summary
Logging types and core appLogger service
src/types/index.ts, src/services/appLogger.ts
AppLogLevel, AppLogSource, and AppLogEntry types define the logging schema. appLogger service sanitizes sensitive URLs and object keys, implements a serialized write queue to persist entries to IndexedDB (capped at 500), and exposes info/warn/error, getLogs(), clearLogs(), and exportLogs() APIs; dispatches browser events on log changes.
AI service instrumentation
src/services/aiAnalysisHelper.ts, src/services/aiService.ts
analyzeRepository logs initialization details, analysis completion with result counts, and failures with error/duration. aiService.requestText introduces a recordAIRequestSuccess helper and wraps all provider paths (OpenAI, Claude, Gemini) to log request completion with timing/model/character counts and request failures with serialized errors.
Infrastructure service instrumentation
src/services/backendAdapter.ts, src/services/githubApi.ts
fetchWithTimeout logs HTTP request lifecycle (method/status/duration/sanitized URL) on success and pre-response failures. GitHubApiService.makeRequest logs rate-limit warnings, fetch failures, response errors with status/duration, and successful completions with status/duration/item count.
Logs viewer UI
src/components/SettingsPanel.tsx, src/components/settings/LogsPanel.tsx, src/components/settings/index.ts
SettingsPanel adds a logs tab with ScrollText icon and renders LogsPanel on selection. LogsPanel loads logs from appLogger.getLogs(), listens for gsm:diagnostic-log-added and gsm:diagnostic-logs-cleared events, filters logs by search query and level/source selection, and provides refresh, export, and clear controls.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Logs now flutter in the browser's den,
IndexedDB writes them down again,
Sanitized secrets, events traced with care,
A settings panel shows the threads we share!

🚥 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 'Add:redacted diagnostic logs' clearly and specifically describes the main change—adding a diagnostic logging feature with privacy safeguards.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/components/settings/LogsPanel.tsx (1)

48-59: ⚡ Quick win

Debounce the reload on log events.

handleChange triggers a full getLogs() (reads up to maxEntries from IndexedDB) plus re-render on every gsm:diagnostic-log-added. While the panel is open during a chatty flow (e.g., AI analysis issuing many GitHub requests), this can cause a burst of redundant reads and UI jank. A small debounce coalesces the events.

♻️ Proposed debounce
   useEffect(() => {
     loadLogs();
-    const handleChange = () => {
-      void loadLogs();
-    };
+    let timer: ReturnType<typeof setTimeout> | null = null;
+    const handleChange = () => {
+      if (timer) clearTimeout(timer);
+      timer = setTimeout(() => { void loadLogs(); }, 300);
+    };
     window.addEventListener('gsm:diagnostic-log-added', handleChange);
     window.addEventListener('gsm:diagnostic-logs-cleared', handleChange);
     return () => {
+      if (timer) clearTimeout(timer);
       window.removeEventListener('gsm:diagnostic-log-added', handleChange);
       window.removeEventListener('gsm:diagnostic-logs-cleared', handleChange);
     };
   }, [loadLogs]);
🤖 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/components/settings/LogsPanel.tsx` around lines 48 - 59, The event
handler in LogsPanel (inside the useEffect that calls loadLogs) fires loadLogs
on every gsm:diagnostic-log-added and gsm:diagnostic-logs-cleared causing
redundant reads; wrap the reload in a short debounce: replace the inline
handleChange with a debounced version (e.g., useRef timer or a
useDebouncedCallback) that batches rapid events (100–300ms) and only calls
loadLogs once per burst, ensure the cleanup clears the timer and removes the
same debounced handler from window events so loadLogs and the event listeners in
the useEffect still reference the debounced function.
🤖 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 `@src/components/settings/LogsPanel.tsx`:
- Around line 200-204: The CheckCircle/AlertCircle icons in LogsPanel.tsx convey
status only visually; update the JSX that renders CheckCircle and AlertCircle
(the block using entry.success) to add role="img" and a meaningful aria-label
(e.g., aria-label={`Log entry ${entry.success ? 'succeeded' : 'failed'}`} or
similar) so screen readers announce status; keep the existing classNames and
conditional logic but ensure the aria-label reflects the success boolean.
- Around line 182-185: The current JSX always shows "No logs yet" when
filteredLogs.length === 0; change it to differentiate the case where there are
logs but none match the current filters/search. In the LogsPanel component,
replace the single conditional rendering that checks filteredLogs.length with a
nested check: if logs.length === 0 render the existing "No logs yet" message,
otherwise render a distinct message like "No matching logs" (or "No logs match
your filters") so users know filters are excluding results; update the branch
that references filteredLogs and logs accordingly in the component's
render/return.

In `@src/services/aiService.ts`:
- Around line 331-340: The log call in aiService.ts is persisting raw upstream
error bodies via appLogger.serializeError(error); update the error logging in
the AI request failure path (the appLogger.error call inside
requestText()/request handling) to remove or redact provider response bodies
before serialization: extract error metadata you need (status/code/message) but
strip any response.body, response.data, prompt, or text fields added to the
error object by the provider/backend, then pass the sanitized error to
appLogger.serializeError (or log a constructed sanitizedError object) so no raw
provider response content is persisted.

In `@src/services/appLogger.ts`:
- Around line 209-214: clearLogs() currently deletes IndexedDB immediately which
races with pending writes; change it to run via the existing writeQueue so the
delete executes only after prior queued writes and prevents new writes from
running concurrently. Concretely, wrap the idbDelete(LOG_STORAGE_KEY) +
window.dispatchEvent(...) logic inside the writeQueue task runner (e.g.
writeQueue.run / writeQueue.enqueue / writeQueue.add depending on the queue API
used) and keep the canUseIndexedDB and withTimeout checks inside that queued
task so the deletion is serialized with other operations.

---

Nitpick comments:
In `@src/components/settings/LogsPanel.tsx`:
- Around line 48-59: The event handler in LogsPanel (inside the useEffect that
calls loadLogs) fires loadLogs on every gsm:diagnostic-log-added and
gsm:diagnostic-logs-cleared causing redundant reads; wrap the reload in a short
debounce: replace the inline handleChange with a debounced version (e.g., useRef
timer or a useDebouncedCallback) that batches rapid events (100–300ms) and only
calls loadLogs once per burst, ensure the cleanup clears the timer and removes
the same debounced handler from window events so loadLogs and the event
listeners in the useEffect still reference the debounced function.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6766fe8c-abf7-482a-be40-5c7f5ee13482

📥 Commits

Reviewing files that changed from the base of the PR and between 12a14d7 and b89ed1f.

📒 Files selected for processing (9)
  • src/components/SettingsPanel.tsx
  • src/components/settings/LogsPanel.tsx
  • src/components/settings/index.ts
  • src/services/aiAnalysisHelper.ts
  • src/services/aiService.ts
  • src/services/appLogger.ts
  • src/services/backendAdapter.ts
  • src/services/githubApi.ts
  • src/types/index.ts

Comment on lines +182 to +185
{filteredLogs.length === 0 ? (
<div className="p-8 text-center text-sm text-gray-500 dark:text-text-tertiary">
{t('暂无日志', 'No logs yet')}
</div>

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

Distinguish "no logs" from "no matching logs".

When logs.length > 0 but filters/search exclude everything, the panel still shows "No logs yet", implying all logs are gone. Branch the message on logs.length.

💡 Proposed fix
         {filteredLogs.length === 0 ? (
           <div className="p-8 text-center text-sm text-gray-500 dark:text-text-tertiary">
-            {t('暂无日志', 'No logs yet')}
+            {logs.length === 0
+              ? t('暂无日志', 'No logs yet')
+              : t('没有符合条件的日志', 'No logs match the current filters')}
           </div>
🤖 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/components/settings/LogsPanel.tsx` around lines 182 - 185, The current
JSX always shows "No logs yet" when filteredLogs.length === 0; change it to
differentiate the case where there are logs but none match the current
filters/search. In the LogsPanel component, replace the single conditional
rendering that checks filteredLogs.length with a nested check: if logs.length
=== 0 render the existing "No logs yet" message, otherwise render a distinct
message like "No matching logs" (or "No logs match your filters") so users know
filters are excluding results; update the branch that references filteredLogs
and logs accordingly in the component's render/return.

Comment on lines +200 to +204
{entry.success !== undefined && (
entry.success
? <CheckCircle className="w-4 h-4 text-green-600 dark:text-green-400" />
: <AlertCircle className="w-4 h-4 text-red-600 dark:text-red-400" />
)}

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

Add accessible names to status icons.

Success/failure is conveyed only by the CheckCircle/AlertCircle icon shape and color, so screen-reader users get no status. Add role="img" + aria-label.

♿ Proposed fix
                     {entry.success !== undefined && (
                       entry.success
-                        ? <CheckCircle className="w-4 h-4 text-green-600 dark:text-green-400" />
-                        : <AlertCircle className="w-4 h-4 text-red-600 dark:text-red-400" />
+                        ? <CheckCircle role="img" aria-label={t('成功', 'Success')} className="w-4 h-4 text-green-600 dark:text-green-400" />
+                        : <AlertCircle role="img" aria-label={t('失败', 'Failed')} className="w-4 h-4 text-red-600 dark:text-red-400" />
                     )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{entry.success !== undefined && (
entry.success
? <CheckCircle className="w-4 h-4 text-green-600 dark:text-green-400" />
: <AlertCircle className="w-4 h-4 text-red-600 dark:text-red-400" />
)}
{entry.success !== undefined && (
entry.success
? <CheckCircle role="img" aria-label={t('成功', 'Success')} className="w-4 h-4 text-green-600 dark:text-green-400" />
: <AlertCircle role="img" aria-label={t('失败', 'Failed')} className="w-4 h-4 text-red-600 dark:text-red-400" />
)}
🤖 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/components/settings/LogsPanel.tsx` around lines 200 - 204, The
CheckCircle/AlertCircle icons in LogsPanel.tsx convey status only visually;
update the JSX that renders CheckCircle and AlertCircle (the block using
entry.success) to add role="img" and a meaningful aria-label (e.g.,
aria-label={`Log entry ${entry.success ? 'succeeded' : 'failed'}`} or similar)
so screen readers announce status; keep the existing classNames and conditional
logic but ensure the aria-label reflects the success boolean.

Comment thread src/services/aiService.ts
Comment on lines +331 to +340
appLogger.error('ai', 'request', 'AI request failed', {
success: false,
durationMs: Date.now() - startedAt,
details: {
apiType,
model: this.config.model,
provider: backend.isAvailable ? 'backend-proxy' : 'direct',
requestChars,
error: appLogger.serializeError(error),
},

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 | 🟠 Major | ⚡ Quick win

Don't persist raw upstream AI error bodies.

This log stores serializeError(error) verbatim, but the errors thrown earlier in requestText() already append the provider/backend response body. A validation failure can therefore end up writing prompt fragments or other provider-returned content into IndexedDB.

Suggested fix
     } catch (error) {
+      const rawError = appLogger.serializeError(error);
+      const safeError = rawError.includes(' - ') ? rawError.split(' - ')[0] : rawError;
+
       appLogger.error('ai', 'request', 'AI request failed', {
         success: false,
         durationMs: Date.now() - startedAt,
         details: {
           apiType,
           model: this.config.model,
           provider: backend.isAvailable ? 'backend-proxy' : 'direct',
           requestChars,
-          error: appLogger.serializeError(error),
+          error: safeError,
         },
       });
       throw error;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
appLogger.error('ai', 'request', 'AI request failed', {
success: false,
durationMs: Date.now() - startedAt,
details: {
apiType,
model: this.config.model,
provider: backend.isAvailable ? 'backend-proxy' : 'direct',
requestChars,
error: appLogger.serializeError(error),
},
} catch (error) {
const rawError = appLogger.serializeError(error);
const safeError = rawError.includes(' - ') ? rawError.split(' - ')[0] : rawError;
appLogger.error('ai', 'request', 'AI request failed', {
success: false,
durationMs: Date.now() - startedAt,
details: {
apiType,
model: this.config.model,
provider: backend.isAvailable ? 'backend-proxy' : 'direct',
requestChars,
error: safeError,
},
});
throw error;
}
🤖 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/services/aiService.ts` around lines 331 - 340, The log call in
aiService.ts is persisting raw upstream error bodies via
appLogger.serializeError(error); update the error logging in the AI request
failure path (the appLogger.error call inside requestText()/request handling) to
remove or redact provider response bodies before serialization: extract error
metadata you need (status/code/message) but strip any response.body,
response.data, prompt, or text fields added to the error object by the
provider/backend, then pass the sanitized error to appLogger.serializeError (or
log a constructed sanitizedError object) so no raw provider response content is
persisted.

Comment thread src/services/appLogger.ts
Comment on lines +209 to +214
async clearLogs(): Promise<void> {
if (typeof window === 'undefined') return;
if (canUseIndexedDB()) {
await withTimeout(idbDelete(LOG_STORAGE_KEY));
}
window.dispatchEvent(new CustomEvent('gsm:diagnostic-logs-cleared'));

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 | 🟠 Major | ⚡ Quick win

Serialize clearLogs() through writeQueue.

clearLogs() deletes IndexedDB immediately, but any write already queued in writeQueue can still run afterward and repopulate the store. That makes “Clear” nondeterministic and can leave supposedly deleted entries visible again.

Suggested fix
   async clearLogs(): Promise<void> {
     if (typeof window === 'undefined') return;
-    if (canUseIndexedDB()) {
-      await withTimeout(idbDelete(LOG_STORAGE_KEY));
-    }
-    window.dispatchEvent(new CustomEvent('gsm:diagnostic-logs-cleared'));
+    writeQueue = writeQueue.then(async () => {
+      if (canUseIndexedDB()) {
+        await withTimeout(idbDelete(LOG_STORAGE_KEY));
+      }
+      window.dispatchEvent(new CustomEvent('gsm:diagnostic-logs-cleared'));
+    });
+
+    await writeQueue;
   },
🤖 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/services/appLogger.ts` around lines 209 - 214, clearLogs() currently
deletes IndexedDB immediately which races with pending writes; change it to run
via the existing writeQueue so the delete executes only after prior queued
writes and prevents new writes from running concurrently. Concretely, wrap the
idbDelete(LOG_STORAGE_KEY) + window.dispatchEvent(...) logic inside the
writeQueue task runner (e.g. writeQueue.run / writeQueue.enqueue /
writeQueue.add depending on the queue API used) and keep the canUseIndexedDB and
withTimeout checks inside that queued task so the deletion is serialized with
other operations.

@AmintaCCCP

Copy link
Copy Markdown
Owner

重复了兄弟,我也在弄

@hych0317

Copy link
Copy Markdown
Contributor Author

重复了兄弟,我也在弄

那很不巧了LOL

@hych0317 hych0317 closed this May 29, 2026
@AmintaCCCP

Copy link
Copy Markdown
Owner

重复了兄弟,我也在弄

那很不巧了LOL

你那个UI回头我可以抄下,我现在只做了导出。主要是方便定位用户遇到的BUG

@hych0317

Copy link
Copy Markdown
Contributor Author

你直接改就行,我勾选了你能直接编辑的权限

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.

2 participants