/**
* Logger
*
* Structured logging infrastructure with configurable output.
* Production-ready with support for different log levels and contexts.
*/
import { LogLevel, LogEntry } from '../../domain/types.js';
/**
* Logger interface for dependency injection
*/
export interface ILogger {
debug(message: string, context?: Record<string, unknown>): void;
info(message: string, context?: Record<string, unknown>): void;
warn(message: string, context?: Record<string, unknown>): void;
error(message: string, error?: Error, context?: Record<string, unknown>): void;
}
/**
* Console logger implementation
* Outputs structured JSON logs to console
*/
export class ConsoleLogger implements ILogger {
private readonly serviceName: string;
private readonly minLevel: LogLevel;
private readonly levelPriority: Record<LogLevel, number> = {
debug: 0,
info: 1,
warn: 2,
error: 3,
};
constructor(serviceName: string = 'mcp-server', minLevel: LogLevel = 'info') {
this.serviceName = serviceName;
this.minLevel = minLevel;
}
debug(message: string, context?: Record<string, unknown>): void {
this.log('debug', message, context);
}
info(message: string, context?: Record<string, unknown>): void {
this.log('info', message, context);
}
warn(message: string, context?: Record<string, unknown>): void {
this.log('warn', message, context);
}
error(message: string, error?: Error, context?: Record<string, unknown>): void {
this.log('error', message, context, error);
}
private log(
level: LogLevel,
message: string,
context?: Record<string, unknown>,
error?: Error
): void {
if (this.levelPriority[level] < this.levelPriority[this.minLevel]) {
return;
}
const entry: LogEntry = {
level,
message,
timestamp: new Date().toISOString(),
context: {
service: this.serviceName,
...context,
},
error,
};
// Use appropriate console method
const output = this.formatLogEntry(entry);
switch (level) {
case 'debug':
console.debug(output);
break;
case 'info':
console.info(output);
break;
case 'warn':
console.warn(output);
break;
case 'error':
console.error(output);
break;
}
}
private formatLogEntry(entry: LogEntry): string {
const { level, message, timestamp, context, error } = entry;
const logObject: Record<string, unknown> = {
timestamp,
level,
message,
...context,
};
if (error) {
logObject.error = {
name: error.name,
message: error.message,
stack: error.stack,
};
}
return JSON.stringify(logObject);
}
}
/**
* No-op logger for testing
*/
export class NoOpLogger implements ILogger {
debug(): void {}
info(): void {}
warn(): void {}
error(): void {}
}
/**
* Create a logger instance
*/
export function createLogger(
serviceName?: string,
minLevel?: LogLevel
): ILogger {
const logLevel = (process.env.LOG_LEVEL as LogLevel) || minLevel || 'info';
return new ConsoleLogger(serviceName, logLevel);
}