feat(debug): App-Crash-Reporting via RVS — Logs in der Diagnostic-UI
Stefan ist unterwegs, ADB-Zugriff nicht moeglich. Loesung: die App
loggt ihre eigenen Crashes via RVS, Bridge sammelt sie in
/shared/logs/app.log, Diagnostic-Server liefert sie als JSON.
Damit braucht's keinen ADB mehr — Crashes sind sofort vom Browser
(oder Claude per curl) lesbar.
Komponenten:
1. App components/ErrorBoundary.tsx
- React-ErrorBoundary fuer kritische Sections
- componentDidCatch → reportAppError (RVS-Send)
- UI zeigt Error-Box statt White-Screen + Reset-Button
2. App services/logger.ts
- reportAppError(scope, message, stack) → rvs.send('app_log', ...)
- installGlobalCrashReporter() haengt sich an ErrorUtils.setGlobalHandler
UND HermesInternal.enablePromiseRejectionTracker — fangt sowohl
ungefangene Errors als auch unhandled Promise-Rejections
- Konsole bleibt parallel aktiv (damit ADB im Dev-Build weiter
was sieht)
3. App App.tsx: installGlobalCrashReporter() im useEffect zusammen
mit initLogger.
4. App ChatScreen.tsx:
- Inbox-Modal mit ErrorBoundary umschlossen (scope: InboxModal,
onReset schliesst Modal)
- MemoryDetailModal mit ErrorBoundary umschlossen
- DetailModal wird nur noch konditional gerendert (memoryDetailId
!= null) statt immer visible-toggle — vermeidet potentielles
Modal-Stacking-Problem
5. RVS server.js: ALLOWED_TYPES += "app_log"
6. Bridge aria_bridge.py:
- elif msg_type == "app_log": haengt eine Zeile an
/shared/logs/app.log (JSONL, jedes Item {ts, platform, level,
scope, message, stack})
- Plus log.info Hinweis fuer das normale Bridge-Log
7. Diagnostic server.js:
- GET /api/app-log[?limit=N] → letzte N Eintraege als JSON
- POST /api/app-log/clear → log-Datei loeschen
Workflow zum Debuggen des Inbox-Crashes:
Stefan rebuilded App → drueckt Inbox → ErrorBoundary fangt den
Crash (oder Global-Handler bei ungefangenem Error) → reportAppError
→ RVS → Bridge schreibt nach /shared/logs/app.log → Stefan
oder Claude rufen GET /api/app-log auf → sehen Stacktrace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1338,6 +1338,42 @@ const server = http.createServer((req, res) => {
|
||||
else broadcast({ type: "agent_activity", activity: "idle" });
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ ok: true }));
|
||||
} else if (req.url.startsWith("/api/app-log") && req.method === "GET") {
|
||||
// App-Crash-Reporting-Log lesen — die App schickt JS-Errors via RVS,
|
||||
// Bridge schreibt JSONL nach /shared/logs/app.log. Wir liefern die
|
||||
// letzten 200 Eintraege (oder ?limit=N).
|
||||
const url = new URL(req.url, "http://x");
|
||||
const limit = Math.max(1, Math.min(2000, parseInt(url.searchParams.get("limit") || "200", 10) || 200));
|
||||
try {
|
||||
const file = "/shared/logs/app.log";
|
||||
if (!fs.existsSync(file)) {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ count: 0, entries: [] }));
|
||||
return;
|
||||
}
|
||||
const raw = fs.readFileSync(file, "utf-8");
|
||||
const lines = raw.split("\n").filter(l => l.trim());
|
||||
const tail = lines.slice(-limit);
|
||||
const entries = tail.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ count: entries.length, entries }));
|
||||
} catch (err) {
|
||||
res.writeHead(500, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: err.message }));
|
||||
}
|
||||
return;
|
||||
} else if (req.url === "/api/app-log/clear" && req.method === "POST") {
|
||||
// App-Log leeren — nach erfolgreichem Debug.
|
||||
try {
|
||||
const file = "/shared/logs/app.log";
|
||||
if (fs.existsSync(file)) fs.unlinkSync(file);
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ ok: true }));
|
||||
} catch (err) {
|
||||
res.writeHead(500, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ ok: false, error: err.message }));
|
||||
}
|
||||
return;
|
||||
} else if (req.url === "/api/files-list" && req.method === "GET") {
|
||||
// Liste alle Dateien in /shared/uploads/ — die kommen entweder vom User
|
||||
// (Upload aus App/Diagnostic) oder von ARIA (aria_<name>.<ext> Pattern).
|
||||
|
||||
Reference in New Issue
Block a user