Table of Contents
- Technische Umsetzung: Logging-Service
- 1. Überblick
- 2. Konfiguration in config/config.php
- 3. LoggingService – Aufbau und Verhalten
- 4. Globaler Logger in index.php
- 5. Logging im Controller
- 5.1 Grundprinzip
- 5.2 Muster: Logger im Controller verfügbar machen
- 5.3 Beispiel: AuthController – Fehler beim Login
- 5.4 Beispiel: UserManagementController – Fehler beim Laden von Benutzern
- 6. Logging in Services (optional)
- 7. Best Practices
- 8. Kurzanleitung für neue Controller
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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:
app/
├── Controllers/
├── Models/
└── Services/
├── Ldap/
├── Powershell/
├── Snmp/
└── Logging/
└── LoggingService.php
Die Konfiguration erfolgt zentral über config/config.php.
Die Logdatei liegt aktuell unter:
public/logs/app.log
Hinweis: In der ursprünglichen Projektstruktur war
storage/logsvorgesehen.
Für das aktuelle Setup wirdpublic/logsverwendet.
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
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_dirzeigt auf das Logverzeichnis.log_fileist der Dateiname, in den geschrieben wird.min_levellegt 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:
public function log(string $level, string $message, array $context = []): void
level:debug,info,warningodererrormessage: kurze Beschreibungcontext: zusätzliche Informationen (z. B.route,username,remote_addr)
Exception-Logging:
public function logException(string $message, \Throwable $exception, array $context = []): void
message: allgemeiner Kontext (z. B. „Fehler beim Laden von Benutzern.“)exception: Original-Exceptioncontext: 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:
[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:
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:
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:
use App\Services\Logging\LoggingService;
Schritt 2 – Property:
private LoggingService $logger;
Schritt 3 – Initialisierung im Konstruktor:
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
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
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:
-
Konstruktor-Injection des LoggingService:
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
LoggingServicemit. -
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,serveretc. helfen bei der Fehlersuche.
Diese Werte dürfen (soweit unkritisch) im Kontext stehen. -
Log-Level sinnvoll wählen
debug: nur in der Entwicklung (kann übermin_leveldeaktiviert werden)info: normale Abläufe (z. B. „Benutzer X hat sich angemeldet“ – nur wenn gewünscht)warning: auffällige, aber nicht kritische Situationenerror: Exceptions und wirklich unerwartete Fehler
-
Fehler nicht doppelt loggen
Entweder Controller loggt gezielt oder sie werden vom globalen Handler inindex.phpabgefangen.
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:
use App\Services\Logging\LoggingService;ergänzen.private LoggingService $logger;als Property anlegen.- Im Konstruktor:
$this->logger = new LoggingService($config['logging'] ?? []); - Alle Aufrufe von Services, die externe Systeme nutzen (LDAP, SNMP, PowerShell etc.), in
try/catchkapseln. - Im
catch:$this->logger->logException('Kontexttext', $exception, [...])aufrufen.- Eine neutrale Fehlermeldung für die View zurückgeben.
- 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.