package updates and build time error fix in the main app

This commit is contained in:
user
2026-02-28 15:41:44 +02:00
parent b731b68918
commit 9328f79c92
9 changed files with 1133 additions and 317 deletions

View File

@@ -1,222 +0,0 @@
type LogLevel = "error" | "warn" | "info" | "http" | "debug";
export interface LogEntry {
level: LogLevel;
timestamp: string;
message: any;
metadata?: any;
}
interface Error {
code: string;
message: string;
description?: string;
detail?: string;
error?: any;
actionable?: boolean;
}
class BrowserLogger {
private queue: LogEntry[] = [];
private timer: ReturnType<typeof setInterval> | null = null;
private readonly BATCH_INTERVAL = 5000; // 5 seconds
private readonly BATCH_SIZE_LIMIT = 50;
private readonly isDev: boolean;
constructor(isDev: boolean) {
this.isDev = isDev;
if (!this.isDev) {
this.startBatchTimer();
this.setupBeforeUnloadHandler();
}
}
private startBatchTimer() {
this.timer = setInterval(() => this.flush(), this.BATCH_INTERVAL);
}
private setupBeforeUnloadHandler() {
// Flush logs before page unload to avoid losing them
if (typeof window !== "undefined") {
window.addEventListener("beforeunload", () => {
this.flushSync();
});
}
}
private async flush() {
if (this.queue.length === 0) return;
const batch = [...this.queue];
this.queue = [];
try {
// Forward batch to Hono route
await fetch("/api/logs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ logs: batch }),
});
} catch (err) {
console.error("Axiom batch upload failed", err);
// Re-add failed logs back to queue (up to a limit to avoid memory issues)
if (this.queue.length < this.BATCH_SIZE_LIMIT * 2) {
this.queue.push(...batch);
}
}
}
private flushSync() {
// Synchronous flush for beforeunload using sendBeacon
if (this.queue.length === 0) return;
const batch = [...this.queue];
this.queue = [];
try {
const blob = new Blob([JSON.stringify({ logs: batch })], {
type: "application/json",
});
navigator.sendBeacon("/api/logs", blob);
} catch (err) {
console.error("Failed to send logs via sendBeacon", err);
}
}
private serializeError(error: unknown): any {
if (error instanceof Error) {
return {
name: error.name,
message: error.message,
stack: error.stack,
...(error.cause && { cause: this.serializeError(error.cause) }),
};
}
return error;
}
private createLogEntry(
level: LogLevel,
message: any,
metadata?: any,
): LogEntry {
// Handle Error serialization for message
const cleanMessage =
message instanceof Error ? this.serializeError(message) : message;
// Handle Error serialization for metadata
const cleanMetadata =
metadata instanceof Error
? this.serializeError(metadata)
: metadata;
return {
level,
timestamp: new Date().toISOString(),
message: cleanMessage,
metadata: cleanMetadata || {},
};
}
private async sendLog(entry: LogEntry) {
// Always log errors to console, even in production (for user debugging)
// In dev, log everything to console
const shouldConsoleLog = this.isDev || entry.level === "error";
if (shouldConsoleLog) {
const consoleMethod =
entry.level === "http" ? "debug" : entry.level;
console[consoleMethod](
`[client-${entry.level}] ${entry.timestamp}:`,
entry.message,
entry.metadata && Object.keys(entry.metadata).length > 0
? entry.metadata
: "",
);
}
// In production, add to queue for batching
if (!this.isDev) {
this.queue.push(entry);
// Safety flush if queue gets too large
if (this.queue.length >= this.BATCH_SIZE_LIMIT) {
await this.flush();
}
}
}
error(message: any, metadata?: any) {
this.sendLog(this.createLogEntry("error", message, metadata));
}
warn(message: any, metadata?: any) {
this.sendLog(this.createLogEntry("warn", message, metadata));
}
info(message: any, metadata?: any) {
this.sendLog(this.createLogEntry("info", message, metadata));
}
http(message: any, metadata?: any) {
this.sendLog(this.createLogEntry("http", message, metadata));
}
debug(message: any, metadata?: any) {
this.sendLog(this.createLogEntry("debug", message, metadata));
}
// Manual flush method for advanced use cases
async forceFlush() {
await this.flush();
}
// Cleanup method
destroy() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
this.flushSync();
}
}
/**
* Factory function to create a BrowserLogger instance
*
* @param isDev - Whether the app is running in development mode
* @returns A new BrowserLogger instance
*
* @example
* // SvelteKit
* import { dev } from '$app/environment';
* const logger = getLoggerInstance(dev);
*
* @example
* // Next.js
* const logger = getLoggerInstance(process.env.NODE_ENV === 'development');
*
* @example
* // Vite
* const logger = getLoggerInstance(import.meta.env.DEV);
*/
function getLoggerInstance(isDev: boolean): BrowserLogger {
return new BrowserLogger(isDev);
}
function getError(logger: BrowserLogger, payload: Error, error?: any) {
logger.error(payload);
if (error) {
logger.error(error);
}
return {
code: payload.code,
message: payload.message,
description: payload.description,
detail: payload.detail,
error: error,
actionable: payload.actionable,
} as Error;
}
export { getError, getLoggerInstance };