514 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			514 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Adaptive SMTP Logging System
							 | 
						||
| 
								 | 
							
								 * Automatically switches between logging modes based on server load (active connections)
							 | 
						||
| 
								 | 
							
								 * to maintain performance during high-concurrency scenarios
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import * as plugins from '../../../../plugins.ts';
							 | 
						||
| 
								 | 
							
								import { logger } from '../../../../logger.ts';
							 | 
						||
| 
								 | 
							
								import { SecurityLogLevel, SecurityEventType } from '../constants.ts';
							 | 
						||
| 
								 | 
							
								import type { ISmtpSession } from '../interfaces.ts';
							 | 
						||
| 
								 | 
							
								import type { LogLevel, ISmtpLogOptions } from './logging.ts';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Log modes based on server load
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export enum LogMode {
							 | 
						||
| 
								 | 
							
								  VERBOSE = 'VERBOSE',     // < 20 connections: Full detailed logging
							 | 
						||
| 
								 | 
							
								  REDUCED = 'REDUCED',     // 20-40 connections: Limited command/response logging, full error logging
							 | 
						||
| 
								 | 
							
								  MINIMAL = 'MINIMAL'      // 40+ connections: Aggregated logging only, critical errors only
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Configuration for adaptive logging thresholds
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export interface IAdaptiveLogConfig {
							 | 
						||
| 
								 | 
							
								  verboseThreshold: number;     // Switch to REDUCED mode above this connection count
							 | 
						||
| 
								 | 
							
								  reducedThreshold: number;     // Switch to MINIMAL mode above this connection count
							 | 
						||
| 
								 | 
							
								  aggregationInterval: number;  // How often to flush aggregated logs (ms)
							 | 
						||
| 
								 | 
							
								  maxAggregatedEntries: number; // Max entries to hold before forced flush
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Aggregated log entry for batching similar events
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								interface IAggregatedLogEntry {
							 | 
						||
| 
								 | 
							
								  type: 'connection' | 'command' | 'response' | 'error';
							 | 
						||
| 
								 | 
							
								  count: number;
							 | 
						||
| 
								 | 
							
								  firstSeen: number;
							 | 
						||
| 
								 | 
							
								  lastSeen: number;
							 | 
						||
| 
								 | 
							
								  sample: {
							 | 
						||
| 
								 | 
							
								    message: string;
							 | 
						||
| 
								 | 
							
								    level: LogLevel;
							 | 
						||
| 
								 | 
							
								    options?: ISmtpLogOptions;
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Connection metadata for aggregation tracking
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								interface IConnectionTracker {
							 | 
						||
| 
								 | 
							
								  activeConnections: number;
							 | 
						||
| 
								 | 
							
								  peakConnections: number;
							 | 
						||
| 
								 | 
							
								  totalConnections: number;
							 | 
						||
| 
								 | 
							
								  connectionsPerSecond: number;
							 | 
						||
| 
								 | 
							
								  lastConnectionTime: number;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Adaptive SMTP Logger that scales logging based on server load
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export class AdaptiveSmtpLogger {
							 | 
						||
| 
								 | 
							
								  private static instance: AdaptiveSmtpLogger;
							 | 
						||
| 
								 | 
							
								  private currentMode: LogMode = LogMode.VERBOSE;
							 | 
						||
| 
								 | 
							
								  private config: IAdaptiveLogConfig;
							 | 
						||
| 
								 | 
							
								  private aggregatedEntries: Map<string, IAggregatedLogEntry> = new Map();
							 | 
						||
| 
								 | 
							
								  private aggregationTimer: NodeJS.Timeout | null = null;
							 | 
						||
| 
								 | 
							
								  private connectionTracker: IConnectionTracker = {
							 | 
						||
| 
								 | 
							
								    activeConnections: 0,
							 | 
						||
| 
								 | 
							
								    peakConnections: 0,
							 | 
						||
| 
								 | 
							
								    totalConnections: 0,
							 | 
						||
| 
								 | 
							
								    connectionsPerSecond: 0,
							 | 
						||
| 
								 | 
							
								    lastConnectionTime: Date.now()
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  private constructor(config?: Partial<IAdaptiveLogConfig>) {
							 | 
						||
| 
								 | 
							
								    this.config = {
							 | 
						||
| 
								 | 
							
								      verboseThreshold: 20,
							 | 
						||
| 
								 | 
							
								      reducedThreshold: 40,
							 | 
						||
| 
								 | 
							
								      aggregationInterval: 30000, // 30 seconds
							 | 
						||
| 
								 | 
							
								      maxAggregatedEntries: 100,
							 | 
						||
| 
								 | 
							
								      ...config
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.startAggregationTimer();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Get singleton instance
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public static getInstance(config?: Partial<IAdaptiveLogConfig>): AdaptiveSmtpLogger {
							 | 
						||
| 
								 | 
							
								    if (!AdaptiveSmtpLogger.instance) {
							 | 
						||
| 
								 | 
							
								      AdaptiveSmtpLogger.instance = new AdaptiveSmtpLogger(config);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return AdaptiveSmtpLogger.instance;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Update active connection count and adjust log mode if needed
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public updateConnectionCount(activeConnections: number): void {
							 | 
						||
| 
								 | 
							
								    this.connectionTracker.activeConnections = activeConnections;
							 | 
						||
| 
								 | 
							
								    this.connectionTracker.peakConnections = Math.max(
							 | 
						||
| 
								 | 
							
								      this.connectionTracker.peakConnections,
							 | 
						||
| 
								 | 
							
								      activeConnections
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const newMode = this.determineLogMode(activeConnections);
							 | 
						||
| 
								 | 
							
								    if (newMode !== this.currentMode) {
							 | 
						||
| 
								 | 
							
								      this.switchLogMode(newMode);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Track new connection for rate calculation
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public trackConnection(): void {
							 | 
						||
| 
								 | 
							
								    this.connectionTracker.totalConnections++;
							 | 
						||
| 
								 | 
							
								    const now = Date.now();
							 | 
						||
| 
								 | 
							
								    const timeDiff = (now - this.connectionTracker.lastConnectionTime) / 1000;
							 | 
						||
| 
								 | 
							
								    if (timeDiff > 0) {
							 | 
						||
| 
								 | 
							
								      this.connectionTracker.connectionsPerSecond = 1 / timeDiff;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.connectionTracker.lastConnectionTime = now;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Get current logging mode
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public getCurrentMode(): LogMode {
							 | 
						||
| 
								 | 
							
								    return this.currentMode;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Get connection statistics
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public getConnectionStats(): IConnectionTracker {
							 | 
						||
| 
								 | 
							
								    return { ...this.connectionTracker };
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Log a message with adaptive behavior
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public log(level: LogLevel, message: string, options: ISmtpLogOptions = {}): void {
							 | 
						||
| 
								 | 
							
								    // Always log structured data
							 | 
						||
| 
								 | 
							
								    const errorInfo = options.error ? {
							 | 
						||
| 
								 | 
							
								      errorMessage: options.error.message,
							 | 
						||
| 
								 | 
							
								      errorStack: options.error.stack,
							 | 
						||
| 
								 | 
							
								      errorName: options.error.name
							 | 
						||
| 
								 | 
							
								    } : {};
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    const logData = {
							 | 
						||
| 
								 | 
							
								      component: 'smtp-server',
							 | 
						||
| 
								 | 
							
								      logMode: this.currentMode,
							 | 
						||
| 
								 | 
							
								      activeConnections: this.connectionTracker.activeConnections,
							 | 
						||
| 
								 | 
							
								      ...options,
							 | 
						||
| 
								 | 
							
								      ...errorInfo
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    if (logData.error) {
							 | 
						||
| 
								 | 
							
								      delete logData.error;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    logger.log(level, message, logData);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Adaptive console logging based on mode
							 | 
						||
| 
								 | 
							
								    switch (this.currentMode) {
							 | 
						||
| 
								 | 
							
								      case LogMode.VERBOSE:
							 | 
						||
| 
								 | 
							
								        // Full console logging
							 | 
						||
| 
								 | 
							
								        if (level === 'error' || level === 'warn') {
							 | 
						||
| 
								 | 
							
								          console[level](`[SMTP] ${message}`, logData);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      case LogMode.REDUCED:
							 | 
						||
| 
								 | 
							
								        // Only errors and warnings to console
							 | 
						||
| 
								 | 
							
								        if (level === 'error' || level === 'warn') {
							 | 
						||
| 
								 | 
							
								          console[level](`[SMTP] ${message}`, logData);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      case LogMode.MINIMAL:
							 | 
						||
| 
								 | 
							
								        // Only critical errors to console
							 | 
						||
| 
								 | 
							
								        if (level === 'error' && (message.includes('critical') || message.includes('security') || message.includes('crash'))) {
							 | 
						||
| 
								 | 
							
								          console[level](`[SMTP] ${message}`, logData);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Log command with adaptive behavior
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public logCommand(command: string, socket: plugins.net.Socket | plugins.tls.TLSSocket, session?: ISmtpSession): void {
							 | 
						||
| 
								 | 
							
								    const clientInfo = {
							 | 
						||
| 
								 | 
							
								      remoteAddress: socket.remoteAddress,
							 | 
						||
| 
								 | 
							
								      remotePort: socket.remotePort,
							 | 
						||
| 
								 | 
							
								      secure: socket instanceof plugins.tls.TLSSocket,
							 | 
						||
| 
								 | 
							
								      sessionId: session?.id,
							 | 
						||
| 
								 | 
							
								      sessionState: session?.state
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    switch (this.currentMode) {
							 | 
						||
| 
								 | 
							
								      case LogMode.VERBOSE:
							 | 
						||
| 
								 | 
							
								        this.log('info', `Command received: ${command}`, {
							 | 
						||
| 
								 | 
							
								          ...clientInfo,
							 | 
						||
| 
								 | 
							
								          command: command.split(' ')[0]?.toUpperCase()
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								        console.log(`← ${command}`);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      case LogMode.REDUCED:
							 | 
						||
| 
								 | 
							
								        // Aggregate commands instead of logging each one
							 | 
						||
| 
								 | 
							
								        this.aggregateEntry('command', 'info', `Command: ${command.split(' ')[0]?.toUpperCase()}`, clientInfo);
							 | 
						||
| 
								 | 
							
								        // Only show error commands
							 | 
						||
| 
								 | 
							
								        if (command.toUpperCase().startsWith('QUIT') || command.includes('error')) {
							 | 
						||
| 
								 | 
							
								          console.log(`← ${command}`);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      case LogMode.MINIMAL:
							 | 
						||
| 
								 | 
							
								        // Only aggregate, no console output unless it's an error command
							 | 
						||
| 
								 | 
							
								        this.aggregateEntry('command', 'info', `Command: ${command.split(' ')[0]?.toUpperCase()}`, clientInfo);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Log response with adaptive behavior
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public logResponse(response: string, socket: plugins.net.Socket | plugins.tls.TLSSocket): void {
							 | 
						||
| 
								 | 
							
								    const clientInfo = {
							 | 
						||
| 
								 | 
							
								      remoteAddress: socket.remoteAddress,
							 | 
						||
| 
								 | 
							
								      remotePort: socket.remotePort,
							 | 
						||
| 
								 | 
							
								      secure: socket instanceof plugins.tls.TLSSocket
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const responseCode = response.substring(0, 3);
							 | 
						||
| 
								 | 
							
								    const isError = responseCode.startsWith('4') || responseCode.startsWith('5');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    switch (this.currentMode) {
							 | 
						||
| 
								 | 
							
								      case LogMode.VERBOSE:
							 | 
						||
| 
								 | 
							
								        if (responseCode.startsWith('2') || responseCode.startsWith('3')) {
							 | 
						||
| 
								 | 
							
								          this.log('debug', `Response sent: ${response}`, clientInfo);
							 | 
						||
| 
								 | 
							
								        } else if (responseCode.startsWith('4')) {
							 | 
						||
| 
								 | 
							
								          this.log('warn', `Temporary error response: ${response}`, clientInfo);
							 | 
						||
| 
								 | 
							
								        } else if (responseCode.startsWith('5')) {
							 | 
						||
| 
								 | 
							
								          this.log('error', `Permanent error response: ${response}`, clientInfo);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        console.log(`→ ${response}`);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      case LogMode.REDUCED:
							 | 
						||
| 
								 | 
							
								        // Log errors normally, aggregate success responses
							 | 
						||
| 
								 | 
							
								        if (isError) {
							 | 
						||
| 
								 | 
							
								          if (responseCode.startsWith('4')) {
							 | 
						||
| 
								 | 
							
								            this.log('warn', `Temporary error response: ${response}`, clientInfo);
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            this.log('error', `Permanent error response: ${response}`, clientInfo);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          console.log(`→ ${response}`);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          this.aggregateEntry('response', 'debug', `Response: ${responseCode}xx`, clientInfo);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      case LogMode.MINIMAL:
							 | 
						||
| 
								 | 
							
								        // Only log critical errors
							 | 
						||
| 
								 | 
							
								        if (responseCode.startsWith('5')) {
							 | 
						||
| 
								 | 
							
								          this.log('error', `Permanent error response: ${response}`, clientInfo);
							 | 
						||
| 
								 | 
							
								          console.log(`→ ${response}`);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          this.aggregateEntry('response', 'debug', `Response: ${responseCode}xx`, clientInfo);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Log connection event with adaptive behavior
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public logConnection(
							 | 
						||
| 
								 | 
							
								    socket: plugins.net.Socket | plugins.tls.TLSSocket,
							 | 
						||
| 
								 | 
							
								    eventType: 'connect' | 'close' | 'error',
							 | 
						||
| 
								 | 
							
								    session?: ISmtpSession,
							 | 
						||
| 
								 | 
							
								    error?: Error
							 | 
						||
| 
								 | 
							
								  ): void {
							 | 
						||
| 
								 | 
							
								    const clientInfo = {
							 | 
						||
| 
								 | 
							
								      remoteAddress: socket.remoteAddress,
							 | 
						||
| 
								 | 
							
								      remotePort: socket.remotePort,
							 | 
						||
| 
								 | 
							
								      secure: socket instanceof plugins.tls.TLSSocket,
							 | 
						||
| 
								 | 
							
								      sessionId: session?.id,
							 | 
						||
| 
								 | 
							
								      sessionState: session?.state
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (eventType === 'connect') {
							 | 
						||
| 
								 | 
							
								      this.trackConnection();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    switch (this.currentMode) {
							 | 
						||
| 
								 | 
							
								      case LogMode.VERBOSE:
							 | 
						||
| 
								 | 
							
								        // Full connection logging
							 | 
						||
| 
								 | 
							
								        switch (eventType) {
							 | 
						||
| 
								 | 
							
								          case 'connect':
							 | 
						||
| 
								 | 
							
								            this.log('info', `New ${clientInfo.secure ? 'secure ' : ''}connection from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, clientInfo);
							 | 
						||
| 
								 | 
							
								            break;
							 | 
						||
| 
								 | 
							
								          case 'close':
							 | 
						||
| 
								 | 
							
								            this.log('info', `Connection closed from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, clientInfo);
							 | 
						||
| 
								 | 
							
								            break;
							 | 
						||
| 
								 | 
							
								          case 'error':
							 | 
						||
| 
								 | 
							
								            this.log('error', `Connection error from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, {
							 | 
						||
| 
								 | 
							
								              ...clientInfo,
							 | 
						||
| 
								 | 
							
								              error
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								            break;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      case LogMode.REDUCED:
							 | 
						||
| 
								 | 
							
								        // Aggregate normal connections, log errors
							 | 
						||
| 
								 | 
							
								        if (eventType === 'error') {
							 | 
						||
| 
								 | 
							
								          this.log('error', `Connection error from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, {
							 | 
						||
| 
								 | 
							
								            ...clientInfo,
							 | 
						||
| 
								 | 
							
								            error
							 | 
						||
| 
								 | 
							
								          });
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          this.aggregateEntry('connection', 'info', `Connection ${eventType}`, clientInfo);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      case LogMode.MINIMAL:
							 | 
						||
| 
								 | 
							
								        // Only aggregate, except for critical errors
							 | 
						||
| 
								 | 
							
								        if (eventType === 'error' && error && (error.message.includes('security') || error.message.includes('critical'))) {
							 | 
						||
| 
								 | 
							
								          this.log('error', `Critical connection error from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, {
							 | 
						||
| 
								 | 
							
								            ...clientInfo,
							 | 
						||
| 
								 | 
							
								            error
							 | 
						||
| 
								 | 
							
								          });
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          this.aggregateEntry('connection', 'info', `Connection ${eventType}`, clientInfo);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Log security event (always logged regardless of mode)
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public logSecurityEvent(
							 | 
						||
| 
								 | 
							
								    level: SecurityLogLevel,
							 | 
						||
| 
								 | 
							
								    type: SecurityEventType,
							 | 
						||
| 
								 | 
							
								    message: string,
							 | 
						||
| 
								 | 
							
								    details: Record<string, any>,
							 | 
						||
| 
								 | 
							
								    ipAddress?: string,
							 | 
						||
| 
								 | 
							
								    domain?: string,
							 | 
						||
| 
								 | 
							
								    success?: boolean
							 | 
						||
| 
								 | 
							
								  ): void {
							 | 
						||
| 
								 | 
							
								    const logLevel: LogLevel = level === SecurityLogLevel.DEBUG ? 'debug' :
							 | 
						||
| 
								 | 
							
								                               level === SecurityLogLevel.INFO ? 'info' :
							 | 
						||
| 
								 | 
							
								                               level === SecurityLogLevel.WARN ? 'warn' : 'error';
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    // Security events are always logged in full detail
							 | 
						||
| 
								 | 
							
								    this.log(logLevel, message, {
							 | 
						||
| 
								 | 
							
								      component: 'smtp-security',
							 | 
						||
| 
								 | 
							
								      eventType: type,
							 | 
						||
| 
								 | 
							
								      success,
							 | 
						||
| 
								 | 
							
								      ipAddress,
							 | 
						||
| 
								 | 
							
								      domain,
							 | 
						||
| 
								 | 
							
								      ...details
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Determine appropriate log mode based on connection count
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  private determineLogMode(activeConnections: number): LogMode {
							 | 
						||
| 
								 | 
							
								    if (activeConnections >= this.config.reducedThreshold) {
							 | 
						||
| 
								 | 
							
								      return LogMode.MINIMAL;
							 | 
						||
| 
								 | 
							
								    } else if (activeConnections >= this.config.verboseThreshold) {
							 | 
						||
| 
								 | 
							
								      return LogMode.REDUCED;
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      return LogMode.VERBOSE;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Switch to a new log mode
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  private switchLogMode(newMode: LogMode): void {
							 | 
						||
| 
								 | 
							
								    const oldMode = this.currentMode;
							 | 
						||
| 
								 | 
							
								    this.currentMode = newMode;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Log the mode switch
							 | 
						||
| 
								 | 
							
								    console.log(`[SMTP] Adaptive logging switched from ${oldMode} to ${newMode} (${this.connectionTracker.activeConnections} active connections)`);
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    this.log('info', `Adaptive logging mode changed to ${newMode}`, {
							 | 
						||
| 
								 | 
							
								      oldMode,
							 | 
						||
| 
								 | 
							
								      newMode,
							 | 
						||
| 
								 | 
							
								      activeConnections: this.connectionTracker.activeConnections,
							 | 
						||
| 
								 | 
							
								      peakConnections: this.connectionTracker.peakConnections,
							 | 
						||
| 
								 | 
							
								      totalConnections: this.connectionTracker.totalConnections
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // If switching to more verbose mode, flush aggregated entries
							 | 
						||
| 
								 | 
							
								    if ((oldMode === LogMode.MINIMAL && newMode !== LogMode.MINIMAL) ||
							 | 
						||
| 
								 | 
							
								        (oldMode === LogMode.REDUCED && newMode === LogMode.VERBOSE)) {
							 | 
						||
| 
								 | 
							
								      this.flushAggregatedEntries();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Add entry to aggregation buffer
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  private aggregateEntry(
							 | 
						||
| 
								 | 
							
								    type: 'connection' | 'command' | 'response' | 'error',
							 | 
						||
| 
								 | 
							
								    level: LogLevel,
							 | 
						||
| 
								 | 
							
								    message: string,
							 | 
						||
| 
								 | 
							
								    options?: ISmtpLogOptions
							 | 
						||
| 
								 | 
							
								  ): void {
							 | 
						||
| 
								 | 
							
								    const key = `${type}:${message}`;
							 | 
						||
| 
								 | 
							
								    const now = Date.now();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (this.aggregatedEntries.has(key)) {
							 | 
						||
| 
								 | 
							
								      const entry = this.aggregatedEntries.get(key)!;
							 | 
						||
| 
								 | 
							
								      entry.count++;
							 | 
						||
| 
								 | 
							
								      entry.lastSeen = now;
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      this.aggregatedEntries.set(key, {
							 | 
						||
| 
								 | 
							
								        type,
							 | 
						||
| 
								 | 
							
								        count: 1,
							 | 
						||
| 
								 | 
							
								        firstSeen: now,
							 | 
						||
| 
								 | 
							
								        lastSeen: now,
							 | 
						||
| 
								 | 
							
								        sample: { message, level, options }
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Force flush if we have too many entries
							 | 
						||
| 
								 | 
							
								    if (this.aggregatedEntries.size >= this.config.maxAggregatedEntries) {
							 | 
						||
| 
								 | 
							
								      this.flushAggregatedEntries();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Start the aggregation timer
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  private startAggregationTimer(): void {
							 | 
						||
| 
								 | 
							
								    if (this.aggregationTimer) {
							 | 
						||
| 
								 | 
							
								      clearInterval(this.aggregationTimer);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.aggregationTimer = setInterval(() => {
							 | 
						||
| 
								 | 
							
								      this.flushAggregatedEntries();
							 | 
						||
| 
								 | 
							
								    }, this.config.aggregationInterval);
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    // Unref the timer so it doesn't keep the process alive
							 | 
						||
| 
								 | 
							
								    if (this.aggregationTimer && typeof this.aggregationTimer.unref === 'function') {
							 | 
						||
| 
								 | 
							
								      this.aggregationTimer.unref();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Flush aggregated entries to logs
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  private flushAggregatedEntries(): void {
							 | 
						||
| 
								 | 
							
								    if (this.aggregatedEntries.size === 0) {
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const summary: Record<string, number> = {};
							 | 
						||
| 
								 | 
							
								    let totalAggregated = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for (const [key, entry] of this.aggregatedEntries.entries()) {
							 | 
						||
| 
								 | 
							
								      summary[entry.type] = (summary[entry.type] || 0) + entry.count;
							 | 
						||
| 
								 | 
							
								      totalAggregated += entry.count;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Log a sample of high-frequency entries
							 | 
						||
| 
								 | 
							
								      if (entry.count >= 10) {
							 | 
						||
| 
								 | 
							
								        this.log(entry.sample.level, `${entry.sample.message} (aggregated: ${entry.count} occurrences)`, {
							 | 
						||
| 
								 | 
							
								          ...entry.sample.options,
							 | 
						||
| 
								 | 
							
								          aggregated: true,
							 | 
						||
| 
								 | 
							
								          occurrences: entry.count,
							 | 
						||
| 
								 | 
							
								          timeSpan: entry.lastSeen - entry.firstSeen
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Log aggregation summary
							 | 
						||
| 
								 | 
							
								    console.log(`[SMTP] Aggregated ${totalAggregated} log entries: ${JSON.stringify(summary)}`);
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    this.log('info', 'Aggregated log summary', {
							 | 
						||
| 
								 | 
							
								      totalEntries: totalAggregated,
							 | 
						||
| 
								 | 
							
								      breakdown: summary,
							 | 
						||
| 
								 | 
							
								      logMode: this.currentMode,
							 | 
						||
| 
								 | 
							
								      activeConnections: this.connectionTracker.activeConnections
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Clear aggregated entries
							 | 
						||
| 
								 | 
							
								    this.aggregatedEntries.clear();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Cleanup resources
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  public destroy(): void {
							 | 
						||
| 
								 | 
							
								    if (this.aggregationTimer) {
							 | 
						||
| 
								 | 
							
								      clearInterval(this.aggregationTimer);
							 | 
						||
| 
								 | 
							
								      this.aggregationTimer = null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.flushAggregatedEntries();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Default instance for easy access
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export const adaptiveLogger = AdaptiveSmtpLogger.getInstance();
							 |