import winston from 'winston' import LokiTransport from 'winston-loki' import { randomUUID } from 'crypto' const isDevelopment = process.env.NODE_ENV === 'development' // Custom format for structured logging const structuredFormat = winston.format.combine( winston.format.timestamp({ format: 'ISO' }), winston.format.errors({ stack: true }), winston.format.json({ replacer: (_key, value) => typeof value === 'bigint' ? value.toString() : value, }) ) // Console format for development const consoleFormat = winston.format.combine( winston.format.timestamp({ format: 'HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.colorize(), winston.format.simple(), winston.format.printf(({ timestamp, level, message, service, requestId, ...meta }) => { const requestInfo = requestId ? `[${requestId}]` : '' const serviceInfo = service ? `[${service}]` : '[mozdIT]' const metaStr = Object.keys(meta).length ? `\n${JSON.stringify(meta, null, 2)}` : '' return `${timestamp} ${serviceInfo} ${level} ${requestInfo} ${message}${metaStr}` }) ) // Transports configuration const transports: winston.transport[] = [ // Loki transport for centralized logging ...(process.env.LOKI_HOST ? [ new LokiTransport({ host: process.env.LOKI_HOST, labels: { app: 'mozdit-web', environment: process.env.NODE_ENV || 'development', service: 'frontend' }, basicAuth: process.env.LOKI_USERNAME && process.env.LOKI_PASSWORD ? `${process.env.LOKI_USERNAME}:${process.env.LOKI_PASSWORD}` : undefined, json: true, format: winston.format.json(), onConnectionError: (err: Error) => console.error('Loki connection error:', err) }) ] : []), // Console for development logging new winston.transports.Console({ level: isDevelopment ? 'debug' : 'info', format: isDevelopment ? consoleFormat : structuredFormat, handleExceptions: true, handleRejections: true }) ] // Root logger configuration export const logger = winston.createLogger({ level: isDevelopment ? 'debug' : 'info', format: structuredFormat, defaultMeta: { service: 'mozdit-web', version: process.env.DEPLOY_VERSION || process.env.npm_package_version || '1.0.0', environment: process.env.NODE_ENV || 'development' }, transports, exceptionHandlers: transports, rejectionHandlers: transports }) // Specialized loggers for different components export const createComponentLogger = (component: string) => { return logger.child({ component }) } export const requestLogger = logger.child({ component: 'request' }) export const apiLogger = logger.child({ component: 'api' }) export const dbLogger = logger.child({ component: 'database' }) export const authLogger = logger.child({ component: 'auth' }) export const errorLogger = logger.child({ component: 'error' }) // Request ID generator for correlation export const generateRequestId = (): string => `${Date.now()}-${randomUUID().slice(0, 8)}` // Helper function for timing operations export const withTiming = async ( operation: string, fn: () => Promise, metadata: any = {} ): Promise => { const startTime = Date.now() logger.debug(`Started ${operation}`, metadata) try { const result = await fn() const duration = Date.now() - startTime logger.debug(`Completed ${operation} in ${duration}ms`, { ...metadata, duration }) return result } catch (error) { const duration = Date.now() - startTime logger.error(`Failed ${operation} in ${duration}ms`, { ...metadata, duration, error }) throw error } } // Graceful shutdown const gracefulShutdown = () => { logger.info('Initiating graceful shutdown...') logger.end() } process.on('SIGTERM', gracefulShutdown) process.on('SIGINT', gracefulShutdown) export default logger