119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import type { LoggerPort } from '@gridpilot/automation/application/ports/LoggerPort';
|
|
import type { LogContext } from '@gridpilot/automation/application/ports/LoggerContext';
|
|
import type { LogLevel } from '@gridpilot/automation/application/ports/LoggerLogLevel';
|
|
import { loadLoggingConfig, type LoggingEnvironmentConfig } from '../../config/LoggingConfig';
|
|
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
|
|
|
|
const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
|
|
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 LoggerPort, 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<string, unknown> = {
|
|
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<LogLevel, string> = {
|
|
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): LoggerPort {
|
|
return new PinoLogAdapter(this.config, { ...this.baseContext, ...context });
|
|
}
|
|
|
|
async flush(): Promise<void> {
|
|
// Console output is synchronous, nothing to flush
|
|
}
|
|
} |