0, 'alert' => 1, 'critical' => 2, 'error' => 3, 'warning' => 4, 'notice' => 5, 'info' => 6, 'debug' => 7 ]; private function __construct() { $this->initializeHandlers(); } /** * Singleton-Instanz abrufen */ public static function getInstance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } /** * Log-Handler initialisieren */ private function initializeHandlers() { // File-Handler $this->handlers['file'] = new FileLogHandler(); // Database-Handler $this->handlers['database'] = new DatabaseLogHandler(); // Email-Handler (für kritische Fehler) $this->handlers['email'] = new EmailLogHandler(); // Syslog-Handler $this->handlers['syslog'] = new SyslogHandler(); } /** * Emergency-Level loggen */ public function emergency($message, array $context = []) { $this->log('emergency', $message, $context); } /** * Alert-Level loggen */ public function alert($message, array $context = []) { $this->log('alert', $message, $context); } /** * Critical-Level loggen */ public function critical($message, array $context = []) { $this->log('critical', $message, $context); } /** * Error-Level loggen */ public function error($message, array $context = []) { $this->log('error', $message, $context); } /** * Warning-Level loggen */ public function warning($message, array $context = []) { $this->log('warning', $message, $context); } /** * Notice-Level loggen */ public function notice($message, array $context = []) { $this->log('notice', $message, $context); } /** * Info-Level loggen */ public function info($message, array $context = []) { $this->log('info', $message, $context); } /** * Debug-Level loggen */ public function debug($message, array $context = []) { $this->log('debug', $message, $context); } /** * Log-Eintrag erstellen */ public function log($level, $message, array $context = []) { if (!$this->enabled || !$this->shouldLog($level)) { return; } $logEntry = [ 'level' => $level, 'message' => $message, 'context' => $context, 'timestamp' => time(), 'datetime' => date('Y-m-d H:i:s'), 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown', 'request_uri' => $_SERVER['REQUEST_URI'] ?? 'unknown', 'user_id' => $this->getCurrentUserId(), 'session_id' => session_id() ?: 'unknown' ]; // Log an alle Handler senden foreach ($this->handlers as $handler) { try { $handler->handle($logEntry); } catch (\Exception $e) { // Handler-Fehler nicht loggen um Endlosschleife zu vermeiden error_log('Logger-Handler Fehler: ' . $e->getMessage()); } } } /** * Prüfen ob Level geloggt werden soll */ private function shouldLog($level) { return isset($this->logLevels[$level]) && $this->logLevels[$level] <= $this->logLevels[$this->logLevel]; } /** * Aktuelle User-ID abrufen */ private function getCurrentUserId() { if (isset($_SESSION['user_id'])) { return $_SESSION['user_id']; } if (isset($_SESSION['employee_id'])) { return 'employee_' . $_SESSION['employee_id']; } return null; } /** * Log-Level setzen */ public function setLogLevel($level) { if (isset($this->logLevels[$level])) { $this->logLevel = $level; } return $this; } /** * Log-Level abrufen */ public function getLogLevel() { return $this->logLevel; } /** * Logger aktivieren/deaktivieren */ public function setEnabled($enabled) { $this->enabled = $enabled; return $this; } /** * Logger-Status prüfen */ public function isEnabled() { return $this->enabled; } /** * Log-Statistiken abrufen */ public function getStatistics($days = 7) { $stats = []; foreach ($this->handlers as $name => $handler) { $stats[$name] = $handler->getStatistics($days); } return $stats; } /** * Log-Einträge abrufen */ public function getLogs($level = null, $limit = 100, $offset = 0) { $logs = []; foreach ($this->handlers as $name => $handler) { $logs[$name] = $handler->getLogs($level, $limit, $offset); } return $logs; } /** * Logs löschen */ public function clearLogs($level = null, $days = null) { foreach ($this->handlers as $handler) { $handler->clearLogs($level, $days); } return $this; } /** * Log-Rotation */ public function rotateLogs() { foreach ($this->handlers as $handler) { $handler->rotate(); } return $this; } /** * Exception loggen */ public function logException(\Exception $exception, array $context = []) { $context['exception'] = [ 'class' => get_class($exception), 'message' => $exception->getMessage(), 'file' => $exception->getFile(), 'line' => $exception->getLine(), 'trace' => $exception->getTraceAsString() ]; $this->error('Exception: ' . $exception->getMessage(), $context); } /** * SQL-Query loggen */ public function logQuery($sql, $params = [], $executionTime = null) { $context = [ 'sql' => $sql, 'params' => $params, 'execution_time' => $executionTime ]; $this->debug('SQL Query', $context); } /** * Performance-Metriken loggen */ public function logPerformance($metric, $value, $unit = 'ms') { $context = [ 'metric' => $metric, 'value' => $value, 'unit' => $unit ]; $this->info('Performance: ' . $metric . ' = ' . $value . ' ' . $unit, $context); } /** * Security-Event loggen */ public function logSecurity($event, $details = []) { $context = array_merge($details, [ 'event' => $event, 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown' ]); $this->warning('Security Event: ' . $event, $context); } /** * User-Action loggen */ public function logUserAction($action, $details = []) { $context = array_merge($details, [ 'action' => $action, 'user_id' => $this->getCurrentUserId() ]); $this->info('User Action: ' . $action, $context); } } /** * File-Log-Handler */ class FileLogHandler { private $logDir; private $maxFileSize = 10485760; // 10MB private $maxFiles = 10; public function __construct() { $this->logDir = __DIR__ . '/../../../logs/'; if (!is_dir($this->logDir)) { mkdir($this->logDir, 0755, true); } } public function handle($logEntry) { $filename = $this->getLogFilename($logEntry['level']); $logLine = $this->formatLogEntry($logEntry); file_put_contents($filename, $logLine . PHP_EOL, FILE_APPEND | LOCK_EX); // Dateigröße prüfen und rotieren falls nötig if (filesize($filename) > $this->maxFileSize) { $this->rotate($logEntry['level']); } } public function getStatistics($days = 7) { $stats = []; $levels = ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug']; foreach ($levels as $level) { $filename = $this->getLogFilename($level); if (file_exists($filename)) { $lines = count(file($filename)); $size = filesize($filename); $stats[$level] = [ 'entries' => $lines, 'size' => $size, 'last_modified' => filemtime($filename) ]; } else { $stats[$level] = [ 'entries' => 0, 'size' => 0, 'last_modified' => null ]; } } return $stats; } public function getLogs($level = null, $limit = 100, $offset = 0) { $logs = []; if ($level) { $filename = $this->getLogFilename($level); if (file_exists($filename)) { $lines = file($filename, FILE_IGNORE_NEW_LINES); $logs = array_slice($lines, $offset, $limit); } } else { $levels = ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug']; foreach ($levels as $level) { $filename = $this->getLogFilename($level); if (file_exists($filename)) { $lines = file($filename, FILE_IGNORE_NEW_LINES); $logs = array_merge($logs, array_slice($lines, 0, $limit)); } } } return $logs; } public function clearLogs($level = null, $days = null) { if ($level) { $filename = $this->getLogFilename($level); if (file_exists($filename)) { unlink($filename); } } else { $levels = ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug']; foreach ($levels as $level) { $filename = $this->getLogFilename($level); if (file_exists($filename)) { unlink($filename); } } } } public function rotate() { $levels = ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug']; foreach ($levels as $level) { $filename = $this->getLogFilename($level); if (file_exists($filename) && filesize($filename) > $this->maxFileSize) { $this->rotateFile($filename); } } } private function getLogFilename($level) { return $this->logDir . $level . '.log'; } private function formatLogEntry($logEntry) { $context = !empty($logEntry['context']) ? ' ' . json_encode($logEntry['context']) : ''; return sprintf( '[%s] [%s] %s%s', $logEntry['datetime'], strtoupper($logEntry['level']), $logEntry['message'], $context ); } private function rotateFile($filename) { for ($i = $this->maxFiles - 1; $i >= 1; $i--) { $oldFile = $filename . '.' . $i; $newFile = $filename . '.' . ($i + 1); if (file_exists($oldFile)) { if ($i == $this->maxFiles - 1) { unlink($oldFile); } else { rename($oldFile, $newFile); } } } if (file_exists($filename)) { rename($filename, $filename . '.1'); } } } /** * Database-Log-Handler */ class DatabaseLogHandler { private $conn; public function __construct() { try { $this->conn = DriverManager::getConnection([ 'url' => getenv('DATABASE_URL') ?: 'mysql://root:password@localhost/webshop' ]); } catch (Exception $e) { error_log('Database-Log-Handler Verbindung fehlgeschlagen: ' . $e->getMessage()); } } public function handle($logEntry) { try { $stmt = $this->conn->prepare(' INSERT INTO ws_logs ( log_level, message, context, timestamp, datetime, ip_address, user_agent, request_uri, user_id, session_id, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) '); $stmt->execute([ $logEntry['level'], $logEntry['message'], json_encode($logEntry['context']), $logEntry['timestamp'], $logEntry['datetime'], $logEntry['ip_address'], $logEntry['user_agent'], $logEntry['request_uri'], $logEntry['user_id'], $logEntry['session_id'] ]); } catch (Exception $e) { error_log('Database-Log-Handler Fehler: ' . $e->getMessage()); } } public function getStatistics($days = 7) { try { $stmt = $this->conn->prepare(' SELECT log_level, COUNT(*) as count, MIN(created_at) as first_entry, MAX(created_at) as last_entry FROM ws_logs WHERE created_at >= DATE_SUB(NOW(), INTERVAL ? DAY) GROUP BY log_level ORDER BY log_level '); $stmt->execute([$days]); $stats = []; while ($row = $stmt->fetchAssociative()) { $stats[$row['log_level']] = [ 'count' => $row['count'], 'first_entry' => $row['first_entry'], 'last_entry' => $row['last_entry'] ]; } return $stats; } catch (Exception $e) { return []; } } public function getLogs($level = null, $limit = 100, $offset = 0) { try { $sql = 'SELECT * FROM ws_logs'; $params = []; if ($level) { $sql .= ' WHERE log_level = ?'; $params[] = $level; } $sql .= ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; $params[] = $limit; $params[] = $offset; $stmt = $this->conn->prepare($sql); $stmt->execute($params); return $stmt->fetchAllAssociative(); } catch (Exception $e) { return []; } } public function clearLogs($level = null, $days = null) { try { $sql = 'DELETE FROM ws_logs'; $params = []; $conditions = []; if ($level) { $conditions[] = 'log_level = ?'; $params[] = $level; } if ($days) { $conditions[] = 'created_at < DATE_SUB(NOW(), INTERVAL ? DAY)'; $params[] = $days; } if (!empty($conditions)) { $sql .= ' WHERE ' . implode(' AND ', $conditions); } $stmt = $this->conn->prepare($sql); $stmt->execute($params); } catch (Exception $e) { error_log('Database-Log-Clear Fehler: ' . $e->getMessage()); } } public function rotate() { // Database-Logs werden automatisch archiviert $this->clearLogs(null, 30); // Logs älter als 30 Tage löschen } } /** * Email-Log-Handler */ class EmailLogHandler { private $emailConfig; public function __construct() { $this->emailConfig = [ 'to' => getenv('LOG_EMAIL_TO') ?: 'admin@webshop.local', 'from' => getenv('LOG_EMAIL_FROM') ?: 'noreply@webshop.local', 'subject_prefix' => '[Webshop Log] ' ]; } public function handle($logEntry) { // Nur kritische Level per E-Mail senden $criticalLevels = ['emergency', 'alert', 'critical']; if (!in_array($logEntry['level'], $criticalLevels)) { return; } $subject = $this->emailConfig['subject_prefix'] . strtoupper($logEntry['level']) . ': ' . $logEntry['message']; $message = $this->formatEmailMessage($logEntry); $headers = [ 'From: ' . $this->emailConfig['from'], 'Content-Type: text/plain; charset=UTF-8' ]; mail($this->emailConfig['to'], $subject, $message, implode("\r\n", $headers)); } public function getStatistics($days = 7) { // Email-Handler hat keine eigenen Statistiken return []; } public function getLogs($level = null, $limit = 100, $offset = 0) { // Email-Handler hat keine eigenen Logs return []; } public function clearLogs($level = null, $days = null) { // Email-Handler hat keine Logs zu löschen } public function rotate() { // Email-Handler benötigt keine Rotation } private function formatEmailMessage($logEntry) { $message = "Log-Eintrag:\n\n"; $message .= "Level: " . strtoupper($logEntry['level']) . "\n"; $message .= "Zeit: " . $logEntry['datetime'] . "\n"; $message .= "Nachricht: " . $logEntry['message'] . "\n"; $message .= "IP-Adresse: " . $logEntry['ip_address'] . "\n"; $message .= "User-Agent: " . $logEntry['user_agent'] . "\n"; $message .= "Request-URI: " . $logEntry['request_uri'] . "\n"; if (!empty($logEntry['context'])) { $message .= "\nKontext:\n" . json_encode($logEntry['context'], JSON_PRETTY_PRINT); } return $message; } } /** * Syslog-Handler */ class SyslogHandler { private $ident; private $facility; public function __construct() { $this->ident = 'webshop'; $this->facility = LOG_LOCAL0; openlog($this->ident, LOG_PID | LOG_PERROR, $this->facility); } public function handle($logEntry) { $priority = $this->getSyslogPriority($logEntry['level']); $message = $this->formatSyslogMessage($logEntry); syslog($priority, $message); } public function getStatistics($days = 7) { // Syslog-Handler hat keine eigenen Statistiken return []; } public function getLogs($level = null, $limit = 100, $offset = 0) { // Syslog-Handler hat keine eigenen Logs return []; } public function clearLogs($level = null, $days = null) { // Syslog-Handler hat keine Logs zu löschen } public function rotate() { // Syslog-Handler benötigt keine Rotation } private function getSyslogPriority($level) { $priorities = [ 'emergency' => LOG_EMERG, 'alert' => LOG_ALERT, 'critical' => LOG_CRIT, 'error' => LOG_ERR, 'warning' => LOG_WARNING, 'notice' => LOG_NOTICE, 'info' => LOG_INFO, 'debug' => LOG_DEBUG ]; return isset($priorities[$level]) ? $priorities[$level] : LOG_INFO; } private function formatSyslogMessage($logEntry) { $context = !empty($logEntry['context']) ? ' ' . json_encode($logEntry['context']) : ''; return sprintf( '[%s] %s%s', strtoupper($logEntry['level']), $logEntry['message'], $context ); } public function __destruct() { closelog(); } }