diff --git a/Home.md b/Home.md index f9abf58..034fe48 100644 --- a/Home.md +++ b/Home.md @@ -36,7 +36,7 @@ Wenn du neu im Projekt bist, lies die Seiten am besten in dieser Reihenfolge: 5. [Projektstruktur und Best Practices](Projektstruktur-und-Best-Practices.-) 6. [Gitea-Workflow](Gitea-Workflow.-) 7. [Coding-Guidelines-PHP](Coding-Guidelines.-) -8. [[Coding-Guidelines-C#]] (sofern wir C# benutzen wollen) +8. [Logging und Fehlerbehandlung (WICHTIG)](Logging-Service.-) --- diff --git a/Logging-Service.-.md b/Logging-Service.-.md new file mode 100644 index 0000000..e356112 --- /dev/null +++ b/Logging-Service.-.md @@ -0,0 +1,410 @@ + +# Technische Umsetzung: Logging-Service + +Dieses Kapitel beschreibt die konkrete Implementierung des Logging-Konzepts in der PHP-Anwendung. +Ziel ist, dass jedes Teammitglied genau weiß: + +- wo der Logging-Service liegt, +- wie er konfiguriert wird, +- wie er in Controllern und Services verwendet wird, +- welche Regeln beim Loggen gelten. + +Die Inhalte bauen direkt auf der Seite **„Fehlerbehandlung und Logging“** auf und setzen die dort beschriebenen Grundprinzipien praktisch um. + +--- + +## 1. Überblick + +### 1.1 Ziel + +- Benutzerfreundliche, neutrale Fehlermeldungen im Frontend. +- Detaillierte technische Informationen in Logdateien. +- Einheitliches Vorgehen in allen Controllern und Services. + +### 1.2 Position im Projekt + +Der Logging-Service ist eine eigene Klasse im `app/`-Bereich: + +```text +app/ +├── Controllers/ +├── Models/ +└── Services/ + ├── Ldap/ + ├── Powershell/ + ├── Snmp/ + └── Logging/ + └── LoggingService.php +``` + +Die Konfiguration erfolgt zentral über `config/config.php`. +Die Logdatei liegt aktuell unter: + +```text +public/logs/app.log +``` + +> Hinweis: In der ursprünglichen Projektstruktur war `storage/logs` vorgesehen. +> Für das aktuelle Setup wird `public/logs` verwendet. +> Auf dem IIS-Server muss das Verzeichnis so konfiguriert werden, dass es zwar für die Anwendung schreibbar ist, aber nicht direkt per URL im Browser geöffnet werden kann (z. B. über Web.config-Regeln). + +--- + +## 2. Konfiguration in `config/config.php` + +Damit der Logging-Service funktioniert, wird im Config-Array ein eigener Abschnitt `logging` verwendet: + +```php + [ + // ... + ], + + 'security' => [ + // ... + ], + + 'snmp' => [ + // ... + ], + + // Logging-Konfiguration + 'logging' => [ + // Verzeichnis für Logdateien + 'log_dir' => __DIR__ . '/../public/logs', + // Dateiname der Logdatei + 'log_file' => 'app.log', + // Minimale Log-Stufe: debug, info, warning, error + 'min_level' => 'info', + ], +]; +``` + +Wichtige Punkte: + +- `log_dir` zeigt auf das Logverzeichnis. +- `log_file` ist der Dateiname, in den geschrieben wird. +- `min_level` legt fest, ab welchem Level ein Eintrag wirklich ins Log geschrieben wird. + +--- + +## 3. LoggingService – Aufbau und Verhalten + +### 3.1 Pfad und Klasse + +Die Klasse liegt in `app/Services/Logging/LoggingService.php` und verwendet den Namespace `App\Services\Logging`. + +Kernaufgaben: + +- Sicherstellen, dass das Logverzeichnis existiert (`ensureLogDirectoryExists()`). +- Logeinträge in eine Datei schreiben (`log()`). +- Exceptions einheitlich und mit Kontextinformationen protokollieren (`logException()`). + +### 3.2 Öffentliche Methoden + +**Allgemeines Logging:** + +```php +public function log(string $level, string $message, array $context = []): void +``` + +- `level`: `debug`, `info`, `warning` oder `error` +- `message`: kurze Beschreibung +- `context`: zusätzliche Informationen (z. B. `route`, `username`, `remote_addr`) + +**Exception-Logging:** + +```php +public function logException(string $message, \Throwable $exception, array $context = []): void +``` + +- `message`: allgemeiner Kontext (z. B. „Fehler beim Laden von Benutzern.“) +- `exception`: Original-Exception +- `context`: zusätzliche strukturierte Daten + +Beide Methoden kümmern sich darum, dass: + +- der Zeitstempel gesetzt wird, +- das Level formatiert wird, +- Kontextdaten als JSON in die Logdatei geschrieben werden. + +Beispiel für einen Logeintrag: + +```text +[2025-12-04 20:13:12] ERROR Technischer Fehler bei der Anmeldung. {"route":"login.submit","username":"j.doe","remote_addr":"192.168.50.10","exception_class":"RuntimeException","exception_message":"Bind failed", ...} +``` + +--- + +## 4. Globaler Logger in `index.php` + +### 4.1 Initialisierung + +Im Front-Controller (`public/index.php`) wird ein globaler Logger aus der Konfiguration erstellt: + +```php +use App\Services\Logging\LoggingService; + +// ... + +/** @var array $config */ +$config = require $configPath; + +$globalLogger = new LoggingService($config['logging'] ?? []); +``` + +### 4.2 Globale Error- und Exception-Handler + +PHP-Fehler und unbehandelte Exceptions werden zentral geloggt: + +```php +set_error_handler( + static function ( + int $severity, + string $message, + string $file = '', + int $line = 0 + ) use ($globalLogger): bool { + if ((error_reporting() & $severity) === 0) { + return false; + } + + $globalLogger->log( + 'error', + 'PHP-Fehler: ' . $message, + [ + 'severity' => $severity, + 'file' => $file, + 'line' => $line, + ] + ); + + return false; + } +); + +set_exception_handler( + static function (\Throwable $exception) use ($globalLogger): void { + $globalLogger->logException( + 'Unbehandelte Exception in der Anwendung.', + $exception, + [ + 'request_uri' => $_SERVER['REQUEST_URI'] ?? null, + 'route' => $_GET['route'] ?? null, + ] + ); + + http_response_code(500); + echo 'Es ist ein unerwarteter Fehler aufgetreten. ' + . 'Bitte versuchen Sie es später erneut oder wenden Sie sich an den Administrator.'; + } +); +``` + +Effekt: + +- Alles, was **nicht** im Controller oder Service abgefangen wird, landet automatisch im Log. +- Benutzer sehen eine generische, neutrale Fehlermeldung. + +--- + +## 5. Logging im Controller + +### 5.1 Grundprinzip + +Controller: + +- fangen Exceptions aus Services ab, +- loggen die technischen Details über den LoggingService, +- bereiten eine neutrale, fachliche Fehlermeldung für die View vor. + +### 5.2 Muster: Logger im Controller verfügbar machen + +**Schritt 1 – Namespace-Import:** + +```php +use App\Services\Logging\LoggingService; +``` + +**Schritt 2 – Property:** + +```php +private LoggingService $logger; +``` + +**Schritt 3 – Initialisierung im Konstruktor:** + +```php +public function __construct(array $config) +{ + $this->config = $config; + + // Fach-Service initialisieren (Beispiel) + $this->someService = new SomeService($config['some'] ?? []); + + // Logging-Service initialisieren (immer aus $config['logging']) + $this->logger = new LoggingService($config['logging'] ?? []); +} +``` + +### 5.3 Beispiel: AuthController – Fehler beim Login + +```php +public function processLogin(): array +{ + $username = trim($_POST['username'] ?? ''); + $password = (string)($_POST['password'] ?? ''); + + try { + $authenticated = $this->ldapAuthService->authenticate($username, $password); + } catch (\Throwable $exception) { + // Technische Details protokollieren + $this->logger->logException( + 'Technischer Fehler bei der Anmeldung.', + $exception, + [ + 'route' => 'login.submit', + 'username' => $username, + 'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? null, + ] + ); + + // Neutrale Fehlermeldung für die View zurückgeben + return $this->showLoginForm( + 'Technischer Fehler bei der Anmeldung. Bitte versuchen Sie es später erneut ' + . 'oder wenden Sie sich an den Administrator.' + ); + } + + if ($authenticated === false) { + return $this->showLoginForm('Benutzername oder Passwort ist ungültig.'); + } + + // ... erfolgreicher Login (Session setzen, Redirect zurückgeben) ... +} +``` + +Wichtig: + +- **Keine** technischen Details (Exception-Message, Stacktrace) an die View geben. +- Username kann im Kontext geloggt werden, Passwörter niemals. + +### 5.4 Beispiel: UserManagementController – Fehler beim Laden von Benutzern + +```php +public function show(): array +{ + $error = null; + $users = []; + $groups = []; + + try { + $users = $this->directoryService->getUsers(); + $groups = $this->directoryService->getGroups(); + } catch (\Throwable $exception) { + $this->logger->logException( + 'Fehler beim Laden von Benutzern/Gruppen.', + $exception, + [ + 'route' => 'users', + 'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? null, + ] + ); + + $error = 'Fehler beim Laden von Benutzern/Gruppen. ' + . 'Bitte versuchen Sie es später erneut oder wenden Sie sich an den Administrator.'; + } + + // View-Result wie gehabt zurückgeben +} +``` + +--- + +## 6. Logging in Services (optional) + +Grundregel aus der bestehenden Architektur: + +- Controller sind die erste Stelle für Fehlerbehandlung + Logging. +- Services dürfen Exceptions werfen; sie müssen nicht alles selbst loggen. + +Wenn ein Service zusätzlich loggen soll (z. B. für Ablaufprotokolle), gibt es zwei Möglichkeiten: + +1. **Konstruktor-Injection des LoggingService:** + + ```php + use App\Services\Logging\LoggingService; + + class LdapDirectoryService + { + private LoggingService $logger; + + public function __construct(array $config, LoggingService $logger) + { + $this->config = $config; + $this->logger = $logger; + } + + public function getUsers(): array + { + $this->logger->log('info', 'Lade Benutzerliste aus LDAP.', [ + 'component' => 'LdapDirectoryService', + ]); + + // ... + } + } + ``` + + In diesem Fall übergibt der Controller beim Erzeugen des Services einfach seinen `LoggingService` mit. + +2. **Nur Controller loggen, Services werfen Exceptions** (empfohlener Standard): + + - Service konzentriert sich auf Fachlogik und wirft bei Fehlern Exceptions. + - Controller fängt diese ab, loggt sie und bereitet die Meldung für die View auf. + +Welche Variante verwendet wird, hängt von der Komplexität des Services ab. +Für die aktuelle Version des Projekts ist Variante 2 der Standard. + +--- + +## 7. Best Practices + +- **Keine Zugangsdaten loggen** + Passwörter, Tokens, Connection-Strings usw. werden niemals in Klartext ins Log geschrieben. + +- **Kontext nutzen** + `route`, `username`, `remote_addr`, `component`, `server` etc. helfen bei der Fehlersuche. + Diese Werte dürfen (soweit unkritisch) im Kontext stehen. + +- **Log-Level sinnvoll wählen** + + - `debug`: nur in der Entwicklung (kann über `min_level` deaktiviert werden) + - `info`: normale Abläufe (z. B. „Benutzer X hat sich angemeldet“ – nur wenn gewünscht) + - `warning`: auffällige, aber nicht kritische Situationen + - `error`: Exceptions und wirklich unerwartete Fehler + +- **Fehler nicht doppelt loggen** + Entweder Controller loggt gezielt oder sie werden vom globalen Handler in `index.php` abgefangen. + Kritische Stellen (Login, AD-Zugriffe, SNMP-Status) sollten allerdings bewusst im Controller geloggt werden, damit der Kontext vollständig ist. + +--- + +## 8. Kurzanleitung für neue Controller + +Bei neuen Controllern reicht folgende Checkliste: + +1. `use App\Services\Logging\LoggingService;` ergänzen. +2. `private LoggingService $logger;` als Property anlegen. +3. Im Konstruktor: + `$this->logger = new LoggingService($config['logging'] ?? []);` +4. Alle Aufrufe von Services, die externe Systeme nutzen (LDAP, SNMP, PowerShell etc.), in `try/catch` kapseln. +5. Im `catch`: + - `$this->logger->logException('Kontexttext', $exception, [...])` aufrufen. + - Eine neutrale Fehlermeldung für die View zurückgeben. +6. Keine technischen Details an den Benutzer ausgeben. + +Damit ist der Logging-Service im gesamten Projekt einheitlich angebunden und alle Entwickler wissen, wie sie ihn korrekt einsetzen.