/** * 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)}`; }