Files
plainleaf/lib/logger.ts
T
Zef HemelandGitHub dc2987a3f9 Sync engine rearchitecture (#1516)
* Don't require auth for .ping
* Finally introducing loggers
* Remove file list caching from disk_space_primitives
I don't think this is required anymore with the sync model
* Simplification of space primitives and more tests
* More reliable online status
* Add .zed settings
* Remove selfUpdate argument from `writeFile` no longer used
- Add `sync` config schema and document sync options
- Move sync config handling to client boot logic
- Move BootConfig type to ui_types.ts
- Remove sync env vars from server
- Update documentation for sync and architecture
* Improve sync logic and proxy routing for full sync state
* Add BoxProxy for late-bound client and improve Lua boot
- Add isMobile syscall for editor
* Add concurrency to sync process and log indexing duration
* Better service worker lifecycle management
2025-09-12 14:43:57 +02:00

106 lines
2.8 KiB
TypeScript

// Logger that monkey patches console methods with prefixes and can capture logs for server transmission
export interface LogEntry {
level: "log" | "info" | "warn" | "error" | "debug";
timestamp: number;
message: string;
args: any[];
}
export class Logger {
private originalConsole: {
log: typeof console.log;
info: typeof console.info;
warn: typeof console.warn;
error: typeof console.error;
debug: typeof console.debug;
};
private logCapture: LogEntry[] = [];
constructor(
private prefix: string = "",
private maxCaptureSize: number = 1000,
) {
this.prefix = prefix;
// Store original console methods
this.originalConsole = {
log: console.log.bind(console),
info: console.info.bind(console),
warn: console.warn.bind(console),
error: console.error.bind(console),
debug: console.debug.bind(console),
};
this.patchConsole();
}
private patchConsole(): void {
const createPatchedMethod = (level: keyof typeof this.originalConsole) => {
return (...args: any[]) => {
const prefixedArgs = this.prefix ? [this.prefix, ...args] : args;
// Call original console method
this.originalConsole[level](...prefixedArgs);
// Capture log if capturing is enabled
this.captureLog(level, args);
};
};
console.log = createPatchedMethod("log");
console.info = createPatchedMethod("info");
console.warn = createPatchedMethod("warn");
console.error = createPatchedMethod("error");
console.debug = createPatchedMethod("debug");
}
private captureLog(level: LogEntry["level"], args: any[]): void {
const entry: LogEntry = {
level,
timestamp: Date.now(),
message: args.map((arg) => {
if (typeof arg === "string") {
return arg;
}
try {
return JSON.stringify(arg);
} catch {
// Handle circular references or other JSON.stringify failures
return String(arg);
}
}).join(" "),
args: args.map((arg) => {
// Serialize complex objects for safe transmission
try {
return typeof arg === "object"
? JSON.parse(JSON.stringify(arg))
: arg;
} catch {
// This may fail due to recursive structures, in that case just fall back to a best-effort string
return String(arg);
}
}),
};
this.logCapture.push(entry);
// Maintain max capture size by removing oldest entries
if (this.logCapture.length > this.maxCaptureSize) {
this.logCapture.shift();
}
}
getCapturedLogs(): LogEntry[] {
return [...this.logCapture];
}
}
// Global logger instance
let globalLogger: Logger | undefined = undefined;
export function initLogger(prefix: string = ""): Logger {
globalLogger = new Logger(prefix);
return globalLogger;
}