Add on-device debug logging: file capture + share (for real-session testing)
- fileLogger: append redacted API requests/responses + app events to an on-device log file (persists across restarts); the client's logSink now writes to it - Diagnostics screen (Account > Diagnostics): toggle recording, Share log file (expo-sharing), clear. StartSession logs the session attempt/result explicitly. - Lets a real parking session be captured while away from a computer, then shared. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
80812d25cf
commit
7ec7ae9200
8 changed files with 216 additions and 19 deletions
83
app/src/features/diagnostics/fileLogger.ts
Normal file
83
app/src/features/diagnostics/fileLogger.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import * as FileSystem from 'expo-file-system';
|
||||
import * as Sharing from 'expo-sharing';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
/**
|
||||
* On-device debug log. When enabled, API requests/responses (already redacted by
|
||||
* the client) and app events are appended to a file the user can share afterward
|
||||
* — useful for capturing a real parking-session attempt while away from a computer.
|
||||
*/
|
||||
const LOG_URI = FileSystem.documentDirectory + 'bbp-debug.log';
|
||||
const ENABLED_KEY = 'ps_file_logging';
|
||||
const MAX_LINES = 4000;
|
||||
|
||||
let enabled = false;
|
||||
let buffer: string[] = [];
|
||||
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
export async function initFileLogging(): Promise<boolean> {
|
||||
enabled = (await AsyncStorage.getItem(ENABLED_KEY)) === 'true';
|
||||
if (enabled) {
|
||||
// Carry over the previous session's log so a crash mid-test isn't lost.
|
||||
try {
|
||||
const info = await FileSystem.getInfoAsync(LOG_URI);
|
||||
if (info.exists) {
|
||||
const prev = await FileSystem.readAsStringAsync(LOG_URI);
|
||||
buffer = prev.split('\n').slice(-MAX_LINES);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
export function isFileLoggingEnabled(): boolean {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
export async function setFileLogging(on: boolean): Promise<void> {
|
||||
enabled = on;
|
||||
await AsyncStorage.setItem(ENABLED_KEY, String(on));
|
||||
if (on) logLine('--- logging enabled ---');
|
||||
else await flush();
|
||||
}
|
||||
|
||||
export function logLine(line: string): void {
|
||||
if (!enabled) return;
|
||||
buffer.push(`${new Date().toISOString()} ${line}`);
|
||||
if (buffer.length > MAX_LINES) buffer = buffer.slice(-MAX_LINES);
|
||||
if (flushTimer) clearTimeout(flushTimer);
|
||||
flushTimer = setTimeout(() => void flush(), 1200);
|
||||
}
|
||||
|
||||
export async function flush(): Promise<void> {
|
||||
try {
|
||||
await FileSystem.writeAsStringAsync(LOG_URI, buffer.join('\n'));
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
export async function shareLog(): Promise<void> {
|
||||
await flush();
|
||||
if (await Sharing.isAvailableAsync()) {
|
||||
await Sharing.shareAsync(LOG_URI, {
|
||||
mimeType: 'text/plain',
|
||||
dialogTitle: 'BigBrainParking debug log',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearLog(): Promise<void> {
|
||||
buffer = [];
|
||||
try {
|
||||
await FileSystem.deleteAsync(LOG_URI, { idempotent: true });
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
export function logStats(): { lines: number } {
|
||||
return { lines: buffer.length };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue