import type { ILogger, LogContext, LogLevel } from '../../../application/ports/ILogger'; import { loadLoggingConfig, type LoggingEnvironmentConfig } from '../../config/LoggingConfig'; const LOG_LEVEL_PRIORITY: Record = { debug: 10, info: 20, warn: 30, error: 40, fatal: 50, }; /** * PinoLogAdapter - Electron-compatible logger implementation. * * Note: We use a custom console-based implementation instead of pino * because pino's internal use of diagnostics_channel.tracingChannel * is not compatible with Electron's Node.js version. * * This provides structured JSON logging to stdout with the same interface. */ export class PinoLogAdapter implements ILogger { private readonly config: LoggingEnvironmentConfig; private readonly baseContext: LogContext; private readonly levelPriority: number; constructor(config?: LoggingEnvironmentConfig, baseContext?: LogContext) { this.config = config || loadLoggingConfig(); this.baseContext = { app: 'gridpilot-companion', version: process.env.npm_package_version || '0.0.0', processType: process.type || 'main', ...baseContext, }; this.levelPriority = LOG_LEVEL_PRIORITY[this.config.level]; } private shouldLog(level: LogLevel): boolean { return LOG_LEVEL_PRIORITY[level] >= this.levelPriority; } private formatLog(level: LogLevel, message: string, context?: LogContext, error?: Error): string { const entry: Record = { level, time: new Date().toISOString(), ...this.baseContext, ...context, msg: message, }; if (error) { entry.err = { message: error.message, name: error.name, stack: error.stack, }; } return JSON.stringify(entry); } private log(level: LogLevel, message: string, context?: LogContext, error?: Error): void { if (!this.shouldLog(level)) { return; } const output = this.formatLog(level, message, context, error); if (this.config.prettyPrint) { const timestamp = new Date().toLocaleString(); const levelColors: Record = { debug: '\x1b[36m', // cyan info: '\x1b[32m', // green warn: '\x1b[33m', // yellow error: '\x1b[31m', // red fatal: '\x1b[35m', // magenta }; const reset = '\x1b[0m'; const color = levelColors[level]; const contextStr = context ? ` ${JSON.stringify(context)}` : ''; const errorStr = error ? `\n ${error.stack || error.message}` : ''; console.log(`${color}[${timestamp}] ${level.toUpperCase()}${reset}: ${message}${contextStr}${errorStr}`); } else { console.log(output); } } debug(message: string, context?: LogContext): void { this.log('debug', message, context); } info(message: string, context?: LogContext): void { this.log('info', message, context); } warn(message: string, context?: LogContext): void { this.log('warn', message, context); } error(message: string, error?: Error, context?: LogContext): void { this.log('error', message, context, error); } fatal(message: string, error?: Error, context?: LogContext): void { this.log('fatal', message, context, error); } child(context: LogContext): ILogger { return new PinoLogAdapter(this.config, { ...this.baseContext, ...context }); } async flush(): Promise { // Console output is synchronous, nothing to flush } }