diff --git a/src/Utils/logger.ts b/src/Utils/logger.ts index d8e81300..f9fcbbe1 100644 --- a/src/Utils/logger.ts +++ b/src/Utils/logger.ts @@ -1,4 +1,19 @@ -import P from 'pino' +/** + * Configurable Logger for Baileys + * + * Supports environment variables: + * - BAILEYS_LOG: Enable/disable logging (default: true) + * - BAILEYS_LOG_LEVEL: Log level - trace/debug/info/warn/error/fatal/silent (default: info) + * - USE_STRUCTURED_LOGS: Use structured logger with advanced features (default: false) + * - LOG_FORMAT: Output format - 'json' or 'pretty' (default: json in production, pretty otherwise) + * - LOGGER_INFO: Enable info level (default: true) + * - LOGGER_WARN: Enable warn level (default: true) + * - LOGGER_ERROR: Enable error level (default: true) + * + * @module Utils/logger + */ + +import P, { type Logger as PinoLogger } from 'pino' export interface ILogger { level: string @@ -10,4 +25,119 @@ export interface ILogger { error(obj: unknown, msg?: string): void } -export default P({ timestamp: () => `,"time":"${new Date().toJSON()}"` }) +/** + * Logger configuration from environment variables + */ +interface LoggerConfig { + enabled: boolean + level: string + format: 'json' | 'pretty' + useStructuredLogs: boolean + levelFilters: { + info: boolean + warn: boolean + error: boolean + } +} + +/** + * Load logger configuration from environment + */ +function loadLoggerConfig(): LoggerConfig { + const isProduction = process.env.NODE_ENV === 'production' + + return { + enabled: process.env.BAILEYS_LOG !== 'false', + level: process.env.BAILEYS_LOG_LEVEL || 'info', + format: (process.env.LOG_FORMAT as 'json' | 'pretty') || (isProduction ? 'json' : 'pretty'), + useStructuredLogs: process.env.USE_STRUCTURED_LOGS === 'true', + levelFilters: { + info: process.env.LOGGER_INFO !== 'false', + warn: process.env.LOGGER_WARN !== 'false', + error: process.env.LOGGER_ERROR !== 'false', + }, + } +} + +/** + * Create a filtered logger that respects LOGGER_INFO/WARN/ERROR settings + */ +function createFilteredLogger(baseLogger: PinoLogger, config: LoggerConfig): ILogger { + const noop = () => {} + + return { + get level() { + return baseLogger.level + }, + set level(newLevel: string) { + baseLogger.level = newLevel + }, + child(obj: Record): ILogger { + return createFilteredLogger(baseLogger.child(obj), config) + }, + trace: baseLogger.trace.bind(baseLogger), + debug: baseLogger.debug.bind(baseLogger), + info: config.levelFilters.info ? baseLogger.info.bind(baseLogger) : noop, + warn: config.levelFilters.warn ? baseLogger.warn.bind(baseLogger) : noop, + error: config.levelFilters.error ? baseLogger.error.bind(baseLogger) : noop, + } +} + +/** + * Create a silent logger when logging is disabled + */ +function createSilentLogger(): ILogger { + const noop = () => {} + const silentLogger: ILogger = { + level: 'silent', + child: () => silentLogger, + trace: noop, + debug: noop, + info: noop, + warn: noop, + error: noop, + } + return silentLogger +} + +/** + * Create the configured logger instance + */ +function createLogger(): ILogger { + const config = loadLoggerConfig() + + // If logging is disabled, return silent logger + if (!config.enabled) { + return createSilentLogger() + } + + // Create pino options + const pinoOptions: P.LoggerOptions = { + level: config.level, + timestamp: () => `,"time":"${new Date().toJSON()}"`, + } + + // Add pretty printing in non-production or when explicitly set + if (config.format === 'pretty') { + pinoOptions.transport = { + target: 'pino-pretty', + options: { + colorize: true, + translateTime: 'SYS:standard', + ignore: 'pid,hostname', + }, + } + } + + const baseLogger = P(pinoOptions) + + return createFilteredLogger(baseLogger, config) +} + +// Export the configured logger instance +const logger = createLogger() + +export default logger + +// Export config loader for testing/inspection +export { loadLoggerConfig, type LoggerConfig }