Gestire gli errori in PHP con logging su syslog

by theArchitect
SHARE
Gestire gli errori in PHP con logging su syslog
© Guida-HTML5.it

Introduzione

Quando un’applicazione PHP cresce, gestire gli errori con semplici echo o var_dump non basta più. In un ambiente reale, soprattutto su server Linux o in contesti con più servizi, è molto più utile inviare gli errori a un sistema di logging centralizzato come syslog. Questo approccio permette di raccogliere messaggi di errore in modo standard, consultarli facilmente e integrarli con gli strumenti di monitoraggio del sistema.

In questo tutorial vedremo come intercettare errori, eccezioni e problemi fatali in PHP e scriverli su syslog. Il vantaggio principale è la separazione tra log applicativi e output dell’applicazione: l’utente finale non vede dettagli tecnici, mentre lo sviluppatore ha comunque accesso a informazioni utili per il debug e la manutenzione.

Il sotto-argomento scelto è pratico perché syslog è molto usato in produzione, ma spesso trascurato da chi si limita al logging su file. Inoltre, imparare a usarlo aiuta a comprendere meglio il flusso di gestione degli errori in PHP.

Codice completo

<?php
declare(strict_types=1);

/**
 * Esempio di gestione errori in PHP con logging su syslog.
 * - intercetta errori PHP
 * - intercetta eccezioni non gestite
 * - intercetta errori fatali in chiusura
 * - invia tutto a syslog con un formato leggibile
 */

// Configurazione base del logger
openlog(´mia_app_php´, LOG_PID | LOG_ODELAY, LOG_USER);

/**
 * Converte un livello di errore PHP in una stringa leggibile.
 */
function errorLevelToString(int $severity): string
{
    return match ($severity) {
        E_ERROR => ´E_ERROR´,
        E_WARNING => ´E_WARNING´,
        E_PARSE => ´E_PARSE´,
        E_NOTICE => ´E_NOTICE´,
        E_CORE_ERROR => ´E_CORE_ERROR´,
        E_CORE_WARNING => ´E_CORE_WARNING´,
        E_COMPILE_ERROR => ´E_COMPILE_ERROR´,
        E_COMPILE_WARNING => ´E_COMPILE_WARNING´,
        E_USER_ERROR => ´E_USER_ERROR´,
        E_USER_WARNING => ´E_USER_WARNING´,
        E_USER_NOTICE => ´E_USER_NOTICE´,
        E_STRICT => ´E_STRICT´,
        E_RECOVERABLE_ERROR => ´E_RECOVERABLE_ERROR´,
        E_DEPRECATED => ´E_DEPRECATED´,
        E_USER_DEPRECATED => ´E_USER_DEPRECATED´,
        default => ´UNKNOWN_ERROR´,
    };
}

/**
 * Scrive un messaggio su syslog con contesto utile.
 */
function logToSyslog(string $level, string $message, array $context = []): void
{
    $contextString = $context ? json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : ´{}´;
    $finalMessage = sprintf(´[%s] %s | context=%s´, $level, $message, $contextString);

    // In un progetto reale potresti scegliere livelli diversi
    syslog(LOG_ERR, $finalMessage);
}

/**
 * Gestore degli errori PHP.
 * Converte errori non fatali in log strutturati.
 */
set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
    $levelName = errorLevelToString($severity);

    logToSyslog(´PHP_ERROR´, $message, [
        ´severity´ => $levelName,
        ´file´ => $file,
        ´line´ => $line,
        ´request_uri´ => $_SERVER[´REQUEST_URI´] ?? null,
        ´user_agent´ => $_SERVER[´HTTP_USER_AGENT´] ?? null,
    ]);

    // Restituiamo true per indicare che l´errore è stato gestito
    return true;
});

/**
 * Gestore delle eccezioni non catturate.
 */
set_exception_handler(function (Throwable $exception): void {
    logToSyslog(´UNCAUGHT_EXCEPTION´, $exception->getMessage(), [
        ´type´ => get_class($exception),
        ´file´ => $exception->getFile(),
        ´line´ => $exception->getLine(),
        ´trace´ => $exception->getTraceAsString(),
        ´request_uri´ => $_SERVER[´REQUEST_URI´] ?? null,
    ]);

    http_response_code(500);
    echo ´Si è verificato un errore interno.´;
});

/**
 * Gestore degli errori fatali alla chiusura dello script.
 */
register_shutdown_function(function (): void {
    $lastError = error_get_last();

    if ($lastError !== null) {
        $fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR];

        if (in_array($lastError[´type´], $fatalTypes, true)) {
            logToSyslog(´FATAL_ERROR´, $lastError[´message´], [
                ´type´ => errorLevelToString($lastError[´type´]),
                ´file´ => $lastError[´file´],
                ´line´ => $lastError[´line´],
            ]);
        }
    }

    closelog();
});

/**
 * Simulazione di un warning controllato.
 */
function divide(int $a, int $b): float
{
    if ($b === 0) {
        trigger_error(´Divisione per zero non consentita´, E_USER_WARNING);
        return 0.0;
    }

    return $a / $b;
}

// Esempio di utilizzo
echo ´Risultato: ´ . divide(10, 0) . PHP_EOL;

// Esempio di eccezione non gestita
throw new RuntimeException(´Eccezione di prova non gestita´);

Spiegazione

Il codice mostrato implementa una strategia completa di gestione degli errori. Vediamo i pezzi principali uno per uno.

1. Apertura del canale syslog

La funzione openlog() inizializza il collegamento con il sistema di logging. Il primo parametro è il nome dell’applicazione, utile per riconoscere i messaggi nei log di sistema. Le opzioni LOG_PID e LOG_ODELAY aggiungono rispettivamente il PID del processo e un comportamento di apertura ritardata del log. LOG_USER indica la facility, cioè la categoria di sistema usata per i messaggi.

2. Conversione del livello di errore

La funzione errorLevelToString() trasforma i codici numerici di PHP in etichette leggibili. Questo è utile perché i numeri da soli sono poco chiari nei log. Ad esempio, invece di memorizzare solo E_WARNING come valore numerico, salvare la stringa rende più immediata l’analisi.

3. Funzione di logging centralizzata

logToSyslog() costruisce un messaggio uniforme. Inserisce il livello, il messaggio e un contesto in formato JSON. Questa è una buona pratica perché i log strutturati sono più facili da cercare e analizzare. Il contesto può contenere informazioni come file, linea, URL richiesta o user agent.

4. Gestione degli errori non fatali

Con set_error_handler() intercettiamo warning, notice e altri errori gestibili. La callback riceve severità, messaggio, file e linea. Nel nostro esempio trasformiamo tutto in un evento di log e restituiamo true, segnalando a PHP che l’errore è stato preso in carico.

Questo approccio è molto utile per evitare che warning ripetuti finiscano direttamente nell’output HTTP, cosa che in produzione è quasi sempre da evitare.

5. Gestione delle eccezioni non catturate

Con set_exception_handler() intercettiamo le eccezioni che non sono state gestite nel flusso normale. Qui scriviamo nel log il tipo dell’eccezione, il messaggio, il file, la linea e lo stack trace. Dopo il logging, restituiamo all’utente un messaggio generico, senza esporre dettagli sensibili.

6. Gestione dei fatal error

Gli errori fatali non possono essere gestiti come i warning, ma possiamo intercettarli in fase di shutdown con register_shutdown_function(). Dentro la funzione controlliamo error_get_last() e verifichiamo se l’ultimo errore appartiene a una categoria fatale. Se sì, lo inviamo a syslog prima di chiudere il processo.

Questo è importante perché alcuni errori, come un parse error o un fatal error, altrimenti rischierebbero di non lasciare traccia applicativa utile.

Best practice

  • Non mostrare dettagli tecnici all’utente finale: i messaggi completi devono andare nel log, mentre all’utente va mostrato un errore generico.
  • Usa log strutturati: includere contesto in JSON rende la ricerca e l’analisi molto più efficaci.
  • Separa ambiente di sviluppo e produzione: in locale puoi mantenere error_reporting alto, ma in produzione meglio loggare senza esporre output diretto.
  • Evita di loggare dati sensibili: mai inserire password, token o dati personali nei log.
  • Gestisci anche gli errori fatali: non limitarti a warning ed eccezioni, perché i problemi più gravi spesso emergono solo in shutdown.
  • Chiudi sempre il canale con closelog(): è una buona abitudine per liberare correttamente le risorse.
  • Usa livelli coerenti: non tutto è un errore critico. Alcuni eventi possono essere warning, notice o informational in base al contesto.

Riepilogo

Il logging su syslog è una soluzione robusta per la gestione degli errori in PHP, soprattutto in ambienti server e applicazioni in produzione. Con poche funzioni native puoi intercettare errori, eccezioni e problemi fatali, centralizzando la diagnosi in un unico flusso di log.

Abbiamo visto come:

  • inizializzare syslog con openlog();
  • gestire gli errori PHP con set_error_handler();
  • catturare le eccezioni con set_exception_handler();
  • intercettare i fatal error con register_shutdown_function();
  • scrivere messaggi coerenti e leggibili per il debug.

Se vuoi migliorare ulteriormente il sistema, puoi integrare questo approccio con un framework di logging più avanzato, oppure inviare i messaggi a un collector centralizzato. Ma come base, syslog resta una scelta solida, semplice e molto efficace.

Approfondisci con risorse ufficiali

  • PHP Manual - error handling: documentazione ufficiale sulla gestione degli errori in PHP.
  • PHP Manual - set_error_handler(): dettagli su come intercettare errori personalizzati.
  • PHP Manual - set_exception_handler(): gestione delle eccezioni non catturate.
  • PHP Manual - register_shutdown_function(): uso della funzione di shutdown per intercettare errori fatali.
  • PHP Manual - syslog() e openlog(): funzioni native per l’invio dei log al sistema.
  • Documentazione Linux syslog: utile per capire come i messaggi vengono raccolti dal sistema operativo.

SHARE