type LogLevel = 'debug' | 'info' | 'warn' | 'error';
interface LogEntry {
level: LogLevel;
message: string;
timestamp: string;
data?: unknown;
}
class Logger {
private logLevel: LogLevel;
constructor() {
this.logLevel = (process.env.LOG_LEVEL?.toLowerCase() as LogLevel) || 'warn';
}
private shouldLog(level: LogLevel): boolean {
const levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];
return levels.indexOf(level) >= levels.indexOf(this.logLevel);
}
private formatMessage(entry: LogEntry): string {
const { level, message, timestamp, data } = entry;
let output = `[${timestamp}] ${level.toUpperCase()}: ${message}`;
if (data !== undefined) {
output += ` ${JSON.stringify(data, null, 2)}`;
}
return output;
}
private log(level: LogLevel, message: string, data?: unknown): void {
if (!this.shouldLog(level)) {
return;
}
const entry: LogEntry = {
level,
message,
timestamp: new Date().toISOString(),
data,
};
const formattedMessage = this.formatMessage(entry);
switch (level) {
case 'debug':
console.debug(formattedMessage);
break;
case 'info':
console.info(formattedMessage);
break;
case 'warn':
console.warn(formattedMessage);
break;
case 'error':
console.error(formattedMessage);
break;
}
}
debug(message: string, data?: unknown): void {
this.log('debug', message, data);
}
info(message: string, data?: unknown): void {
this.log('info', message, data);
}
warn(message: string, data?: unknown): void {
this.log('warn', message, data);
}
error(message: string, data?: unknown): void {
this.log('error', message, data);
}
}
export const logger = new Logger();