tenseleyflow/claudex / 6c20a65

Browse files

debug: coalesce duplicate log entries over 500ms window

Authored by espadonne
SHA
6c20a654e9c18c06a68dc2c26321906aee2b9e42
Parents
68a4c05
Tree
459c64b

1 changed file

StatusFile+-
M src/lib/debug.ts 47 3
src/lib/debug.tsmodified
@@ -17,6 +17,50 @@ import { logFrontend, type LogLevel } from "@/lib/ipc/client";
1717
 
1818
 let installed = false;
1919
 
20
+/** Coalesce repeated identical log entries into a single IPC call.
21
+ *  The Tauri bridge fires bursts of 10+ "Couldn't find callback id"
22
+ *  warnings in a couple of ms whenever a listener is unlistened
23
+ *  while events are still in flight — sending each one through
24
+ *  `log_frontend` turns into 10 main-thread IPC round-trips. We
25
+ *  dedupe over a 500 ms window and emit a summary with the count
26
+ *  when it finally flushes. */
27
+const COALESCE_WINDOW_MS = 500;
28
+
29
+interface PendingLog {
30
+  level: LogLevel;
31
+  source: string;
32
+  message: string;
33
+  stack?: string;
34
+  count: number;
35
+  timer: ReturnType<typeof setTimeout>;
36
+}
37
+
38
+const pendingLogs = new Map<string, PendingLog>();
39
+
40
+function queueLog(
41
+  level: LogLevel,
42
+  source: string,
43
+  message: string,
44
+  stack?: string,
45
+) {
46
+  const key = `${level}::${source}::${message}`;
47
+  const existing = pendingLogs.get(key);
48
+  if (existing) {
49
+    existing.count += 1;
50
+    return;
51
+  }
52
+  const timer = setTimeout(() => {
53
+    const entry = pendingLogs.get(key);
54
+    pendingLogs.delete(key);
55
+    if (!entry) return;
56
+    const msg = entry.count > 1
57
+      ? `${entry.message} (×${entry.count})`
58
+      : entry.message;
59
+    void logFrontend(entry.level, entry.source, msg, entry.stack);
60
+  }, COALESCE_WINDOW_MS);
61
+  pendingLogs.set(key, { level, source, message, stack, count: 1, timer });
62
+}
63
+
2064
 export function installDebugBridge(): void {
2165
   if (installed) return;
2266
   installed = true;
@@ -26,7 +70,7 @@ export function installDebugBridge(): void {
2670
     const err = ev.error as unknown;
2771
     const message = formatMessage(ev.message, err);
2872
     const stack = extractStack(err);
29
-    void logFrontend("error", "window.onerror", message, stack);
73
+    queueLog("error", "window.onerror", message, stack);
3074
   });
3175
 
3276
   // Unhandled promise rejections.
@@ -34,7 +78,7 @@ export function installDebugBridge(): void {
3478
     const reason = ev.reason as unknown;
3579
     const message = formatMessage("unhandledrejection", reason);
3680
     const stack = extractStack(reason);
37
-    void logFrontend("error", "unhandledrejection", message, stack);
81
+    queueLog("error", "unhandledrejection", message, stack);
3882
   });
3983
 
4084
   // Shadow console.error / console.warn so anything that goes to
@@ -52,7 +96,7 @@ function wrapConsole(level: "error" | "warn") {
5296
     try {
5397
       const message = args.map(stringify).join(" ");
5498
       const stack = args.map(extractStack).find((s): s is string => !!s);
55
-      void logFrontend(mapped, `console.${level}`, message, stack);
99
+      queueLog(mapped, `console.${level}`, message, stack);
56100
     } catch {
57101
       // Never let logging fail noisily.
58102
     }