120 lines
3.2 KiB
TypeScript
120 lines
3.2 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;
|
|
}
|
|
|
|
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;
|
|
};
|
|
public logBuffer: 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(" "),
|
|
};
|
|
|
|
this.logBuffer.push(entry);
|
|
|
|
// Maintain max capture size by removing oldest entries
|
|
if (this.logBuffer.length > this.maxCaptureSize) {
|
|
this.logBuffer.shift();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Posts all buffered logs to a server endpoint
|
|
*/
|
|
async postToServer(logEndpoint: string, source: string) {
|
|
const logs = this.logBuffer;
|
|
if (logs.length > 0) {
|
|
// Flush the buffer
|
|
const logCopy = [...this.logBuffer];
|
|
this.logBuffer = [];
|
|
try {
|
|
const resp = await fetch(logEndpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(logCopy.map((entry) => ({ ...entry, source }))),
|
|
});
|
|
if (!resp.ok) {
|
|
throw new Error("Failed to post logs to server");
|
|
}
|
|
} catch (e: any) {
|
|
console.warn("Could not post logs to server", e.message);
|
|
// Put back the logs into the buffer
|
|
this.logBuffer.unshift(...logCopy);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global logger instance
|
|
let globalLogger: Logger | undefined;
|
|
|
|
export function initLogger(prefix: string = ""): Logger {
|
|
globalLogger = new Logger(prefix);
|
|
return globalLogger;
|
|
}
|