191 lines
5.3 KiB
TypeScript
191 lines
5.3 KiB
TypeScript
// Define types locally to avoid circular dependency
|
|
export enum LogLevel {
|
|
TRACE = 0,
|
|
DEBUG = 1,
|
|
INFO = 2,
|
|
WARN = 3,
|
|
ERROR = 4,
|
|
FATAL = 5,
|
|
}
|
|
|
|
export interface ILoggerComponent {
|
|
init(): Promise<void>;
|
|
setLevel(level: LogLevel): void;
|
|
trace(message: string, ...args: unknown[]): void;
|
|
debug(message: string, ...args: unknown[]): void;
|
|
info(message: string, ...args: unknown[]): void;
|
|
warn(message: string, ...args: unknown[]): void;
|
|
error(message: string, ...args: unknown[]): void;
|
|
fatal(message: string, ...args: unknown[]): void;
|
|
child(context: string): ILoggerComponent;
|
|
}
|
|
|
|
// ANSI color codes for terminal output
|
|
const COLORS = {
|
|
RESET: '\x1b[0m',
|
|
BRIGHT: '\x1b[1m',
|
|
DIM: '\x1b[2m',
|
|
RED: '\x1b[31m',
|
|
GREEN: '\x1b[32m',
|
|
YELLOW: '\x1b[33m',
|
|
BLUE: '\x1b[34m',
|
|
MAGENTA: '\x1b[35m',
|
|
CYAN: '\x1b[36m',
|
|
WHITE: '\x1b[37m',
|
|
GRAY: '\x1b[90m',
|
|
};
|
|
|
|
const LEVEL_COLORS = {
|
|
[LogLevel.TRACE]: COLORS.GRAY,
|
|
[LogLevel.DEBUG]: COLORS.CYAN,
|
|
[LogLevel.INFO]: COLORS.GREEN,
|
|
[LogLevel.WARN]: COLORS.YELLOW,
|
|
[LogLevel.ERROR]: COLORS.RED,
|
|
[LogLevel.FATAL]: COLORS.BRIGHT + COLORS.RED,
|
|
};
|
|
|
|
const LEVEL_NAMES = {
|
|
[LogLevel.TRACE]: 'TRACE',
|
|
[LogLevel.DEBUG]: 'DEBUG',
|
|
[LogLevel.INFO]: 'INFO ',
|
|
[LogLevel.WARN]: 'WARN ',
|
|
[LogLevel.ERROR]: 'ERROR',
|
|
[LogLevel.FATAL]: 'FATAL',
|
|
};
|
|
|
|
export class LoggerComponent implements ILoggerComponent {
|
|
private level: LogLevel;
|
|
private context: string;
|
|
private static globalLevel: LogLevel = LogLevel.INFO;
|
|
|
|
constructor(context = 'main') {
|
|
this.context = context;
|
|
this.level = LoggerComponent.globalLevel;
|
|
|
|
// Check environment variable for log level
|
|
const envLevel = process.env.LOG_LEVEL?.toUpperCase();
|
|
if (envLevel && LogLevel[envLevel as keyof typeof LogLevel] !== undefined) {
|
|
this.level = LogLevel[envLevel as keyof typeof LogLevel];
|
|
LoggerComponent.globalLevel = this.level;
|
|
} else if (process.env.DEBUG === 'true') {
|
|
this.level = LogLevel.DEBUG;
|
|
LoggerComponent.globalLevel = LogLevel.DEBUG;
|
|
}
|
|
}
|
|
|
|
async init(): Promise<void> {
|
|
this.info(`Logger initialized for context: ${this.context}`);
|
|
}
|
|
|
|
setLevel(level: LogLevel): void {
|
|
this.level = level;
|
|
LoggerComponent.globalLevel = level;
|
|
}
|
|
|
|
private formatMessage(
|
|
level: LogLevel,
|
|
message: string,
|
|
args: unknown[],
|
|
): string {
|
|
const timestamp = new Date().toISOString();
|
|
const levelColor = LEVEL_COLORS[level];
|
|
const levelName = LEVEL_NAMES[level];
|
|
|
|
// Format the main message
|
|
let formattedMessage = message;
|
|
if (args.length > 0) {
|
|
// Handle object arguments
|
|
const argStrings = args.map((arg) => {
|
|
// Special handling for Error objects
|
|
if (arg instanceof Error) {
|
|
return `${arg.name}: ${arg.message}${arg.stack ? '\n' + arg.stack : ''}`;
|
|
}
|
|
if (typeof arg === 'object' && arg !== null) {
|
|
try {
|
|
// Check if it's an empty object
|
|
const str = JSON.stringify(arg, null, 2);
|
|
if (str === '{}' && Object.getOwnPropertyNames(arg).length > 0) {
|
|
// Object has non-enumerable properties, try to get more info
|
|
return `${arg.constructor.name}: ${String(arg)}`;
|
|
}
|
|
return str;
|
|
} catch {
|
|
return String(arg);
|
|
}
|
|
}
|
|
return String(arg);
|
|
});
|
|
formattedMessage = `${message} ${argStrings.join(' ')}`;
|
|
}
|
|
|
|
// Build the log line with colors
|
|
return `${COLORS.DIM}[${timestamp}]${COLORS.RESET} ${levelColor}[${levelName}]${COLORS.RESET} ${COLORS.BRIGHT}[${this.context}]${COLORS.RESET} ${formattedMessage}`;
|
|
}
|
|
|
|
private log(level: LogLevel, message: string, args: unknown[]): void {
|
|
if (level < this.level) return;
|
|
|
|
const formattedMessage = this.formatMessage(level, message, args);
|
|
|
|
switch (level) {
|
|
case LogLevel.TRACE:
|
|
case LogLevel.DEBUG:
|
|
console.log(formattedMessage);
|
|
break;
|
|
case LogLevel.INFO:
|
|
console.log(formattedMessage);
|
|
break;
|
|
case LogLevel.WARN:
|
|
console.warn(formattedMessage);
|
|
break;
|
|
case LogLevel.ERROR:
|
|
case LogLevel.FATAL:
|
|
console.error(formattedMessage);
|
|
break;
|
|
}
|
|
}
|
|
|
|
trace(message: string, ...args: unknown[]): void {
|
|
this.log(LogLevel.TRACE, message, args);
|
|
}
|
|
|
|
debug(message: string, ...args: unknown[]): void {
|
|
this.log(LogLevel.DEBUG, message, args);
|
|
}
|
|
|
|
info(message: string, ...args: unknown[]): void {
|
|
this.log(LogLevel.INFO, message, args);
|
|
}
|
|
|
|
warn(message: string, ...args: unknown[]): void {
|
|
this.log(LogLevel.WARN, message, args);
|
|
}
|
|
|
|
error(message: string, ...args: unknown[]): void {
|
|
this.log(LogLevel.ERROR, message, args);
|
|
}
|
|
|
|
fatal(message: string, ...args: unknown[]): void {
|
|
this.log(LogLevel.FATAL, message, args);
|
|
}
|
|
|
|
child(context: string): ILoggerComponent {
|
|
const childLogger = new LoggerComponent(`${this.context}:${context}`);
|
|
childLogger.level = this.level;
|
|
return childLogger;
|
|
}
|
|
}
|
|
|
|
// Export a singleton instance for convenience
|
|
let defaultLogger: LoggerComponent | null = null;
|
|
|
|
export function getLogger(context = 'main'): ILoggerComponent {
|
|
if (!defaultLogger) {
|
|
defaultLogger = new LoggerComponent(context);
|
|
}
|
|
return defaultLogger;
|
|
}
|
|
|
|
// Export default instance
|
|
export const logger = new LoggerComponent('main');
|