Introduzione
Quando un’applicazione PHP cresce, i semplici messaggi testuali nei file di log diventano difficili da consultare. Una riga come Errore database non indica necessariamente quale richiesta l’ha generata, quale utente fosse coinvolto o quale eccezione sia stata sollevata.
Una soluzione pratica consiste nell’utilizzare il logging strutturato in formato JSON. Ogni evento viene scritto come un oggetto JSON contenente campi standardizzati: data e ora, livello, messaggio, URL, metodo HTTP, identificativo della richiesta ed eventuali dettagli dell’eccezione.
In questo tutorial costruiremo un piccolo sistema di logging per PHP che:
- scrive eventi in formato JSON;
- assegna un identificativo univoco a ogni richiesta;
- registra automaticamente gli errori non gestiti;
- separa i dati tecnici mostrati all’utente da quelli salvati nel log;
- rende i log facilmente analizzabili da strumenti esterni.
L’esempio utilizza solo funzionalità native di PHP e può essere adattato a un’applicazione web reale.
Codice completo
<?php
declare(strict_types=1);
/**
* Logger JSON minimale.
*/
final class JsonLogger
{
public function __construct(
private readonly string $filePath
) {
}
/**
* Scrive un evento nel file di log.
*
* @param array<string, mixed> $context
*/
public function log(
string $level,
string $message,
array $context = []
): void {
$entry = [
´timestamp´ => date(DATE_ATOM),
´level´ => $level,
´message´ => $message,
´request_id´ => $_SERVER[´REQUEST_ID´] ?? null,
´http_method´ => $_SERVER[´REQUEST_METHOD´] ?? null,
´uri´ => $_SERVER[´REQUEST_URI´] ?? null,
´client_ip´ => $_SERVER[´REMOTE_ADDR´] ?? null,
´context´ => $context,
];
$json = json_encode(
$entry,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
if ($json === false) {
error_log(´Impossibile serializzare un evento di logging.´);
return;
}
// FILE_APPEND evita di sovrascrivere gli eventi precedenti.
// LOCK_EX riduce il rischio di scritture contemporanee corrotte.
file_put_contents(
$this->filePath,
$json . PHP_EOL,
FILE_APPEND | LOCK_EX
);
}
/**
* Registra un´eccezione includendo le informazioni utili al debug.
*/
public function exception(Throwable $exception, string $message): void
{
$this->log(´error´, $message, [
´exception_class´ => $exception::class,
´exception_message´ => $exception->getMessage(),
´file´ => $exception->getFile(),
´line´ => $exception->getLine(),
´trace´ => $exception->getTraceAsString(),
]);
}
}
// Creazione dell´identificativo univoco della richiesta.
$requestId = bin2hex(random_bytes(16));
$_SERVER[´REQUEST_ID´] = $requestId;
$logger = new JsonLogger(__DIR__ . ´/logs/application.log´);
// Gestione delle eccezioni non catturate.
set_exception_handler(
function (Throwable $exception) use ($logger): void {
$logger->exception(
$exception,
´Eccezione non gestita durante la richiesta´
);
http_response_code(500);
// Non mostrare dettagli tecnici in produzione.
echo ´Si è verificato un errore. Codice richiesta: ´
. ($_SERVER[´REQUEST_ID´] ?? ´sconosciuto´);
}
);
$logger->log(´info´, ´Richiesta ricevuta´, [
´script´ => $_SERVER[´SCRIPT_NAME´] ?? null,
]);
try {
// Simulazione di una condizione anomala.
throw new RuntimeException(´Connessione al servizio non disponibile´);
} catch (Throwable $exception) {
$logger->exception(
$exception,
´Errore durante l’elaborazione della richiesta´
);
http_response_code(503);
echo ´Servizio temporaneamente non disponibile. ´
. ´Codice richiesta: ´ . $requestId;
}
$logger->log(´info´, ´Richiesta completata´); Spiegazione
Perché utilizzare JSON
Con il formato JSON ogni riga del file rappresenta un evento indipendente. Per esempio:
{"timestamp":"2026-09-03T10:15:20+00:00","level":"error","message":"Errore durante l’elaborazione della richiesta","request_id":"a13f...","http_method":"GET","uri":"/report.php","context":{"exception_class":"RuntimeException","file":"/var/www/report.php","line":72}} Questa struttura è più utile rispetto a una stringa libera perché i campi possono essere filtrati e aggregati facilmente. Un sistema di monitoraggio può cercare tutti gli eventi con level uguale a error, mentre uno sviluppatore può trovare rapidamente tutti i messaggi appartenenti allo stesso request_id.
Il ruolo del request ID
L’identificativo della richiesta, chiamato spesso correlation ID o request ID, viene generato all’inizio dell’esecuzione. Lo stesso valore accompagna tutti i messaggi prodotti durante quella richiesta.
È particolarmente utile quando una pagina esegue molte operazioni: accesso al database, chiamate HTTP, validazione e generazione della risposta. Cercando l’identificativo nel file di log è possibile ricostruire il percorso completo dell’operazione.
In un’architettura con più servizi, il valore può anche essere inviato tramite un header HTTP, ad esempio X-Request-ID, così da collegare i log prodotti da applicazioni differenti.
Gestione delle eccezioni
set_exception_handler() intercetta le eccezioni che non sono state gestite da un blocco try...catch. Il logger salva classe, messaggio, file, riga e stack trace. Queste informazioni devono rimanere nei log e non essere mostrate direttamente agli utenti.
Nel codice gli errori catturati localmente vengono registrati e producono una risposta HTTP appropriata, in questo caso 503 Service Unavailable. L’utente riceve soltanto un messaggio generico e il codice della richiesta, utile per comunicare l’accaduto all’assistenza.
Best practice
- Non registrare password o token: rimuovi sempre credenziali, cookie, numeri di carta e dati personali non necessari.
- Usa nomi di campo coerenti: ad esempio mantieni sempre
request_id,timestampecontext. - Proteggi la directory dei log: il file non dovrebbe essere scaricabile tramite il browser. È preferibile conservarlo fuori dalla directory pubblica.
- Non mostrare lo stack trace in produzione: può rivelare percorsi interni, query o dettagli dell’infrastruttura.
- Usa codici HTTP corretti: distingui tra errori del client, come
400, errori di autenticazione, come401, ed errori del server, come500. - Evita log eccessivi: registra eventi utili, ma non dati sensibili o informazioni ripetitive a ogni riga.
- Controlla la scrittura: in un progetto reale verifica il valore restituito da
file_put_contents()e gestisci eventuali problemi di permessi o spazio. - Valuta una libreria: per applicazioni complesse, strumenti come Monolog offrono handler, formattatori e integrazioni già pronti.
Riepilogo
Il logging strutturato in JSON rende gli errori PHP più facili da cercare, analizzare e collegare. Aggiungendo un identificativo univoco a ogni richiesta è possibile seguire una specifica operazione anche quando genera numerosi eventi.
La soluzione mostrata separa correttamente due responsabilità: il logger conserva dettagli tecnici per gli sviluppatori, mentre la risposta HTTP presenta all’utente un messaggio sicuro e comprensibile. Questa distinzione è essenziale sia per la sicurezza sia per la manutenzione dell’applicazione.
