// Copyright 2025 Chris Bunting
// Brief: Logging infrastructure for MCP Code Analysis & Quality Server
// Scope: Centralized logging system with multiple output support
import { LoggerInterface, LogLevel } from '@mcp-code-analysis/shared-types';
export interface LogEntry {
timestamp: Date;
level: LogLevel;
message: string;
meta?: Record<string, any>;
context?: string;
}
export interface LogTransport {
log(entry: LogEntry): Promise<void>;
flush(): Promise<void>;
}
export class ConsoleTransport implements LogTransport {
async log(entry: LogEntry): Promise<void> {
const timestamp = entry.timestamp.toISOString();
const level = entry.level.toUpperCase();
const context = entry.context ? `[${entry.context}]` : '';
const meta = entry.meta ? ` ${JSON.stringify(entry.meta)}` : '';
const logMessage = `${timestamp} ${level}${context}: ${entry.message}${meta}`;
switch (entry.level) {
case LogLevel.ERROR:
console.error(logMessage);
break;
case LogLevel.WARN:
console.warn(logMessage);
break;
case LogLevel.INFO:
console.info(logMessage);
break;
case LogLevel.DEBUG:
console.debug(logMessage);
break;
}
}
async flush(): Promise<void> {
// Console transport doesn't need flushing
}
}
export class FileTransport implements LogTransport {
private filePath: string;
private fs: typeof import('fs');
private path: typeof import('path');
constructor(filePath: string) {
this.filePath = filePath;
this.fs = require('fs');
this.path = require('path');
this.ensureLogDirectory();
}
private ensureLogDirectory(): void {
const dir = this.path.dirname(this.filePath);
if (!this.fs.existsSync(dir)) {
this.fs.mkdirSync(dir, { recursive: true });
}
}
async log(entry: LogEntry): Promise<void> {
const timestamp = entry.timestamp.toISOString();
const level = entry.level.toUpperCase();
const context = entry.context ? `[${entry.context}]` : '';
const meta = entry.meta ? ` ${JSON.stringify(entry.meta)}` : '';
const logMessage = `${timestamp} ${level}${context}: ${entry.message}${meta}\n`;
try {
this.fs.appendFileSync(this.filePath, logMessage);
} catch (error) {
console.error('Failed to write to log file:', error);
}
}
async flush(): Promise<void> {
// File transport doesn't need explicit flushing
}
}
export class Logger implements LoggerInterface {
private context?: string;
private transports: LogTransport[] = [];
private minLevel: LogLevel;
constructor(
minLevel: LogLevel = LogLevel.INFO,
context?: string
) {
this.minLevel = minLevel;
this.context = context;
}
addTransport(transport: LogTransport): void {
this.transports.push(transport);
}
setContext(context: string): void {
this.context = context;
}
private shouldLog(level: LogLevel): boolean {
const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
return levels.indexOf(level) >= levels.indexOf(this.minLevel);
}
private async log(
level: LogLevel,
message: string,
meta?: Record<string, any>
): Promise<void> {
if (!this.shouldLog(level)) {
return;
}
const entry: LogEntry = {
timestamp: new Date(),
level,
message,
meta,
context: this.context
};
for (const transport of this.transports) {
try {
await transport.log(entry);
} catch (error) {
console.error('Log transport error:', error);
}
}
}
debug(message: string, ...args: any[]): void {
const meta = args.length > 0 ? { args } : undefined;
this.log(LogLevel.DEBUG, message, meta).catch(console.error);
}
info(message: string, ...args: any[]): void {
const meta = args.length > 0 ? { args } : undefined;
this.log(LogLevel.INFO, message, meta).catch(console.error);
}
warn(message: string, ...args: any[]): void {
const meta = args.length > 0 ? { args } : undefined;
this.log(LogLevel.WARN, message, meta).catch(console.error);
}
error(message: string, ...args: any[]): void {
const meta = args.length > 0 ? { args } : undefined;
this.log(LogLevel.ERROR, message, meta).catch(console.error);
}
child(context: string): Logger {
const fullContext = this.context ? `${this.context}:${context}` : context;
const childLogger = new Logger(this.minLevel, fullContext);
childLogger.transports = [...this.transports];
return childLogger;
}
async flush(): Promise<void> {
for (const transport of this.transports) {
try {
await transport.flush();
} catch (error) {
console.error('Error flushing log transport:', error);
}
}
}
}
// Factory functions
export function createLogger(
minLevel: LogLevel = LogLevel.INFO,
context?: string
): Logger {
const logger = new Logger(minLevel, context);
// Add default console transport
logger.addTransport(new ConsoleTransport());
return logger;
}
export function createFileLogger(
filePath: string,
minLevel: LogLevel = LogLevel.INFO,
context?: string
): Logger {
const logger = new Logger(minLevel, context);
// Add console and file transports
logger.addTransport(new ConsoleTransport());
logger.addTransport(new FileTransport(filePath));
return logger;
}