/** * Centralized logging utility for Brainy * Provides configurable log levels and consistent logging across the codebase * Automatically reduces logging in production environments to minimize costs */ import { isProductionEnvironment, getLogLevel } from './environment.js' export enum LogLevel { SILENT = -1, // New: Completely silent mode ERROR = 0, WARN = 1, INFO = 2, DEBUG = 3, TRACE = 4 } export interface LoggerConfig { level: LogLevel // Specific module log levels modules?: { [moduleName: string]: LogLevel } // Whether to include timestamps timestamps?: boolean // Whether to include module names includeModule?: boolean // Custom log handler handler?: (level: LogLevel, module: string, message: string, ...args: any[]) => void } class Logger { private static instance: Logger private config: LoggerConfig = { level: LogLevel.ERROR, // Default to ERROR in production for cost optimization timestamps: false, // Disable timestamps in production to reduce log size includeModule: true } private constructor() { // Auto-detect production environment and set appropriate defaults this.applyEnvironmentDefaults() // Set log level from environment variable if available (overrides auto-detection) const envLogLevel = process.env.BRAINY_LOG_LEVEL if (envLogLevel) { const level = LogLevel[envLogLevel.toUpperCase() as keyof typeof LogLevel] if (level !== undefined) { this.config.level = level } } // Parse module-specific log levels const moduleLogLevels = process.env.BRAINY_MODULE_LOG_LEVELS if (moduleLogLevels) { try { this.config.modules = JSON.parse(moduleLogLevels) } catch (e) { // Ignore parsing errors } } } private applyEnvironmentDefaults(): void { const envLogLevel = getLogLevel() // Convert environment log level to Logger LogLevel switch (envLogLevel) { case 'silent': this.config.level = LogLevel.SILENT break case 'error': this.config.level = LogLevel.ERROR this.config.timestamps = false // Minimize log size in production break case 'warn': this.config.level = LogLevel.WARN this.config.timestamps = false break case 'info': this.config.level = LogLevel.INFO this.config.timestamps = true break case 'verbose': this.config.level = LogLevel.DEBUG this.config.timestamps = true break } // In production environments, be extra conservative to minimize costs if (isProductionEnvironment()) { this.config.level = Math.min(this.config.level, LogLevel.ERROR) this.config.timestamps = false this.config.includeModule = false // Reduce log size } } static getInstance(): Logger { if (!Logger.instance) { Logger.instance = new Logger() } return Logger.instance } configure(config: Partial): void { this.config = { ...this.config, ...config } } private shouldLog(level: LogLevel, module: string): boolean { // Silent mode - never log anything if (this.config.level === LogLevel.SILENT) { return false } // Check module-specific level first if (this.config.modules && this.config.modules[module] !== undefined) { return level <= this.config.modules[module] } // Otherwise use global level return level <= this.config.level } private formatMessage(level: LogLevel, module: string, message: string): string { const parts: string[] = [] if (this.config.timestamps) { parts.push(`[${new Date().toISOString()}]`) } parts.push(`[${LogLevel[level]}]`) if (this.config.includeModule) { parts.push(`[${module}]`) } parts.push(message) return parts.join(' ') } private log(level: LogLevel, module: string, message: string, ...args: any[]): void { if (!this.shouldLog(level, module)) { return } if (this.config.handler) { this.config.handler(level, module, message, ...args) return } const formattedMessage = this.formatMessage(level, module, message) switch (level) { case LogLevel.ERROR: console.error(formattedMessage, ...args) break case LogLevel.WARN: console.warn(formattedMessage, ...args) break case LogLevel.INFO: console.info(formattedMessage, ...args) break case LogLevel.DEBUG: case LogLevel.TRACE: console.log(formattedMessage, ...args) break } } error(module: string, message: string, ...args: any[]): void { this.log(LogLevel.ERROR, module, message, ...args) } warn(module: string, message: string, ...args: any[]): void { this.log(LogLevel.WARN, module, message, ...args) } info(module: string, message: string, ...args: any[]): void { this.log(LogLevel.INFO, module, message, ...args) } debug(module: string, message: string, ...args: any[]): void { this.log(LogLevel.DEBUG, module, message, ...args) } trace(module: string, message: string, ...args: any[]): void { this.log(LogLevel.TRACE, module, message, ...args) } // Create a module-specific logger createModuleLogger(module: string) { return { error: (message: string, ...args: any[]) => this.error(module, message, ...args), warn: (message: string, ...args: any[]) => this.warn(module, message, ...args), info: (message: string, ...args: any[]) => this.info(module, message, ...args), debug: (message: string, ...args: any[]) => this.debug(module, message, ...args), trace: (message: string, ...args: any[]) => this.trace(module, message, ...args) } } } // Export singleton instance export const logger = Logger.getInstance() // Export convenience function for creating module loggers export function createModuleLogger(module: string) { return logger.createModuleLogger(module) } // Export function to configure logger export function configureLogger(config: Partial) { logger.configure(config) } /** * Smart console replacement that automatically reduces logging in production * Dramatically reduces Google Cloud Run logging costs * * Usage: Replace console.log with smartConsole.log, etc. */ export const smartConsole = { log: (message?: any, ...args: any[]) => { if (logger['shouldLog'](LogLevel.INFO, 'console')) { console.log(message, ...args) } }, info: (message?: any, ...args: any[]) => { if (logger['shouldLog'](LogLevel.INFO, 'console')) { console.info(message, ...args) } }, warn: (message?: any, ...args: any[]) => { if (logger['shouldLog'](LogLevel.WARN, 'console')) { console.warn(message, ...args) } }, error: (message?: any, ...args: any[]) => { if (logger['shouldLog'](LogLevel.ERROR, 'console')) { console.error(message, ...args) } }, debug: (message?: any, ...args: any[]) => { if (logger['shouldLog'](LogLevel.DEBUG, 'console')) { console.debug(message, ...args) } }, trace: (message?: any, ...args: any[]) => { if (logger['shouldLog'](LogLevel.TRACE, 'console')) { console.trace(message, ...args) } } } /** * Production-optimized logging functions * These only log in non-production environments or when explicitly enabled */ export const prodLog = { // Only log errors in production (always visible) error: (message?: any, ...args: any[]) => { console.error(message, ...args) }, // These are suppressed in production unless BRAINY_LOG_LEVEL is set warn: (message?: any, ...args: any[]) => smartConsole.warn(message, ...args), info: (message?: any, ...args: any[]) => smartConsole.info(message, ...args), debug: (message?: any, ...args: any[]) => smartConsole.debug(message, ...args), log: (message?: any, ...args: any[]) => smartConsole.log(message, ...args) }