/** * SMTP Logging Utilities * Provides structured logging for SMTP server components */ import * as plugins from '../../../../plugins.js'; import { logger } from '../../../../logger.js'; import { SecurityLogLevel, SecurityEventType } from '../constants.js'; import type { ISmtpSession } from '../interfaces.js'; /** * SMTP connection metadata to include in logs */ export interface IConnectionMetadata { remoteAddress?: string; remotePort?: number; socketId?: string; secure?: boolean; sessionId?: string; } /** * Log levels for SMTP server */ export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; /** * Options for SMTP log */ export interface ISmtpLogOptions { level?: LogLevel; sessionId?: string; sessionState?: string; remoteAddress?: string; remotePort?: number; command?: string; error?: Error; [key: string]: any; } /** * SMTP logger - provides structured logging for SMTP server */ export class SmtpLogger { /** * Log a message with context * @param level - Log level * @param message - Log message * @param options - Additional log options */ public static log(level: LogLevel, message: string, options: ISmtpLogOptions = {}): void { // Extract error information if provided const errorInfo = options.error ? { errorMessage: options.error.message, errorStack: options.error.stack, errorName: options.error.name } : {}; // Structure log data const logData = { component: 'smtp-server', ...options, ...errorInfo }; // Remove error from log data to avoid duplication if (logData.error) { delete logData.error; } // Log through the main logger logger.log(level, message, logData); // Also console log for immediate visibility during development if (level === 'error' || level === 'warn') { console[level](`[SMTP] ${message}`, logData); } } /** * Log debug level message * @param message - Log message * @param options - Additional log options */ public static debug(message: string, options: ISmtpLogOptions = {}): void { this.log('debug', message, options); } /** * Log info level message * @param message - Log message * @param options - Additional log options */ public static info(message: string, options: ISmtpLogOptions = {}): void { this.log('info', message, options); } /** * Log warning level message * @param message - Log message * @param options - Additional log options */ public static warn(message: string, options: ISmtpLogOptions = {}): void { this.log('warn', message, options); } /** * Log error level message * @param message - Log message * @param options - Additional log options */ public static error(message: string, options: ISmtpLogOptions = {}): void { this.log('error', message, options); } /** * Log command received from client * @param command - The command string * @param socket - The client socket * @param session - The SMTP session */ public static 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 }; this.info(`Command received: ${command}`, { ...clientInfo, command: command.split(' ')[0]?.toUpperCase() }); // Also log to console for easy debugging console.log(`← ${command}`); } /** * Log response sent to client * @param response - The response string * @param socket - The client socket */ public static 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 }; // Get the response code from the beginning of the response const responseCode = response.substring(0, 3); // Log different levels based on response code if (responseCode.startsWith('2') || responseCode.startsWith('3')) { this.debug(`Response sent: ${response}`, clientInfo); } else if (responseCode.startsWith('4')) { this.warn(`Temporary error response: ${response}`, clientInfo); } else if (responseCode.startsWith('5')) { this.error(`Permanent error response: ${response}`, clientInfo); } // Also log to console for easy debugging console.log(`→ ${response}`); } /** * Log client connection event * @param socket - The client socket * @param eventType - Type of connection event (connect, close, error) * @param session - The SMTP session * @param error - Optional error object for error events */ public static 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 }; switch (eventType) { case 'connect': this.info(`New ${clientInfo.secure ? 'secure ' : ''}connection from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, clientInfo); break; case 'close': this.info(`Connection closed from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, clientInfo); break; case 'error': this.error(`Connection error from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, { ...clientInfo, error }); break; } } /** * Log security event * @param level - Security log level * @param type - Security event type * @param message - Log message * @param details - Event details * @param ipAddress - Client IP address * @param domain - Optional domain involved * @param success - Whether the security check was successful */ public static logSecurityEvent( level: SecurityLogLevel, type: SecurityEventType, message: string, details: Record, ipAddress?: string, domain?: string, success?: boolean ): void { // Map security log level to system log level const logLevel: LogLevel = level === SecurityLogLevel.DEBUG ? 'debug' : level === SecurityLogLevel.INFO ? 'info' : level === SecurityLogLevel.WARN ? 'warn' : 'error'; // Log the security event this.log(logLevel, message, { component: 'smtp-security', eventType: type, success, ipAddress, domain, ...details }); } } /** * Default instance for backward compatibility */ export const smtpLogger = SmtpLogger;