Anleitung für Logging-Service

blaerf 2025-12-05 10:02:42 +01:00
parent e76bec80ef
commit 9c424029fc
2 changed files with 411 additions and 1 deletions

@ -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.-)
---

410
Logging-Service.-.md Normal file

@ -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
<?php
declare(strict_types=1);
return [
'ldap' => [
// ...
],
'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<string, mixed> $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.