799 lines
21 KiB
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();
|
|
}
|
|
}
|