1 Logging-Service
blaerf edited this page 2025-12-05 10:02:42 +01:00
This file contains ambiguous Unicode characters

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/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
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:

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:

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:

[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:

  1. 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 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.