/** * Server-side logging utility for sexy.pivoine.art * Provides structured logging with context and request tracing */ export type LogLevel = "debug" | "info" | "warn" | "error"; interface LogContext { timestamp: string; level: LogLevel; message: string; context?: Record; requestId?: string; userId?: string; path?: string; method?: string; duration?: number; error?: Error; } class Logger { private isDev = process.env.NODE_ENV === "development"; private serviceName = "sexy.pivoine.art"; private formatLog(ctx: LogContext): string { const { timestamp, level, message, context, requestId, userId, path, method, duration, error } = ctx; const parts = [ `[${timestamp}]`, `[${level.toUpperCase()}]`, requestId ? `[${requestId}]` : null, method && path ? `${method} ${path}` : null, message, userId ? `user=${userId}` : null, duration !== undefined ? `${duration}ms` : null, ].filter(Boolean); let logString = parts.join(" "); if (context && Object.keys(context).length > 0) { logString += " " + JSON.stringify(context); } if (error) { logString += `\n Error: ${error.message}\n Stack: ${error.stack}`; } return logString; } private log(level: LogLevel, message: string, meta: Partial = {}) { const timestamp = new Date().toISOString(); const logContext: LogContext = { timestamp, level, message, ...meta, }; const formattedLog = this.formatLog(logContext); switch (level) { case "debug": if (this.isDev) console.debug(formattedLog); break; case "info": console.info(formattedLog); break; case "warn": console.warn(formattedLog); break; case "error": console.error(formattedLog); break; } } debug(message: string, meta?: Partial) { this.log("debug", message, meta); } info(message: string, meta?: Partial) { this.log("info", message, meta); } warn(message: string, meta?: Partial) { this.log("warn", message, meta); } error(message: string, meta?: Partial) { this.log("error", message, meta); } // Request logging helper request(method: string, path: string, meta: Partial = {}) { this.info("→ Request received", { method, path, ...meta }); } response( method: string, path: string, status: number, duration: number, meta: Partial = {}, ) { const level = status >= 500 ? "error" : status >= 400 ? "warn" : "info"; this.log(level, `← Response ${status}`, { method, path, duration, ...meta }); } // Authentication logging auth(action: string, success: boolean, meta: Partial = {}) { this.info(`šŸ” Auth: ${action} ${success ? "success" : "failed"}`, meta); } // Startup logging startup() { const env = { NODE_ENV: process.env.NODE_ENV, PUBLIC_API_URL: process.env.PUBLIC_API_URL, PUBLIC_URL: process.env.PUBLIC_URL, PUBLIC_UMAMI_ID: process.env.PUBLIC_UMAMI_ID ? "***set***" : "not set", PUBLIC_UMAMI_SCRIPT: process.env.PUBLIC_UMAMI_SCRIPT || "not set", PORT: process.env.PORT || "3000", HOST: process.env.HOST || "0.0.0.0", }; console.log("\n" + "=".repeat(60)); console.log("šŸ‘ sexy.pivoine.art - Server Starting šŸ’œ"); console.log("=".repeat(60)); console.log("\nšŸ“‹ Environment Configuration:"); Object.entries(env).forEach(([key, value]) => { console.log(` ${key}: ${value}`); }); console.log("\n" + "=".repeat(60) + "\n"); } } // Singleton instance export const logger = new Logger(); // Generate request ID export function generateRequestId(): string { return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }