Executive summary
Quando un sistema software distribuito degrada o si interrompe, la capacità di capire perché dipende dalla qualità dei dati che il sistema produce su sé stesso. Questo articolo analizza la distinzione tra sorveglianza tradizionale e osservabilità moderna, illustrando come la combinazione di misure quantitative, registrazioni degli eventi e tracce di propagazione delle richieste consenta di diagnosticare problemi nuovi senza modificare il software in esecuzione. La rilevanza pratica è diretta: i sistemi progettati con criteri di osservabilità riducono il tempo medio di risoluzione degli incidenti e consentono agli ingegneri di porre domande arbitrarie sullo stato interno del sistema. L'analisi mostra inoltre come il framework degli obiettivi di livello di servizio traduca la qualità tecnica in accordi quantitativi verificabili, e come la progettazione degli alert attorno a questi obiettivi riduca il rumore operativo mantenendo la sensibilità agli eventi con impatto reale sull'utente.
Background
Il concetto di osservabilità applicato ai sistemi software trae origine dalla teoria del controllo, dove un sistema è detto osservabile se è possibile determinarne lo stato interno a partire dalle uscite misurabili [1]. Trasferito al dominio dei sistemi distribuiti, il termine acquisisce un significato operativo specifico: un sistema è osservabile se, partendo dai dati di telemetria che produce, è possibile rispondere a domande arbitrarie sul suo comportamento, incluse quelle relative a scenari di guasto mai osservati in precedenza [2, 3]. Questa proprietà distingue l'osservabilità dal monitoring tradizionale, che presuppone la conoscenza a priori delle condizioni di guasto da rilevare, esprimendole come soglie su metriche predeterminate.
Il monitoring, nella sua forma classica, si è sviluppato nei primi anni 2000 come risposta alla crescente complessità dei sistemi enterprise. Strumenti come Nagios (2002) incarnano questo paradigma: si definiscono in anticipo le condizioni di errore, si configurano soglie, si ricevono notifiche quando le soglie vengono superate. Questo approccio è efficace quando il dominio del guasto è noto e circoscritto, ma si rivela inadeguato di fronte alla complessità dei sistemi a microservizi, dove un degrado può manifestarsi attraverso interazioni non anticipate tra decine o centinaia di componenti. Il lavoro di Sigelman et al. su Dapper, pubblicato da Google nel 2010 [1], ha formalizzato l'idea che le richieste in un sistema distribuito debbano essere tracciate end-to-end per consentire la diagnosi dei guasti, segnando un cambio di paradigma nella comunità tecnica.
OpenTelemetry, progetto incubato dalla Cloud Native Computing Foundation (CNCF) e risultato della fusione tra OpenTracing e OpenCensus, rappresenta la risposta della comunità open-source alla necessità di uno standard unificato per la raccolta di telemetria [4, 12]. La sua specifica definisce tre segnali primari, metriche, log e trace, e un protocollo di trasporto neutro rispetto al vendor (OpenTelemetry Protocol, OTLP), che consente di separare la strumentazione del codice dalla destinazione dei dati. Al momento della stesura di questo articolo, OpenTelemetry è il secondo progetto CNCF per volume di attività di sviluppo, dopo Kubernetes [4].
Il framework Google SRE (Site Reliability Engineering), formalizzato nel libro omonimo del 2016 [5] e approfondito nel Site Reliability Workbook del 2018 [6], ha introdotto il vocabolario concettuale con cui l'industria discute oggi gli obiettivi di affidabilità: Service Level Indicator (SLI), Service Level Objective (SLO), Service Level Agreement (SLA), error budget e burn rate. Questi concetti collegano la telemetria alla governance del servizio, trasformando la qualità tecnica in accordi verificabili tra team di sviluppo, team operativi e utenti.
I tre pilastri della telemetria: metriche, log e trace
Metriche
Le metriche sono misure numeriche aggregate nel tempo. Un valore di metrica risponde alla domanda "quanti?" o "quanto?": il numero di richieste al secondo, la latenza al 99° percentile, l'utilizzo della memoria. Prometheus, sviluppato originariamente presso SoundCloud nel 2012 e diventato il primo progetto a ottenere la graduazione CNCF nel 2018 [7], adotta un modello pull basato su HTTP: il server interroga periodicamente gli endpoint delle applicazioni (per convenzione, /metrics), raccogliendo serie temporali multidimensionali identificate da coppie chiave-valore chiamate label. Questo modello pull favorisce la semplicità di deployment e la flessibilità nella configurazione della raccolta, a differenza dei modelli push dove gli agenti inviano metriche a un broker centralizzato.
Il data model di Prometheus distingue quattro tipi di metrica: Counter (valore monotonicamente crescente, es. numero cumulativo di richieste), Gauge (valore istantaneo che può salire o scendere, es. memoria in uso), Histogram (distribuzione di valori su bucket predefiniti, utilizzato per latenze e dimensioni di payload) e Summary (quantili pre-calcolati lato client). La scelta del tipo corretto influenza direttamente l'espressività delle query PromQL e la correttezza delle aggregazioni: un errore frequente consiste nel modellare come Gauge un valore intrinsecamente cumulativo, perdendo la possibilità di calcolare rate su finestre temporali arbitrarie con la funzione rate() di PromQL [8].
Grafana integra Prometheus come datasource primario, consentendo la visualizzazione di serie temporali attraverso dashboard configurabili e query PromQL interattive [9]. La separazione tra raccolta (Prometheus) e visualizzazione (Grafana) rispecchia il principio di responsabilità singola e consente di integrare datasource eterogenei, Loki per i log, Tempo per le trace, nella stessa interfaccia di osservazione. Questa separazione architetturale è una scelta progettuale deliberata di Grafana Labs: nessun componente dell'ecosistema PLG (Prometheus, Loki, Grafana) dipende dagli altri, consentendo sostituzioni modulari senza necessità di riarchitetturare l'intera pipeline.
Log
I log sono registrazioni testuali di eventi discreti, prodotti da un'applicazione nel momento in cui si verifica qualcosa di rilevante. La loro utilità diagnostica è inversamente proporzionale alla loro struttura: log non strutturati (stringhe libere) rendono difficile l'aggregazione automatica, mentre log strutturati in formato JSON o logfmt consentono query per campo e correlazione con altri segnali. La ricerca di Cinque, Cotroneo e Pecchia ha documentato empiricamente come la qualità della strumentazione di logging influenzi direttamente la capacità di diagnostica dei guasti nei sistemi software [10]: sistemi con log inconsistenti o privi di identificatori di correlazione aumentano il tempo medio di risoluzione degli incidenti (MTTR).
Loki, sviluppato da Grafana Labs, adotta per i log un approccio architetturale analogo a quello di Prometheus per le metriche: indicizza solo le label dei log (etichette di metadato come servizio, pod, livello di severità), non il contenuto dei messaggi, riducendo drasticamente il costo di storage rispetto a soluzioni full-text index come Elasticsearch [9]. Il trade-off è una minore espressività nelle ricerche per contenuto libero, compensata dalla semplicità di schema e dal costo operativo ridotto. La correlazione tra log e trace, abilitata dall'inserimento del trace ID come campo strutturato nel messaggio di log, è una delle tecniche più potenti per accelerare la diagnosi della causa radice di un incidente: consente di passare da un alert su una metrica alla trace specifica che mostra il percorso della richiesta difettosa, e da lì ai log del microservizio specifico dove si è verificato il guasto.
Trace distribuite
Una trace distribuita rappresenta il percorso completo di una richiesta attraverso i componenti di un sistema, modellato come albero di span. Ogni span corrisponde all'esecuzione di un'operazione su un singolo servizio e contiene: l'identificatore univoco della trace (trace ID), l'identificatore dello span padre (parent span ID), i timestamp di inizio e fine, gli attributi di contesto e gli eventuali eventi o eccezioni registrati durante l'esecuzione. Il lavoro di Sigelman et al. su Dapper [1] ha introdotto questa astrazione, dimostrando che l'overhead di strumentazione, inferiore all'1% sul throughput in produzione con campionamento adattivo, è accettabile rispetto al beneficio diagnostico in un sistema della scala di Google.
OpenTelemetry standardizza la propagazione del contesto di trace attraverso le frontiere dei servizi tramite il formato W3C TraceContext, consentendo la correlazione end-to-end anche in sistemi poliglotti dove i componenti sono scritti in linguaggi diversi [4]. Il Collector OpenTelemetry funge da proxy configurabile tra le applicazioni e i backend di storage (Jaeger, Zipkin, Grafana Tempo, backend vendor-specific): riceve span tramite OTLP, applica processori (filtraggio, campionamento, arricchimento di attributi) ed esporta verso uno o più destinatari. Questa architettura disaccoppia la strumentazione del codice dal backend di storage, abilitando cambi di infrastruttura senza modifiche alle applicazioni.
flowchart LR
subgraph Applicazioni
A[Servizio A]
B[Servizio B]
C[Servizio C]
end
subgraph Pipeline OTEL
D[OTEL Collector]
end
subgraph Backend
E[Prometheus]
F[Loki]
G[Tempo / Jaeger]
end
subgraph Visualizzazione
H[Grafana]
end
A -- "OTLP (metriche, log, trace)" --> D
B -- "OTLP" --> D
C -- "OTLP" --> D
D -- "metriche" --> E
D -- "log" --> F
D -- "trace" --> G
E --> H
F --> H
G --> H
Figura 1. Architettura di telemetria con OpenTelemetry Collector come punto di aggregazione. Il Collector riceve tutti e tre i segnali dalle applicazioni e li instrada verso backend specializzati, che Grafana unifica in un'unica interfaccia di osservazione.
Il framework SLI/SLO/SLA
Definizioni e gerarchia
Il framework SLI/SLO/SLA, formalizzato nel Google SRE Book [5], introduce una gerarchia di tre concetti distinti che collegano la misurazione tecnica agli accordi di servizio. Un Service Level Indicator (SLI) è una misura quantitativa di una caratteristica del servizio rilevante per l'utente: la proporzione di richieste con latenza inferiore a 300 ms, la proporzione di richieste completate senza errore, la freshness dei dati in un sistema di streaming. Un Service Level Objective (SLO) è un target sul valore dell'SLI nel tempo, espresso tipicamente come percentile su finestra rolling: "il 99,9% delle richieste deve avere latenza < 300 ms su finestra rolling di 30 giorni". Un Service Level Agreement (SLA) è un contratto formale, spesso con penali economiche, che specifica le conseguenze del mancato rispetto degli SLO.
La distinzione operativa più importante è quella tra SLI e SLO. L'SLI è la misura grezza, continuamente aggiornata dalla telemetria; l'SLO è il target che esprime l'aspettativa di qualità negoziata. La scelta degli SLI dovrebbe essere guidata dalla prospettiva dell'utente, non dalla convenienza tecnica di misurazione. Per un'API REST, gli SLI candidati naturali sono la disponibilità (proporzione di richieste con risposta HTTP non-5xx) e la latenza (proporzione di richieste servite entro una soglia temporale). Per uno streaming pipeline, la freshness (ritardo tra l'evento e la sua disponibilità nell'output). Per un sistema batch, la correttezza e la completezza dell'output. Il SRE Workbook [6] raccomanda di misurare gli SLI il più vicino possibile al punto di interazione utente-servizio, per catturare degradazioni invisibili alle metriche lato server.
La formulazione matematica standard di uno SLO è:
$$\text{SLI}{[T-w, T]} = \frac{\text{eventi buoni}$$}}{\text{eventi validi totali}_{[T-w, T]}} \geq \text{target
dove $w$ è la finestra rolling (tipicamente 30 giorni), $T$ è il tempo corrente, e il target è espresso come proporzione (es. 0,999 per il 99,9%). La distinzione tra "eventi buoni" e "eventi validi" è una scelta di design non banale: richieste con errori 4xx sono tipicamente escluse dal denominatore, in quanto rappresentano errori del client piuttosto che del servizio; richieste durante finestre di manutenzione pianificata possono essere escluse o no, a seconda della policy adottata.
Error budget
L'error budget è il complemento dell'SLO: rappresenta la quantità di inaffidabilità che il servizio può tollerare senza violare il proprio obiettivo. Per un SLO del 99,9% su 30 giorni, l'error budget è lo 0,1% degli eventi validi nel periodo:
$$\text{Error budget} = (1 - \text{target SLO}) \times \text{eventi validi totali nel periodo}$$
Se il servizio riceve 10 milioni di richieste in 30 giorni con un SLO del 99,9%, l'error budget è 10.000 richieste fallite. Ogni errore in produzione, causato da un deploy difettoso, un'anomalia infrastrutturale o un bug, consuma una porzione di questo budget. L'error budget diventa il meccanismo formale che bilancia velocity di sviluppo e stabilità operativa [6]: finché il budget non è esaurito, i rilasci procedono secondo la cadenza standard; quando il budget si avvicina all'esaurimento, l'organizzazione attiva misure restrittive definite nell'error budget policy.
La progettazione degli SLO richiede equilibrio tra ambizione e sostenibilità. Un SLO eccessivamente stringente (99,999%) richiede investimenti proibitivi in ridondanza e capacità di risposta, e lascia un error budget così ridotto da bloccare ogni rilascio. Un SLO troppo blando (99%) rende accettabili quasi due giorni di inattività al mese, incompatibili con le aspettative degli utenti per servizi critici. La letteratura SRE raccomanda di calibrare il target sul livello di qualità attualmente erogato, incrementandolo gradualmente man mano che le capacità di engineering migliorano [5].
Progettazione degli alert: principi, routing ed escalation
Principi fondamentali
La progettazione degli alert è uno dei problemi più sottovalutati nell'ingegneria dell'affidabilità. Un sistema di alerting mal progettato genera alert noise, notifiche che non richiedono azione immediata, causando alert fatigue: gli ingegneri di on-call iniziano a ignorare le notifiche, aumentando il rischio che incidenti reali vengano persi. Il Google SRE Book [5] identifica quattro proprietà che ogni alert dovrebbe soddisfare: ogni alert dovrebbe indicare un'azione necessaria e immediata; ogni azione dovrebbe richiedere intelligenza umana; ogni alert dovrebbe descrivere un problema reale dal punto di vista dell'utente; l'azione da intraprendere dovrebbe essere documentata nel runbook associato.
Una distinzione fondamentale nell'alerting moderno è quella tra alert sintomatici e alert causali. Gli alert sintomatici reagiscono al sintomo osservato dall'utente (es. latenza p99 > 500 ms, tasso di errore HTTP 5xx > 1%); gli alert causali reagiscono alla causa probabile (es. CPU > 90%, disco > 80%). Il Google SRE Book [5] raccomanda di privilegiare gli alert sintomatici, perché catturano l'impatto reale sull'utente indipendentemente dalla causa, riducendo i falsi positivi causati da soglie su risorse interne che non si traducono necessariamente in degradazione del servizio.
Alerting basato sul burn rate
Il burn rate è il concetto che connette l'error budget all'alerting operativo. Definito nel capitolo "Alerting on SLOs" del Site Reliability Workbook [6], il burn rate misura la velocità con cui il servizio sta consumando il proprio error budget rispetto al tasso nominale:
$$\text{Burn rate} = \frac{\text{tasso di errore osservato}}{\text{tasso di errore consentito dall'SLO}}$$
Un burn rate pari a 1 indica un consumo del budget esattamente al ritmo previsto; un burn rate di 10 significa che il budget si esaurirà in un decimo del periodo di conformità (3 giorni anziché 30, per un periodo mensile). L'approccio multi-window multi-burn-rate, raccomandato dal SRE Workbook [6], combina due finestre temporali per ciascun livello di alert: una finestra lunga per rilevare il consumo sostenuto, e una finestra corta per confermare che il problema è attuale e non storico. Un alert si attiva solo quando entrambe le condizioni sono soddisfatte simultaneamente, eliminando il problema del reset time: non appena il tasso di errore nella finestra corta torna sotto soglia, l'alert si disattiva.
La configurazione raccomandata dal SRE Workbook [6] definisce più livelli di alert con combinazioni diverse di burn rate e finestre:
| Livello | Budget consumato | Burn rate | Finestra lunga | Finestra corta | Azione |
|---|---|---|---|---|---|
| Critico | 2% in 1 ora | 14,4× | 1 ora | 5 minuti | Page immediato |
| Alto | 5% in 6 ore | 6× | 6 ore | 30 minuti | Page |
| Medio | 10% in 3 giorni | 1× | 3 giorni | 6 ore | Ticket |
Questa logica garantisce che burn rate elevati, corrispondenti a incidenti acuti che esauriranno il budget in poche ore, generino notifiche immediate, mentre burn rate bassi corrispondenti a degradazioni lente siano gestiti con maggiore latenza e minore urgenza.
Alertmanager: routing, raggruppamento e deduplicazione
Prometheus Alertmanager è il componente responsabile della gestione degli alert generati dal server Prometheus [8]. Le sue funzioni principali sono quattro. Il raggruppamento consolida alert simili, stessa causa probabile, stesso servizio, stessa regione, in un'unica notifica, evitando che una partizione di rete generi centinaia di notifiche distinte anziché una sola. Il routing instrada gli alert verso i ricevitori corretti (PagerDuty, Slack, email, webhook) in base a un albero di regole con matching sulle label, consentendo di differenziare la destinazione per severità, servizio, orario o team responsabile. La deduplicazione opera sul fingerprint dell'alert, definito dall'insieme delle label che lo identificano univocamente: alert con lo stesso fingerprint vengono inviati come aggiornamento, non come nuova notifica. L'inibizione sopprime alert di livello inferiore quando un alert di livello superiore sullo stesso componente è già in firing: se un alert di "cluster unreachable" è attivo, tutti gli alert relativi ai singoli nodi del cluster vengono inibiti.
Strategie di escalation e on-call
L'escalation gerarchica assegna inizialmente la responsabilità al team più vicino al componente impattato, con una catena di escalation time-based nel caso in cui il primo livello non risponda entro un intervallo configurato. La costruzione di una rotazione di on-call sostenibile richiede attenzione a tre variabili: la frequenza degli alert fuori orario (il noise notturno causa burnout), la copertura della documentazione operativa (i runbook aggiornati riducono il MTTR), e il processo di post-incident review (ogni incidente significativo dovrebbe produrre miglioramenti concreti, non solo patch immediate) [5]. La pratica di alert hygiene, revisione periodica delle regole con metriche di attivazione e tasso di falsi positivi, è raccomandata per prevenire l'accumulo di regole obsolete [5, 8].
Architettura integrata: dallo stack al processo
Stack di osservabilità di riferimento
La convergenza tra le pratiche descritte nelle sezioni precedenti produce un'architettura di osservabilità a strati, in cui ciascun componente svolge un ruolo preciso e le integrazioni tra componenti creano valore emergente. Il livello di strumentazione incorpora il codice delle applicazioni con l'SDK OpenTelemetry, producendo i tre segnali (metriche, log, trace) in formato standardizzato. Il livello di raccolta aggrega e processa i segnali attraverso il Collector OpenTelemetry prima di distribuirli ai backend. Il livello di storage mantiene i dati in sistemi specializzati: Prometheus per le metriche, Loki per i log, Tempo o Jaeger per le trace. Il livello di visualizzazione e alerting, in Grafana, unifica le query sui tre datasource e ospita le regole di alerting che alimentano Alertmanager [4, 8, 9].
La ricerca accademica su sistemi di monitoring dei microservizi conferma che questo approccio a strati, con separazione delle responsabilità tra raccolta, storage e analisi, produce sistemi più resilienti e scalabili rispetto ad architetture monolitiche dove un singolo componente svolge tutte le funzioni [11]. La survey di Shan, Guo e Jin [11] documenta che la correlazione tra i tre segnali, il passaggio automatico da un alert su una metrica alla trace corrispondente, è il requisito più frequentemente citato dai team operativi come capacità differenziante rispetto al monitoring tradizionale.
Campionamento e gestione del volume
La proliferazione di strumenti di osservabilità ha generato nuove sfide di gestione del volume. Un'installazione matura di OpenTelemetry Collector può generare volumi di telemetria nell'ordine dei terabyte al giorno per sistemi ad alto traffico, richiedendo strategie di campionamento che introducono un trade-off tra completezza della visibilità e costo [4]. Il campionamento head-based, in cui la decisione di campionare una trace viene presa all'inizio della richiesta, è computazionalmente semplice ma non può prioritizzare le trace con errori o latenza anomala, che sono precisamente quelle di maggior interesse diagnostico. Il campionamento tail-based, in cui la decisione è presa dopo che tutti gli span sono stati raccolti, consente di selezionare le trace più rilevanti ma richiede il buffering di tutte le trace in corso, introducendo memory pressure significativa su sistemi ad alta throughput.
La configurazione del campionamento richiede una comprensione del profilo di traffico dell'applicazione e degli obiettivi diagnostici. Una strategia comune è campionare al 100% tutte le trace con errori o con latenza anomala (tail-based), e campionare al 1-10% il traffico nominale (head-based), ottenendo visibilità completa sui percorsi di guasto e visibilità statistica sul percorso nominale. Questa strategia ibrida è implementabile attraverso il processor tail_sampling del Collector OpenTelemetry, configurato con politiche di sampling basate su attributi degli span [4].
Limiti e problemi aperti
Il framework di osservabilità descritto presenta limiti strutturali che è opportuno esplicitare. La definizione di SLO per sistemi basati su intelligenza artificiale resta un problema aperto: la qualità dell'output di un modello generativo non è facilmente catturata da proxy metriche tradizionali come latenza e disponibilità, e le dimensioni di qualità rilevanti (accuratezza, assenza di allucinazioni, appropriatezza del tono) richiedono valutazioni che non si adattano a un framework di alerting automatico.
L'assunzione che gli SLI misurino ciò che è rilevante per l'utente è spesso più difficile da soddisfare di quanto appaia. Degradazioni lente della qualità dei dati, corruzione silente dello storage, accumulo di debito tecnico con impatto progressivo sulle prestazioni, questi scenari possono non violare alcun SLO nel breve termine eppure compromettere la reliability nel lungo periodo. Il framework SLO-based alerting è ottimizzato per problemi che si manifestano come variazioni rapide degli SLI; per i problemi che non si manifestano così, rimane necessario un layer complementare di alert infrastrutturali, con la consapevolezza che questo layer deve essere gestito con disciplina rigorosa per non reintrodurre il rumore che l'SLO-based alerting si propone di eliminare [5, 6].
La correlazione automatica tra i tre segnali, poter passare da un alert su una metrica alla trace corrispondente e da lì ai log del microservizio specifico, richiede l'inserimento esplicito di identificatori di correlazione (trace ID, span ID) in tutti e tre i segnali, nonché una piattaforma di visualizzazione che supporti questa navigazione. Grafana supporta queste correlazioni tramite la funzionalità "Explore" e le "Derived Fields", ma la configurazione richiede convenzioni coerenti di strumentazione su tutti i servizi, difficili da garantire in organizzazioni con team numerosi e autonomi [9].
Infine, la calibrazione dei burn rate e delle finestre temporali richiede un equilibrio tra sensibilità e specificità che dipende dal profilo di traffico del servizio. Servizi con traffico altamente variabile possono generare falsi positivi durante le fasi di traffico basso, quando un numero assoluto ridotto di errori produce un tasso di errore percentuale elevato. L'adozione di SLO con un minimum valid event count, una soglia minima di eventi al di sotto della quale la finestra non viene valutata, mitiga parzialmente il problema ma introduce una zona cieca durante i periodi di basso traffico [6].
Riferimenti
[1] B. H. Sigelman, L. A. Barroso, M. Burrows, P. Stephenson, M. Plakal, D. Beaver, S. Jaspan, and C. Shanbhag, "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure," Google Technical Report, 2010. https://research.google/pubs/dapper-a-large-scale-distributed-systems-tracing-infrastructure/
[2] C. Sridharan, Distributed Systems Observability, O'Reilly Media, 2018. https://www.oreilly.com/library/view/distributed-systems-observability/9781492033431/
[3] C. Majors, L. Fong-Jones, and G. Miranda, Observability Engineering: Achieving Production Excellence, O'Reilly Media, 2022. https://www.oreilly.com/library/view/observability-engineering/9781492076438/
[4] OpenTelemetry Authors, "OpenTelemetry Specification v1.55.0," CNCF, 2024. https://opentelemetry.io/docs/specs/otel/
[5] B. Beyer, C. Jones, J. Petoff, and N. R. Murphy, Site Reliability Engineering: How Google Runs Production Systems, O'Reilly Media, 2016. https://sre.google/sre-book/table-of-contents/
[6] B. Beyer, N. R. Murphy, D. K. Rensin, K. Kawahara, and S. Thorne, The Site Reliability Workbook: Practical Ways to Implement SRE, O'Reilly Media, 2018. https://sre.google/workbook/alerting-on-slos/
[7] CNCF, "Prometheus Project Journey Report," Cloud Native Computing Foundation, 2020. https://www.cncf.io/reports/prometheus-project-journey-report/
[8] Prometheus Authors, "Prometheus Documentation — Alertmanager," 2024. https://prometheus.io/docs/alerting/latest/alertmanager/
[9] Grafana Labs, "Grafana Documentation," 2024. https://grafana.com/docs/grafana/latest/
[10] M. Cinque, D. Cotroneo, and A. Pecchia, "Event Logs for the Analysis of Software Failures: A Rule-Based Approach," IEEE Transactions on Software Engineering, vol. 39, no. 6, pp. 806–821, 2013. https://dl.acm.org/doi/10.1109/TSE.2012.67
[11] E. Shan, T. Guo, and H. Jin, "On Observability and Monitoring of Distributed Systems: An Industry Interview Study," in Proc. International Conference on Service-Oriented Computing (ICSOC), Lecture Notes in Computer Science, vol. 11895, Springer, 2019. https://arxiv.org/abs/1907.12240
[12] CNCF, "OpenTelemetry Project — CNCF Incubating," Cloud Native Computing Foundation, 2024. https://www.cncf.io/projects/opentelemetry/