*/ private const LEVEL_MAP = [ 'debug' => 100, 'info' => 200, 'warning' => 300, 'error' => 400, ]; /** * @param array $config Teilkonfiguration "logging" aus config.php */ public function __construct(array $config) { // Standard: public/logs relativ zum Projektroot $baseDir = $config['log_dir'] ?? (__DIR__ . '/../../../public/logs'); $fileName = $config['log_file'] ?? 'app.log'; $level = strtolower((string)($config['min_level'] ?? 'info')); $this->logDir = rtrim($baseDir, DIRECTORY_SEPARATOR); $this->logFile = $fileName; $this->minLevel = self::LEVEL_MAP[$level] ?? self::LEVEL_MAP['info']; $this->ensureLogDirectoryExists(); } /** * Stellt sicher, dass das Log-Verzeichnis existiert. */ private function ensureLogDirectoryExists(): void { if (is_dir($this->logDir) === true) { return; } if (@mkdir($this->logDir, 0775, true) === false && is_dir($this->logDir) === false) { // Wenn das Anlegen fehlschlägt, wenigstens einen Eintrag im PHP-Error-Log hinterlassen. error_log(sprintf('LoggingService: Konnte Log-Verzeichnis "%s" nicht anlegen.', $this->logDir)); } } /** * Allgemeiner Log-Eintrag. * * @param string $level Log-Level (debug|info|warning|error) * @param string $message Nachrichtentext * @param array $context Zusätzliche Kontextinformationen */ public function log(string $level, string $message, array $context = []): void { $level = strtolower($level); $numericLevel = self::LEVEL_MAP[$level] ?? self::LEVEL_MAP['error']; // Alles unterhalb der minimalen Stufe ignorieren. if ($numericLevel < $this->minLevel) { return; } $timestamp = (new DateTimeImmutable())->format('Y-m-d H:i:s'); $contextJson = $context === [] ? '{}' : (string)json_encode($context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $line = sprintf( "[%s] %-7s %s %s%s", $timestamp, strtoupper($level), $message, $contextJson, PHP_EOL ); $filePath = $this->logDir . DIRECTORY_SEPARATOR . $this->logFile; if (@file_put_contents($filePath, $line, FILE_APPEND | LOCK_EX) === false) { // Fallback, damit Fehler beim Logging selbst nicht die App zerschießen. error_log(sprintf('LoggingService: Konnte in Log-Datei "%s" nicht schreiben.', $filePath)); } } /** * Komfortmethode, um Exceptions strukturiert zu loggen. * * @param string $message Kurzer Kontexttext zur Exception * @param \Throwable $exception Die geworfene Exception * @param array $context Zusätzlicher Kontext (Route, Benutzername, Remote-IP, ...) */ public function logException(string $message, \Throwable $exception, array $context = []): void { $exceptionContext = [ 'exception_class' => get_class($exception), 'exception_message' => $exception->getMessage(), 'file' => $exception->getFile(), 'line' => $exception->getLine(), 'trace' => $exception->getTraceAsString(), ]; $mergedContext = array_merge($context, $exceptionContext); $this->log('error', $message, $mergedContext); } }