1, 'INFO' => 2, 'WARNING' => 3, 'ERROR' => 4, 'CRITICAL' => 5 ]; // Current log level - can be changed through configuration private static function getLogLevel() { $configLevel = Configuration::get('ECOMZONE_LOG_LEVEL', 'INFO'); return isset(self::LEVELS[$configLevel]) ? $configLevel : 'INFO'; } public static function log($message, $level = 'INFO', $context = []) { try { // Check if we should log this level $logLevel = self::getLogLevel(); if (self::LEVELS[$level] < self::LEVELS[$logLevel]) { return true; // Skip logging for less severe levels } // Ensure the log directory exists $logDir = _PS_MODULE_DIR_ . 'ecomzone/log'; if (!is_dir($logDir)) { if (!@mkdir($logDir, 0755, true)) { throw new Exception("Failed to create log directory: " . $logDir); } @chmod($logDir, 0755); // Ensure the directory is writable } // Format the log entry $logEntry = [ 'timestamp' => date('Y-m-d H:i:s'), 'level' => strtoupper($level), 'message' => $message, 'context' => $context ]; // For errors and critical issues, also include a stack trace if (in_array($level, ['ERROR', 'CRITICAL']) && !isset($context['trace'])) { $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); $logEntry['trace'] = array_slice($trace, 1); // Skip the log function itself } $logLine = json_encode($logEntry) . PHP_EOL; // Rotate log file if it's too large (> 10MB) $logFile = $logDir . '/' . self::LOG_FILE; if (file_exists($logFile) && filesize($logFile) > 10 * 1024 * 1024) { self::rotateLogFile($logFile); } // Write to log file with exclusive lock if (!@file_put_contents($logFile, $logLine, FILE_APPEND | LOCK_EX)) { throw new Exception("Failed to write to log file: " . $logFile); } // Also write to PrestaShop log for error/critical levels if (in_array($level, ['ERROR', 'CRITICAL'])) { $psLogLevel = ($level === 'ERROR') ? 3 : 4; $contextStr = json_encode($context); PrestaShopLogger::addLog( "EcomZone: {$message} - Context: {$contextStr}", $psLogLevel, null, 'EcomZone' ); } return true; } catch (Exception $e) { // Last resort logging to PHP error log error_log('EcomZone logging error: ' . $e->getMessage() . '. Original message: ' . $message); // Try to log to PrestaShop's log try { PrestaShopLogger::addLog( 'EcomZone logging error: ' . $e->getMessage() . '. Original message: ' . $message, 3, // Error level null, 'EcomZone' ); } catch (Exception $innerEx) { error_log('Failed to log to PrestaShop: ' . $innerEx->getMessage()); } return false; } } private static function rotateLogFile($logFile) { try { $backup = $logFile . '.' . date('Y-m-d-H-i-s') . '.bak'; if (!@rename($logFile, $backup)) { throw new Exception("Failed to rotate log file"); } // Keep only last 5 backup files $backups = glob($logFile . '.*.bak'); if (count($backups) > 5) { usort($backups, function($a, $b) { return filemtime($b) - filemtime($a); }); $toDelete = array_slice($backups, 5); foreach ($toDelete as $file) { @unlink($file); } } return true; } catch (Exception $e) { error_log('EcomZone log rotation error: ' . $e->getMessage()); return false; } } public static function getLogPath() { // When running in standalone mode, use the local directory if (!defined('_PS_MODULE_DIR_') || !file_exists(_PS_MODULE_DIR_ . 'ecomzone')) { return __DIR__ . '/../log/' . self::LOG_FILE; } return _PS_MODULE_DIR_ . 'ecomzone/log/' . self::LOG_FILE; } public static function getLogContents($maxLines = 100) { $logFile = self::getLogPath(); if (!file_exists($logFile)) { return []; } $logs = []; $lines = array_reverse(file($logFile)); $count = 0; foreach ($lines as $line) { if ($count >= $maxLines) break; $entry = json_decode($line, true); if ($entry) { $logs[] = $entry; $count++; } } return $logs; } public static function clearLogs() { $logFile = self::getLogPath(); if (file_exists($logFile)) { @unlink($logFile); self::log("Log file cleared", "INFO"); } return true; } }