import pino from 'pino'; // Determine environment const isDevelopment = process.env.NODE_ENV === 'development'; const logLevel = (process.env.LOG_LEVEL || (isDevelopment ? 'debug' : 'info')) as pino.Level; // Custom serializers for better log output const serializers = { error: pino.stdSerializers.err, req: (req: any) => ({ id: req.id, method: req.method, url: req.url, query: req.query, params: req.params, remoteAddress: req.headers?.['x-forwarded-for'] || req.socket?.remoteAddress, userAgent: req.headers?.['user-agent'], }), res: (res: any) => ({ statusCode: res.statusCode, }), }; // Sensitive fields to redact from logs const redactPaths = [ 'password', 'token', 'secret', 'apiKey', 'authorization', '*.password', '*.token', '*.secret', '*.apiKey', '*.authorization', 'req.headers.authorization', 'req.headers.cookie', 'SUPERVISOR_PASSWORD', 'SUPERVISOR_USERNAME', ]; // Create base logger export const logger = pino({ level: logLevel, // Pretty printing in development, JSON in production transport: isDevelopment ? { target: 'pino-pretty', options: { colorize: true, translateTime: 'HH:MM:ss', ignore: 'pid,hostname', singleLine: false, }, } : undefined, // Redact sensitive information redact: { paths: redactPaths, censor: '[REDACTED]', }, // Custom serializers serializers, // Add timestamp in production timestamp: isDevelopment ? false : pino.stdTimeFunctions.isoTime, // Base context base: isDevelopment ? undefined : { pid: process.pid, hostname: process.env.HOSTNAME || 'unknown', }, }); /** * Create a child logger with additional context * @param context - Additional context to add to all log messages * @returns Child logger instance */ export function createLogger(context: Record) { return logger.child(context); } /** * Generate a unique request ID * @returns Unique request identifier */ export function generateRequestId(): string { return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Create a logger for API operations * @param operation - The operation name (e.g., 'startProcess', 'stopProcess') * @param metadata - Additional metadata * @returns Child logger with operation context */ export function createOperationLogger(operation: string, metadata: Record = {}) { return createLogger({ operation, ...metadata, }); } /** * Log timing information for an operation * @param logger - Logger instance * @param operation - Operation name * @param startTime - Start time from performance.now() or Date.now() */ export function logTiming(logger: pino.Logger, operation: string, startTime: number) { const duration = Date.now() - startTime; logger.info({ operation, duration }, `${operation} completed in ${duration}ms`); } /** * Format error for logging * @param error - Error object or message * @returns Formatted error object */ export function formatError(error: unknown): { message: string; stack?: string; code?: string } { if (error instanceof Error) { return { message: error.message, stack: error.stack, code: (error as any).code, }; } if (typeof error === 'string') { return { message: error }; } return { message: 'Unknown error', stack: JSON.stringify(error) }; } // Export default logger export default logger;