/** * Logging utility for SMB2 Server * Provides structured logging with different levels and formatting */ import winston from 'winston'; export type LogLevel = 'error' | 'warn' | 'info' | 'debug'; export interface LoggerOptions { level?: LogLevel; component?: string; enableConsole?: boolean; enableFile?: boolean; logFile?: string; format?: 'json' | 'simple' | 'detailed'; } export class Logger { private readonly winston: winston.Logger; private readonly component: string; constructor(component: string = 'SMB2Server', options: LoggerOptions = {}) { this.component = component; const { level = 'debug', enableConsole = true, enableFile = false, logFile = 'smb-server.log', format = 'detailed', } = options; // Create winston transports const transports: winston.transport[] = []; if (enableConsole) { transports.push( new winston.transports.Console({ level, format: this.getConsoleFormat(format), }) ); } if (enableFile) { transports.push( new winston.transports.File({ filename: logFile, level, format: this.getFileFormat(format), maxsize: 10 * 1024 * 1024, // 10MB maxFiles: 5, }) ); } // Create winston logger this.winston = winston.createLogger({ level, transports, exitOnError: false, }); } /** * Log error message */ public error(message: string, meta?: any): void { this.winston.error(message, { component: this.component, ...meta }); } /** * Log warning message */ public warn(message: string, meta?: any): void { this.winston.warn(message, { component: this.component, ...meta }); } /** * Log info message */ public info(message: string, meta?: any): void { this.winston.info(message, { component: this.component, ...meta }); } /** * Log debug message */ public debug(message: string, meta?: any): void { this.winston.debug(message, { component: this.component, ...meta }); } /** * Log with specific level */ public log(level: LogLevel, message: string, meta?: any): void { this.winston.log(level, message, { component: this.component, ...meta }); } /** * Create a child logger with additional context */ public child(context: Record): Logger { const childLogger = new Logger(this.component); childLogger.winston.defaultMeta = { ...this.winston.defaultMeta, ...context }; return childLogger; } /** * Set log level */ public setLevel(level: LogLevel): void { this.winston.level = level; } /** * Get current log level */ public getLevel(): string { return this.winston.level; } /** * Check if level is enabled */ public isLevelEnabled(level: LogLevel): boolean { return this.winston.isLevelEnabled(level); } /** * Log SMB protocol message */ public logProtocol( direction: 'incoming' | 'outgoing', command: string, connectionId?: string, sessionId?: string, meta?: any ): void { this.debug(`${direction.toUpperCase()} ${command}`, { direction, command, connectionId, sessionId, ...meta, }); } /** * Log connection event */ public logConnection( event: 'established' | 'closed', connectionId: string, clientAddress: string, meta?: any ): void { this.info(`Connection ${event}: ${connectionId} from ${clientAddress}`, { event: `connection:${event}`, connectionId, clientAddress, ...meta, }); } /** * Log session event */ public logSession( event: 'established' | 'closed', sessionId: string, connectionId: string, userId?: string, meta?: any ): void { this.info(`Session ${event}: ${sessionId}`, { event: `session:${event}`, sessionId, connectionId, userId, ...meta, }); } /** * Log file operation */ public logFileOperation( operation: 'open' | 'close' | 'read' | 'write' | 'delete', filename: string, connectionId?: string, sessionId?: string, meta?: any ): void { this.info(`File ${operation}: ${filename}`, { operation: `file:${operation}`, filename, connectionId, sessionId, ...meta, }); } /** * Log performance metrics */ public logPerformance( operation: string, duration: number, meta?: any ): void { this.debug(`Performance: ${operation} took ${duration}ms`, { operation, duration, type: 'performance', ...meta, }); } /** * Log security event */ public logSecurity( event: 'auth_success' | 'auth_failure' | 'access_denied' | 'suspicious_activity', details: string, connectionId?: string, sessionId?: string, meta?: any ): void { this.warn(`Security event: ${event} - ${details}`, { event: `security:${event}`, details, connectionId, sessionId, type: 'security', ...meta, }); } /** * Get console format based on format type */ private getConsoleFormat(format: string): winston.Logform.Format { switch (format) { case 'json': return winston.format.combine( winston.format.timestamp(), winston.format.json() ); case 'simple': return winston.format.combine( winston.format.colorize(), winston.format.simple() ); case 'detailed': default: return winston.format.combine( winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.printf(this.detailedFormatter) ); } } /** * Get file format based on format type */ private getFileFormat(format: string): winston.Logform.Format { switch (format) { case 'simple': return winston.format.combine( winston.format.timestamp(), winston.format.simple() ); case 'detailed': return winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.printf(this.detailedFormatter) ); case 'json': default: return winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ); } } /** * Detailed log formatter */ private detailedFormatter = (info: winston.Logform.TransformableInfo): string => { const { timestamp, level, message, component, ...meta } = info; const componentStr = component ? `[${component}]` : ''; let metaStr = ''; if (Object.keys(meta).length > 0) { // Filter out common fields and format remaining metadata const filteredMeta = { ...meta }; delete filteredMeta['stack']; // Stack traces are handled separately if (Object.keys(filteredMeta).length > 0) { metaStr = ` ${JSON.stringify(filteredMeta)}`; } } let result = `${timestamp} ${level.toUpperCase().padEnd(5)} ${componentStr} ${message}${metaStr}`; // Add stack trace for errors if (info['stack']) { result += `\n${info['stack']}`; } return result; }; /** * Create a logger for a specific SMB command */ public static createCommandLogger(command: string, connectionId: string): Logger { return new Logger(`SMB2:${command}:${connectionId}`); } /** * Create a logger with connection context */ public static createConnectionLogger(connectionId: string, clientAddress: string): Logger { const logger = new Logger('SMB2Connection'); return logger.child({ connectionId, clientAddress }); } /** * Create a logger with session context */ public static createSessionLogger(sessionId: string, connectionId: string): Logger { const logger = new Logger('SMB2Session'); return logger.child({ sessionId, connectionId }); } /** * Gracefully close the logger */ public close(): Promise { return new Promise((resolve) => { this.winston.close(); // Winston doesn't provide a global shutdown, so this is a placeholder // for any cleanup that might be needed setTimeout(resolve, 100) }); } /** * Handle process shutdown */ public static async shutdown(): Promise { // Winston doesn't provide a global shutdown, so this is a placeholder // for any cleanup that might be needed await new Promise(resolve => setTimeout(resolve, 100)); } } /** * Default logger instance */ export const defaultLogger = new Logger('SMB2Server', { level: process.env['LOG_LEVEL'] as LogLevel || 'info', enableConsole: true, enableFile: process.env['ENABLE_FILE_LOGGING'] === 'true', logFile: process.env['LOG_FILE'] || 'smb-server.log', format: (process.env['LOG_FORMAT'] as any) || 'detailed', }); /** * Utility function to create a logger */ export function createLogger(component: string, options?: LoggerOptions): Logger { return new Logger(component, options); } /** * Set global log level */ export function setGlobalLogLevel(level: LogLevel): void { defaultLogger.setLevel(level); }