I log sono i registri degli eventi di server, applicazioni e servizi: ogni richiesta a un sito, ogni errore, ogni azione dell’utente vi lascia una riga. Il parsing di log serve per analizzare il traffico, individuare gli errori, indagare sugli incidenti di sicurezza e fare monitoraggio. Formalmente i log sono file di testo, e le tecniche generali per analizzarli sono le stesse; ma hanno abbastanza specificità da meritare un articolo a parte nella nostra panoramica sul parsing di documenti.
In che cosa i log differiscono dal testo comune
La particolarità principale: all’interno di una stessa fonte, il formato della riga è rigorosamente stabile. Il web server scrive ogni riga secondo lo stesso schema, e questo permette di analizzare milioni di righe con un’unica espressione regolare. Ma ci sono anche complicazioni. I log possono essere enormi — gigabyte al giorno, quindi non si possono leggere per intero in memoria, solo in streaming. Ruotano — i file vecchi vengono rinominati e compressi in .gz, e il parser deve saper leggere gli archivi compressi. E contengono voci multiriga — per esempio, la traccia di un errore (stack trace) occupa decine di righe che si riferiscono a un unico evento.
I formati standard dei log web
Il più delle volte si analizzano gli access log di Nginx e Apache in formato «combined». Una riga ha questo aspetto:
192.168.1.10 - - [01/Jun/2026:13:55:36 +0200] "GET /product/101 HTTP/1.1" 200 4523 "https://example.com/" "Mozilla/5.0 ..."Nell’ordine: indirizzo IP, data e ora, metodo e percorso della richiesta, codice di risposta, dimensione della risposta, referer e User-Agent. Poiché il formato è fisso, è comodo analizzarlo con un’unica espressione regolare con gruppi denominati.
Python
L’approccio di base e più flessibile: un’espressione regolare più la lettura in streaming. I gruppi denominati rendono leggibile il risultato.
import re
import gzip
LOG_RE = re.compile(
r'(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] '
r'"(?P<method>\S+) (?P<path>\S+) [^"]*" '
r'(?P<status>\d{3}) (?P<size>\S+) '
r'"(?P<referer>[^"]*)" "(?P<agent>[^"]*)"'
)
def open_log(path):
# legge in modo trasparente sia i file normali sia quelli compressi
return gzip.open(path, "rt") if path.endswith(".gz") else open(path, "r")
with open_log("access.log") as f:
for line in f:
m = LOG_RE.match(line)
if not m:
continue
row = m.groupdict()
if row["status"] != "200":
print(row["status"], row["method"], row["path"], row["ip"])Quando non basta analizzare le righe ma bisogna anche esaminarle — contare la top delle pagine, la distribuzione dei codici di risposta, il traffico per fascia oraria — conviene versare le righe già analizzate in pandas e lavorarci come con una tabella.
import pandas as pd
records = [m.groupdict() for line in open_log("access.log")
if (m := LOG_RE.match(line))]
df = pd.DataFrame(records)
# top 10 delle pagine più richieste
print(df["path"].value_counts().head(10))
# quota di errori 5xx
errors = df[df["status"].str.startswith("5")]
print(len(errors) / len(df))Da qui il risultato si esporta facilmente in CSV o Excel per un report.
Riga di comando: analisi rapida senza codice
Per un compito una tantum, spesso la via più rapida sono le utility Unix. awk divide la riga in campi e conta al volo.
# top 10 degli IP per numero di richieste
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
# tutte le richieste terminate con un errore 404
awk '$9 == 404 {print $7}' access.log | sort | uniq -c | sort -rn
# volume totale di traffico servito (il campo 10 è la dimensione)
awk '{sum += $10} END {print sum/1024/1024 " MB"}' access.logLa combinazione grep + awk + sort + uniq risolve la maggior parte delle domande ad hoc sui log senza dover scrivere nulla.
Analizzatori pronti
Se il compito è l’analisi generale del traffico web, e non l’estrazione di campi specifici, non serve inventare un parser. GoAccess legge gli access log di Nginx/Apache e costruisce un report interattivo nel terminale o nel browser in tempo reale. Per pipeline complesse con fonti eterogenee si usa lo stack basato su Logstash con i suoi set di pattern grok — in sostanza, una libreria di espressioni regolari denominate già pronte per i formati di log più diffusi.
Log strutturati (log in JSON)
Le applicazioni moderne scrivono sempre più spesso i log non come righe di testo libero, ma in JSON — un oggetto per riga (formato JSON Lines). Un log così si analizza in modo molto più semplice e affidabile: non servono espressioni regolari fragili, ogni riga viene semplicemente deserializzata.
import json
with open("app.log") as f:
for line in f:
event = json.loads(line)
if event.get("level") == "ERROR":
print(event["timestamp"], event["message"])Se puoi influire su come vengono scritti i log, il passaggio a JSON Lines semplifica radicalmente la loro analisi successiva — vale la pena prevederlo nel progetto fin dall’inizio.
Quali difficoltà si incontrano
I problemi più frequenti sono il formato dell’ora (i log di sistemi diversi usano fusi orari e modelli di data differenti, da ricondurre a una forma unica), gli errori multiriga (lo stack trace rompe l’analisi riga per riga e le voci vanno «incollate» riconoscendo l’inizio di ogni nuovo record) e il volume (analizzare un log giornaliero da gigabyte richiede elaborazione in streaming e filtraggio al volo, non il caricamento in memoria).
Se hai bisogno di analizzare regolarmente i log di più server, consolidarli in un’analitica unica o monitorare errori e attività sospette — configuriamo la raccolta e il parsing dei log per i tuoi formati, con l’esportazione delle metriche nel sistema che preferisci. Le tecniche generali di lavoro con il testo arbitrario su cui tutto questo si fonda sono descritte nell’articolo sul parsing di TXT.