Cos'è il monitoraggio di un'applicazione e perché serve
In sviluppo, gli errori sono visibili — appaiono nel terminale, nella console del browser, nei log del server di sviluppo. In produzione, gli errori silenziosi sono la norma: un'eccezione non gestita, una query lenta, un task Celery che fallisce silenziosamente. Se non hai un sistema di monitoraggio, scopri i problemi quando li segnala un utente — o quando è troppo tardi.
Il monitoraggio di un'applicazione Django in produzione si costruisce su tre livelli distinti:
- Error tracking — catturare e notificare le eccezioni non gestite in tempo reale
- Logging — registrare gli eventi applicativi in modo strutturato e consultabile
- Metriche — misurare performance, throughput e comportamento nel tempo
Ognuno risponde a una domanda diversa. Sentry risponde a "cosa è andato storto?". Il logging risponde a "cosa stava succedendo in quel momento?". Le metriche rispondono a "com'è andato il mese scorso rispetto a questo?".

Cos'è Sentry e come integrarlo in Django
Sentry è una piattaforma di error tracking — cattura le eccezioni non gestite, le raggruppa per tipo, le arricchisce con contesto (stack trace, variabili locali, utente coinvolto, request HTTP), e le notifica in tempo reale.
Installazione:
pip install sentry-sdkConfigurazione in settings.py:
import sentry_sdk
sentry_sdk.init(
dsn=os.environ.get("SENTRY_DSN"),
environment=os.environ.get("DJANGO_ENV", "production"),
traces_sample_rate=0.1,
profiles_sample_rate=0.1,
send_default_pii=False,
)traces_sample_rate=0.1 abilita il performance monitoring su il 10% delle richieste — sufficiente per avere dati significativi senza impatto sulle performance. send_default_pii=False esclude dati personali identificabili dai report — importante per la conformità GDPR.
Una volta configurato, Sentry cattura automaticamente tutte le eccezioni non gestite in Django, incluse quelle nei task Celery e nei management commands.
Aggiungere contesto utente:
import sentry_sdk
def my_view(request):
if request.user.is_authenticated:
sentry_sdk.set_user({
"id": request.user.id,
"email": request.user.email,
})Con il contesto utente, ogni errore in Sentry mostra quale utente lo ha scatenato — fondamentale per riprodurre e investigare i bug.
Catturare errori manuali:
try:
risultato = operazione_rischiosa()
except Exception as e:
sentry_sdk.capture_exception(e)
logger.error("Operazione fallita", exc_info=True)Non tutti gli errori devono far saltare la request — alcuni vanno catturati, loggati, e gestiti. capture_exception li invia a Sentry senza dover sollevare l'eccezione.
Logging in Django: configurazione strutturata per la produzione
Django usa il sistema di logging standard di Python. La configurazione avviene nel settings.py tramite il dizionario LOGGING.
Una configurazione minima per la produzione:
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(asctime)s %(name)s %(levelname)s %(message)s",
},
"verbose": {
"format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "json",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "/var/log/django/app.log",
"maxBytes": 10 * 1024 * 1024,
"backupCount": 5,
"formatter": "json",
},
},
"root": {
"handlers": ["console", "file"],
"level": "INFO",
},
"loggers": {
"django": {
"handlers": ["console", "file"],
"level": "WARNING",
"propagate": False,
},
"django.db.backends": {
"handlers": ["console"],
"level": "WARNING",
"propagate": False,
},
"myapp": {
"handlers": ["console", "file"],
"level": "DEBUG",
"propagate": False,
},
},
}Logging JSON strutturato
Il formatter JSON (python-json-logger) produce log in formato JSON invece che testo libero:
pip install python-json-loggerIl vantaggio è che i log JSON sono direttamente consumabili da sistemi di aggregazione come Elasticsearch, Loki, o Datadog — puoi filtrare per campo, fare query, costruire dashboard. I log in testo libero richiedono parsing con regex.
Usare il logger nel codice:
import logging
logger = logging.getLogger(__name__)
def processa_pratica(pratica_id: int):
logger.info("Inizio elaborazione pratica", extra={"pratica_id": pratica_id})
try:
pratica = Pratica.objects.get(id=pratica_id)
risultato = elabora(pratica)
logger.info("Pratica elaborata con successo", extra={
"pratica_id": pratica_id,
"risultato": risultato
})
except Pratica.DoesNotExist:
logger.warning("Pratica non trovata", extra={"pratica_id": pratica_id})
except Exception as e:
logger.error("Errore elaborazione pratica", exc_info=True, extra={
"pratica_id": pratica_id
})extra={} aggiunge campi arbitrari al log JSON — con un aggregatore puoi poi filtrare tutti i log relativi a una pratica specifica con pratica_id=123.
Logging dei task Celery:
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
@shared_task
def mio_task(param):
logger.info("Task avviato", extra={"param": param})get_task_logger include automaticamente l'ID del task nel contesto — utile per tracciare l'esecuzione di un task specifico nei log.
Metriche: cosa misurare e come
Le metriche rispondono a domande diverse dagli errori e dai log: quante richieste al secondo stiamo gestendo? Qual è il tempo medio di risposta? Quanti task Celery sono in coda?
django-prometheus è la soluzione più semplice per esporre metriche in formato Prometheus da Django:
pip install django-prometheusINSTALLED_APPS = [
"django_prometheus",
]
MIDDLEWARE = [
"django_prometheus.middleware.PrometheusBeforeMiddleware",
# ... altri middleware
"django_prometheus.middleware.PrometheusAfterMiddleware",
]Aggiunge automaticamente l'endpoint /metrics/ con le metriche HTTP (numero richieste, latenza, status code) e le metriche del database (query per tipo, tempo di esecuzione).
Metriche custom:
from prometheus_client import Counter, Histogram
pratiche_elaborate = Counter(
"pratiche_elaborate_total",
"Numero totale di pratiche elaborate",
["stato"]
)
tempo_elaborazione = Histogram(
"elaborazione_pratica_seconds",
"Tempo di elaborazione pratica in secondi"
)
def processa_pratica(pratica_id: int):
with tempo_elaborazione.time():
risultato = elabora(pratica_id)
pratiche_elaborate.labels(stato=risultato.stato).inc()Con Prometheus e Grafana puoi costruire dashboard che mostrano l'andamento nel tempo — picchi di traffico, degradazione delle performance, anomalie nei task Celery.
Sentry, logging e metriche insieme: come si integrano
I tre sistemi non sono alternativi — si completano.
Uno scenario tipico in Skara: un utente segnala che il suo documento PDF non è stato generato. Il percorso di investigazione è:
- Sentry — cerco l'errore per email utente o per timestamp. Se c'è un'eccezione non gestita, è lì con stack trace completo.
- Log — se l'errore era gestito (caught exception), cerco nei log per utente_id o pratica_id l'evento di generazione PDF. Vedo cosa è successo prima e dopo.
- Metriche — verifico se il problema era isolato o sistematico: le metriche sui task Celery mostrano se c'era un picco di fallimenti in quel periodo.
Tre strumenti, tre domande diverse, un'unica risposta completa.
La configurazione minima da avere in produzione
Se devi scegliere un solo strumento con cui iniziare, scegli Sentry — è il più immediato e quello che cattura i problemi critici prima che diventino invisibili.
Il secondo passo è il logging strutturato JSON, configurato per scrivere su file con rotazione — anche senza un aggregatore esterno, avere log consultabili è già un enorme miglioramento rispetto al nulla.
Le metriche con Prometheus e Grafana sono il terzo passo — richiedono più infrastruttura ma danno visibilità che gli altri due strumenti non possono dare.
In Skara girano tutti e tre: Sentry per gli errori, log JSON aggregati, e metriche Prometheus con dashboard Grafana per il monitoraggio operativo. Il costo di setup è stato alcune ore. Il valore in termini di visibilità sull'applicazione è difficile da sovrastimare.