Introduzione
Quando un’applicazione PHP cresce, salvare gli errori su un semplice file di log non basta più. Un file unico può diventare troppo grande, difficile da consultare e persino rischioso da gestire in produzione. Un approccio molto utile e pratico è la gestione degli errori con logging e rotazione automatica dei file: gli errori vengono scritti in file separati per giorno, dimensione o contesto, così da mantenere i log ordinati, leggibili e facili da analizzare.
Questo tutorial ti mostra come costruire un sistema semplice ma robusto per:
- intercettare errori e eccezioni in PHP;
- registrare messaggi di log in modo strutturato;
- ruotare automaticamente i file quando cambiano giorno o superano una dimensione massima;
- mantenere il codice pulito e adatto a un progetto reale.
L’obiettivo non è creare un framework completo, ma una soluzione concreta che puoi integrare facilmente in un progetto PHP intermedio.
Codice completo
<?php
declare(strict_types=1);
/**
* Logger con rotazione automatica:
* - crea un file di log al giorno
* - se il file supera una certa dimensione, lo rinomina con suffisso
* - intercetta errori PHP, eccezioni e fatal error
*/
class RotatingLogger
{
private string $logDir;
private string $baseFileName;
private int $maxFileSizeBytes;
public function __construct(string $logDir, string $baseFileName = ´app.log´, int $maxFileSizeBytes = 1048576)
{
$this->logDir = rtrim($logDir, DIRECTORY_SEPARATOR);
$this->baseFileName = $baseFileName;
$this->maxFileSizeBytes = $maxFileSizeBytes;
if (!is_dir($this->logDir)) {
mkdir($this->logDir, 0775, true);
}
}
public function log(string $level, string $message, array $context = []): void
{
$filePath = $this->getLogFilePath();
$entry = sprintf(
"[%s] [%s] %s %s%s",
date(´Y-m-d H:i:s´),
strtoupper($level),
$message,
$this->formatContext($context),
PHP_EOL
);
$this->rotateIfNeeded($filePath);
file_put_contents($filePath, $entry, FILE_APPEND | LOCK_EX);
}
public function registerHandlers(): void
{
set_error_handler([$this, ´handlePhpError´]);
set_exception_handler([$this, ´handleException´]);
register_shutdown_function([$this, ´handleShutdown´]);
}
public function handlePhpError(int $severity, string $message, string $file, int $line): bool
{
// Trasforma gli errori PHP in log strutturato
$this->log(´error´, $message, [
´severity´ => $severity,
´file´ => $file,
´line´ => $line
]);
// Restituiamo false per permettere a PHP di gestire anche il suo flusso standard
return false;
}
public function handleException(Throwable $exception): void
{
$this->log(´critical´, $exception->getMessage(), [
´exception´ => get_class($exception),
´file´ => $exception->getFile(),
´line´ => $exception->getLine(),
´trace´ => $exception->getTraceAsString()
]);
http_response_code(500);
echo "Si è verificato un errore interno.";
}
public function handleShutdown(): void
{
$error = error_get_last();
if ($error !== null) {
$types = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
if (in_array($error[´type´], $types, true)) {
$this->log(´fatal´, $error[´message´], [
´file´ => $error[´file´],
´line´ => $error[´line´]
]);
}
}
}
private function getLogFilePath(): string
{
// File giornaliero: app-2026-06-20.log
$datePart = date(´Y-m-d´);
return $this->logDir . DIRECTORY_SEPARATOR . pathinfo($this->baseFileName, PATHINFO_FILENAME) . ´-´ . $datePart . ´.log´;
}
private function rotateIfNeeded(string $filePath): void
{
if (!file_exists($filePath)) {
return;
}
clearstatcache(true, $filePath);
if (filesize($filePath) < $this->maxFileSizeBytes) {
return;
}
$timestamp = date(´His´);
$rotatedFile = preg_replace(´/.log$/´, ´´, $filePath) . ´-´ . $timestamp . ´.log´;
rename($filePath, $rotatedFile);
}
private function formatContext(array $context): string
{
if (empty($context)) {
return ´´;
}
return json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}
// =========================
// ESEMPIO DI UTILIZZO
// =========================
$logger = new RotatingLogger(__DIR__ . ´/logs´, ´app.log´, 1024 * 1024);
$logger->registerHandlers();
// Log manuale
$logger->log(´info´, ´Applicazione avviata correttamente´, [
´user_id´ => 42,
´module´ => ´dashboard´
]);
// Esempio di eccezione gestita
try {
throw new RuntimeException(´Connessione al database non disponibile´);
} catch (Throwable $e) {
$logger->handleException($e);
}
// Esempio di errore PHP (warning) intercettato
echo $undefinedVariable;
// Esempio di fatal error simulato: chiamata a funzione inesistente
// nonExistingFunction(); Spiegazione
La classe RotatingLogger centralizza tre compiti fondamentali: scrittura del log, gestione degli handler di errore e rotazione dei file. Vediamola in modo ordinato.
1. Costruttore e directory dei log
Nel costruttore definiamo:
- la cartella dove salvare i file;
- il nome base del file;
- la dimensione massima consentita.
Se la directory non esiste, viene creata automaticamente. In un progetto reale questo evita errori banali di configurazione e rende il logger più autonomo.
2. Metodo log()
Il metodo log() costruisce una riga leggibile con:
- data e ora;
- livello di gravità;
- messaggio;
- contesto opzionale in formato JSON.
Prima di scrivere, il logger chiama rotateIfNeeded() per controllare se il file ha superato la soglia. Questo è importante: così eviti che il file cresca senza controllo.
3. Rotazione automatica
La rotazione qui è semplice ma efficace. Se il file supera la dimensione massima, viene rinominato con un suffisso orario. Per esempio:
- app-2026-06-20.log
- app-2026-06-20-153012.log
In questo modo il file corrente resta pulito, mentre quello vecchio viene conservato come archivio. La strategia è utile quando vuoi evitare file enormi e mantenere una cronologia consultabile.
4. Gestione degli errori PHP
Con set_error_handler() intercetti errori come warning, notice e altri problemi non fatali. Il metodo handlePhpError() registra anche informazioni utili come severità, file e linea.
Questa informazione contestuale è preziosa perché ti permette di capire subito dove è nato il problema, senza dover riprodurre manualmente il bug.
5. Gestione delle eccezioni
Con set_exception_handler() possiamo intercettare eccezioni non gestite. Nel nostro esempio, il logger salva:
- classe dell’eccezione;
- messaggio;
- file e linea;
- stack trace completo.
In produzione, dopo il log, mostriamo un messaggio generico all’utente e restituiamo HTTP 500. È una buona pratica: il dettaglio tecnico resta nel log, mentre l’utente vede un errore pulito e sicuro.
6. Errori fatali allo shutdown
Alcuni errori, come i fatal error, non possono essere gestiti normalmente. Per questo usiamo register_shutdown_function() e leggiamo error_get_last(). Se l’errore appartiene alle categorie critiche, viene scritto nel log prima della chiusura dello script.
Best practice
- Usa livelli di log coerenti: info, warning, error, critical, fatal. Ti aiuterà a filtrare i problemi più velocemente.
- Non salvare dati sensibili: evita password, token, dati personali o query complete con informazioni riservate.
- Proteggi la cartella dei log: in produzione la directory non dovrebbe essere accessibile direttamente via web.
- Scrivi log strutturati: il contesto in JSON rende più semplice l’analisi manuale e automatica.
- Non mostrare dettagli tecnici all’utente: il messaggio pubblico deve essere generico, mentre il log conserva il dettaglio.
- Usa rotazione e retention: oltre alla rotazione, valuta una pulizia automatica dei file più vecchi di un certo numero di giorni.
- Evita logging eccessivo: troppe informazioni inutili rendono i log rumorosi e difficili da analizzare.
Riepilogo
La gestione degli errori con logging e rotazione automatica è una soluzione molto utile per applicazioni PHP in crescita. Ti permette di:
- tenere sotto controllo gli errori senza perdere informazioni;
- evitare file di log troppo grandi;
- separare i problemi per data o dimensione;
- migliorare la manutenzione e il debug del progetto.
Con poche decine di righe di codice puoi costruire un sistema già valido per ambienti di sviluppo e piccoli progetti di produzione. Se poi il progetto cresce, potrai evolverlo verso soluzioni più avanzate, come librerie dedicate o integrazione con sistemi di osservabilità.
Approfondisci con risorse ufficiali
- Manuale PHP - Error Handling: documentazione ufficiale su gestione di errori, eccezioni e handler personalizzati.
- Manuale PHP - set_error_handler(): dettagli su come intercettare gli errori PHP.
- Manuale PHP - set_exception_handler(): guida alla gestione centralizzata delle eccezioni.
- Manuale PHP - register_shutdown_function(): utile per intercettare errori fatali in fase di terminazione.
- Manuale PHP - file_put_contents(): riferimento per la scrittura sicura su file.
Se vuoi, nel prossimo passo posso anche scrivere una versione di questo tutorial con PSR-3 e Monolog, così da mostrare come gestire logging e rotazione in modo più professionale in un progetto moderno.
