Reviewed-on: https://git.eckertplayground.de/taarly/PHP_AdminTool_Projekt/pulls/17 Co-authored-by: blaerf <blaerf@gmx.de> Co-committed-by: blaerf <blaerf@gmx.de>
135 lines
4.3 KiB
PHP
135 lines
4.3 KiB
PHP
<?php
|
|
|
|
// Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren.
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Logging;
|
|
|
|
use DateTimeImmutable;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Einfacher File-Logger für die AdminTool-Anwendung.
|
|
*
|
|
* Ziele:
|
|
* - Technische Details werden in eine Log-Datei unter public/logs/ geschrieben.
|
|
* - In der Weboberfläche erscheinen nur verständliche, fachliche Fehlermeldungen.
|
|
*/
|
|
class LoggingService
|
|
{
|
|
/** @var string Vollständiger Pfad zum Log-Verzeichnis */
|
|
private string $logDir;
|
|
|
|
/** @var string Dateiname der Log-Datei */
|
|
private string $logFile;
|
|
|
|
/** @var int Minimale Log-Stufe, ab der geschrieben wird. */
|
|
private int $minLevel;
|
|
|
|
/**
|
|
* Zuordnung der Log-Level zu numerischen Werten zur Filterung.
|
|
*
|
|
* @var array<string, int>
|
|
*/
|
|
private const array LEVEL_MAP = [
|
|
'debug' => 100,
|
|
'info' => 200,
|
|
'warning' => 300,
|
|
'error' => 400,
|
|
];
|
|
|
|
/**
|
|
* @param array<string, mixed> $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<string, mixed> $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<string, mixed> $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);
|
|
}
|
|
}
|