Quando qualcosa va storto su un sito in produzione, il log di errore di Apache è spesso il primo posto in cui guardare — ma leggerlo riga per riga, su un file che può contenere migliaia di voci, è lento e porta a perdere pattern ricorrenti. Un piccolo script PHP che struttura le righe del log in dati interrogabili rende molto più semplice individuare, ad esempio, quale file genera più errori o in quale fascia oraria si concentrano.
Il formato di una riga di log Apache
Una riga tipica del log di errore di Apache ha una struttura riconoscibile: timestamp tra parentesi quadre, livello di log, opzionalmente il PID del processo e l'IP del client, seguiti dal messaggio vero e proprio. Un'unica espressione regolare con gruppi di cattura può isolare tutte queste parti in un colpo solo:
<?php
declare(strict_types=1);
function analizzaRigaLog(string $riga): ?array
{
$pattern = '/\[([^\]]+)\] \[([^\]]+)\] (?:\[pid ([^\]]+)\] )?(?:\[client ([^\]]+)\] )?(.+)/';
if (!preg_match($pattern, $riga, $m)) {
return null; // riga che non rispetta il formato atteso: la si scarta
}
return [
'timestamp' => $m[1],
'livello' => $m[2],
'pid' => $m[3] ?? '',
'client_ip' => $m[4] ?? '',
'messaggio' => $m[5],
];
}I due gruppi opzionali per PID e IP client — marcati con (?:...)? — sono importanti: non tutte le righe di log li includono, e senza renderli facoltativi il pattern fallirebbe su una parte consistente del file.
Normalizzare il timestamp con un fallback
Il formato data di Apache (es. Wed Nov 19 07:47:48.164731 2025) include a volte i microsecondi, che DateTime di PHP non sempre digerisce allo stesso modo su tutte le versioni. Conviene rimuoverli con una piccola sostituzione prima del parsing, e prevedere comunque un fallback su strtotime() per non perdere la riga se il formato varia leggermente:
function normalizzaTimestamp(string $timestamp): ?DateTimeImmutable
{
$pulito = preg_replace('/\.\d+/', '', $timestamp) ?? $timestamp;
try {
return new DateTimeImmutable($pulito);
} catch (Exception) {
$unix = strtotime($pulito);
return $unix !== false ? (new DateTimeImmutable())->setTimestamp($unix) : null;
}
}Classificare gli errori per gravità
Non tutte le righe hanno lo stesso peso: un PHP Deprecated non merita la stessa attenzione di un PHP Fatal error. Una funzione di classificazione, basata su un controllo testuale sul livello di log e sul messaggio, permette di ordinare o filtrare rapidamente le voci più critiche:
function determinaGravita(string $livello, string $messaggio): string
{
$messaggioMinuscolo = strtolower($messaggio);
if (str_contains($livello, 'error') || str_contains($messaggioMinuscolo, 'fatal')) {
return 'alta';
}
if (str_contains($livello, 'warn')) {
return 'media';
}
return 'bassa';
}Aggregare le statistiche mentre si legge il file
Per un file di log corposo conviene leggerlo riga per riga con un file handle (come nell'articolo sull'import CSV) invece di caricarlo tutto in memoria, aggiornando contatori aggregati mano a mano:
$statistiche = ['per_gravita' => [], 'per_file' => []];
$handle = fopen($percorsoLog, 'rb');
while (($riga = fgets($handle)) !== false) {
$voce = analizzaRigaLog($riga);
if ($voce === null) {
continue;
}
$gravita = determinaGravita($voce['livello'], $voce['messaggio']);
$statistiche['per_gravita'][$gravita] = ($statistiche['per_gravita'][$gravita] ?? 0) + 1;
}
fclose($handle);
Con questi pochi elementi — un pattern di parsing, una normalizzazione robusta della data e una classificazione semplice — si ottiene uno strumento diagnostico che trasforma un file di testo grezzo in un piccolo cruscotto di errori, utile prima ancora di collegare un sistema di log aggregation più strutturato.