The getLogger() function was using a single defaultLogger variable that would only be initialized once with the first context. All subsequent calls with different contexts would return the same logger instance, causing all logs to show the same context prefix. Changed to use a Map-based cache that stores separate logger instances per context, ensuring each context gets its own logger with the correct context name. Bump version to 1.0.1-beta.3 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
197 lines
5.6 KiB
TypeScript
197 lines
5.6 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;
|
|
// Defensive check: ensure args is defined and is an array
|
|
if (args && Array.isArray(args) && 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;
|
|
}
|
|
}
|
|
|
|
// Cache logger instances by context to avoid recreating them
|
|
const loggerCache = new Map<string, LoggerComponent>();
|
|
|
|
export function getLogger(context = 'main'): ILoggerComponent {
|
|
// Return cached logger for this context if it exists
|
|
if (loggerCache.has(context)) {
|
|
return loggerCache.get(context)!;
|
|
}
|
|
|
|
// Create new logger for this context
|
|
const newLogger = new LoggerComponent(context);
|
|
loggerCache.set(context, newLogger);
|
|
return newLogger;
|
|
}
|
|
|
|
// Export default instance
|
|
export const logger = new LoggerComponent('main');
|