Newwebshop/app/Core/Logger.php

799 lines
21 KiB
PHP

<?php
/**
* Copyright seit 2024 Webshop System
*
* Logger-System für PrestaShop-Modul-Kompatibilität
*
* @author Webshop System
* @license GPL v3
*/
namespace App\Core;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
class Logger
{
private static $instance = null;
private $handlers = [];
private $enabled = true;
private $logLevel = 'info';
private $logLevels = [
'emergency' => 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();
}
}