import { loadEnvConfig } from './env.js'; /** * Log levels enum */ export enum LogLevel { TRACE = 'trace', DEBUG = 'debug', INFO = 'info', WARN = 'warn', ERROR = 'error', FATAL = 'fatal', SILENT = 'silent' } /** * Log level priorities */ const LOG_LEVEL_PRIORITY: Record = { [LogLevel.TRACE]: 0, [LogLevel.DEBUG]: 1, [LogLevel.INFO]: 2, [LogLevel.WARN]: 3, [LogLevel.ERROR]: 4, [LogLevel.FATAL]: 5, [LogLevel.SILENT]: 6 }; /** * Log format options */ export enum LogFormat { TEXT = 'text', JSON = 'json' } /** * Log destination options */ export enum LogDestination { CONSOLE = 'console', FILE = 'file', BOTH = 'both' } /** * Logging configuration interface */ export interface LoggingConfig { level: LogLevel; format: LogFormat; destination: LogDestination; pretty: boolean; includeTimestamp: boolean; includeRequestId: boolean; filePath?: string; redactedFields: string[]; } /** * Default logging configuration */ const DEFAULT_LOGGING_CONFIG: LoggingConfig = { level: LogLevel.INFO, format: LogFormat.TEXT, destination: LogDestination.CONSOLE, pretty: true, includeTimestamp: true, includeRequestId: true, redactedFields: [ 'password', 'apiKey', 'authorization', 'secret', 'token', 'accessKey', 'secretKey', 'credentials' ] }; /** * Load logging configuration from environment * @returns Logging configuration */ export function loadLoggingConfig(): LoggingConfig { const env = loadEnvConfig(); // Parse log level from environment let level = DEFAULT_LOGGING_CONFIG.level; const envLevel = env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG; if (process.env.LOG_LEVEL && isValidLogLevel(process.env.LOG_LEVEL)) { level = process.env.LOG_LEVEL as LogLevel; } else { level = envLevel; } // Parse log format from environment let format = DEFAULT_LOGGING_CONFIG.format; if (process.env.LOG_FORMAT === 'json') { format = LogFormat.JSON; } else if (process.env.LOG_FORMAT === 'text') { format = LogFormat.TEXT; } // In production, default to JSON format if (env.NODE_ENV === 'production' && !process.env.LOG_FORMAT) { format = LogFormat.JSON; } // Parse log destination from environment let destination = DEFAULT_LOGGING_CONFIG.destination; if (process.env.LOG_DESTINATION === 'file') { destination = LogDestination.FILE; } else if (process.env.LOG_DESTINATION === 'both') { destination = LogDestination.BOTH; } // Parse pretty-print option const pretty = process.env.LOG_PRETTY === 'true' || (env.NODE_ENV === 'development' && process.env.LOG_PRETTY !== 'false'); return { level, format, destination, pretty, includeTimestamp: true, includeRequestId: true, filePath: process.env.LOG_FILE_PATH || './logs/app.log', redactedFields: DEFAULT_LOGGING_CONFIG.redactedFields }; } /** * Check if a log level is valid * @param level The log level to check * @returns Whether the log level is valid */ export function isValidLogLevel(level: string): boolean { return Object.values(LogLevel).includes(level as LogLevel); } /** * Check if a log level meets the minimum level * @param level The log level to check * @param minLevel The minimum log level * @returns Whether the log level meets the minimum */ export function shouldLog(level: LogLevel, minLevel: LogLevel): boolean { return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[minLevel]; } /** * Redact sensitive fields from an object * @param obj The object to redact * @param fields Fields to redact * @returns Redacted object */ export function redactSensitiveFields(obj: Record, fields: string[]): Record { if (!obj || typeof obj !== 'object') { return obj; } const result: Record = {}; for (const [key, value] of Object.entries(obj)) { if (fields.some(field => key.toLowerCase().includes(field.toLowerCase()))) { result[key] = '[REDACTED]'; } else if (value && typeof value === 'object' && !Array.isArray(value)) { result[key] = redactSensitiveFields(value, fields); } else { result[key] = value; } } return result; }