true, // Usar archivos rotativos 'max_files' => 14, // Mantener últimos 14 días 'json' => false, // Formato JSON (útil para parseo) 'include_context' => true, // Incluir información de contexto 'level' => self::$defaultLevel ]; $options = array_merge($defaultOptions, $options); // Handler para archivo if ($options['rotating']) { $handler = new RotatingFileHandler( self::$logPath . '/' . $channel . '.log', $options['max_files'], $options['level'] ); } else { $handler = new StreamHandler( self::$logPath . '/' . $channel . '.log', $options['level'] ); } // Formatter if ($options['json']) { $formatter = new JsonFormatter(); } else { $formatter = new LineFormatter( "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", "Y-m-d H:i:s", true, true ); } $handler->setFormatter($formatter); $logger->pushHandler($handler); // Handler adicional para errores críticos (siempre a error_log de PHP) $errorLogHandler = new ErrorLogHandler( ErrorLogHandler::OPERATING_SYSTEM, Logger::ERROR ); $logger->pushHandler($errorLogHandler); // Processors (agregar contexto automático) if ($options['include_context']) { // Agregar información de la petición web $logger->pushProcessor(new WebProcessor()); // Agregar información de archivo/línea donde se generó el log $logger->pushProcessor(new IntrospectionProcessor( Logger::DEBUG, ['Monolog\\', 'LoggerFactory'] )); // Agregar PID y memoria $logger->pushProcessor(function ($record) { $record['extra']['pid'] = getmypid(); $record['extra']['memory_mb'] = round(memory_get_usage(true) / 1024 / 1024, 2); return $record; }); } // Guardar logger en caché self::$loggers[$channel] = $logger; return $logger; } /** * Loggers predefinidos para componentes principales */ public static function webhook(): Logger { return self::getLogger('webhook', [ 'rotating' => true, 'max_files' => 7, 'level' => Logger::INFO ]); } public static function worker(): Logger { return self::getLogger('worker', [ 'rotating' => true, 'max_files' => 7, 'level' => Logger::DEBUG ]); } public static function bot(): Logger { return self::getLogger('bot', [ 'rotating' => true, 'max_files' => 14, 'level' => Logger::INFO ]); } public static function api(): Logger { return self::getLogger('api', [ 'rotating' => true, 'max_files' => 7, 'level' => Logger::INFO ]); } public static function whatsapp(): Logger { return self::getLogger('whatsapp', [ 'rotating' => true, 'max_files' => 7, 'level' => Logger::INFO ]); } public static function queue(): Logger { return self::getLogger('queue', [ 'rotating' => true, 'max_files' => 7, 'level' => Logger::DEBUG ]); } public static function security(): Logger { return self::getLogger('security', [ 'rotating' => true, 'max_files' => 30, // Mantener logs de seguridad por más tiempo 'level' => Logger::WARNING ]); } /** * Logger general de aplicación */ public static function app(): Logger { return self::getLogger('app', [ 'rotating' => true, 'max_files' => 14, 'level' => Logger::INFO ]); } /** * Limpiar logs antiguos (ejecutar con cron) */ public static function cleanup(int $daysToKeep = 30): int { $cleaned = 0; $cutoff = time() - ($daysToKeep * 86400); if (!is_dir(self::$logPath)) { return 0; } $files = glob(self::$logPath . '/*.log*'); foreach ($files as $file) { if (is_file($file) && filemtime($file) < $cutoff) { if (unlink($file)) { $cleaned++; } } } return $cleaned; } /** * Obtener estadísticas de logs */ public static function getStats(): array { if (!is_dir(self::$logPath)) { return ['error' => 'Log directory not found']; } $stats = [ 'total_files' => 0, 'total_size_mb' => 0, 'by_channel' => [] ]; $files = glob(self::$logPath . '/*.log*'); foreach ($files as $file) { if (!is_file($file)) continue; $stats['total_files']++; $size = filesize($file); $stats['total_size_mb'] += $size / 1024 / 1024; // Extraer canal del nombre de archivo $basename = basename($file); preg_match('/^([^-\.]+)/', $basename, $matches); $channel = $matches[1] ?? 'unknown'; if (!isset($stats['by_channel'][$channel])) { $stats['by_channel'][$channel] = [ 'files' => 0, 'size_mb' => 0 ]; } $stats['by_channel'][$channel]['files']++; $stats['by_channel'][$channel]['size_mb'] += $size / 1024 / 1024; } // Redondear tamaños $stats['total_size_mb'] = round($stats['total_size_mb'], 2); foreach ($stats['by_channel'] as $channel => $data) { $stats['by_channel'][$channel]['size_mb'] = round($data['size_mb'], 2); } return $stats; } } /** * Función helper global para logging rápido * Mantiene compatibilidad con writeLog() existente */ if (!function_exists('writeLog')) { function writeLog(string $level, string $message, array $context = [], string $channel = 'app') { try { $logger = LoggerFactory::getLogger($channel); $level = strtolower($level); switch ($level) { case 'debug': $logger->debug($message, $context); break; case 'info': $logger->info($message, $context); break; case 'notice': $logger->notice($message, $context); break; case 'warning': case 'warn': $logger->warning($message, $context); break; case 'error': $logger->error($message, $context); break; case 'critical': $logger->critical($message, $context); break; case 'alert': $logger->alert($message, $context); break; case 'emergency': $logger->emergency($message, $context); break; default: $logger->info($message, $context); } } catch (Exception $e) { // Fallback a error_log nativo de PHP error_log("[$level] $message " . json_encode($context)); } } } // Inicializar al cargar el archivo LoggerFactory::init();