Le applicazioni moderne necessitano di dati quantitativi per comprendere il loro comportamento e le prestazioni. Quante richieste stiamo gestendo? Quanto tempo impiegano? Stiamo esaurendo le risorse? Le metriche aiutano a rispondere a queste domande fornendo misurazioni numeriche del funzionamento del tuo servizio.
OpenTelemetry fornisce diversi strumenti di misurazione, ciascuno progettato per esigenze specifiche. Ogni strumento è definito da:
http.requests.total
)ms
, bytes
)Esploriamo ogni tipo di strumento:
Questi strumenti vengono chiamati direttamente nel codice quando accade qualcosa:
Counter Un valore che può solo aumentare, come il contachilometri di un’auto:
// Contatore per le richieste HTTP totali
requestCounter, _ := meter.Int64Counter("http.requests.total",
metric.WithDescription("Numero totale di richieste HTTP"),
metric.WithUnit("{requests}"))
// Utilizzo: Incrementa quando ricevi una richiesta
requestCounter.Add(ctx, 1)
Perfetto per:
UpDownCounter Un valore che può aumentare o diminuire, come gli elementi in una coda:
// Contatore bidirezionale per gli elementi in coda
queueSize, _ := meter.Int64UpDownCounter("queue.items",
metric.WithDescription("Elementi attuali in coda"),
metric.WithUnit("{items}"))
// Utilizzo: Aggiungi quando inserisci, sottrai quando rimuovi
queueSize.Add(ctx, 1) // Elemento aggiunto
queueSize.Add(ctx, -1) // Elemento rimosso
Perfetto per:
Histogram Traccia la distribuzione dei valori, come la durata delle richieste:
// Istogramma per la durata delle richieste HTTP
latency, _ := meter.Float64Histogram("http.request.duration",
metric.WithDescription("Durata delle richieste HTTP"),
metric.WithUnit("ms"))
// Utilizzo: Registra il valore quando la richiesta è completata
latency.Record(ctx, time.Since(start).Milliseconds())
Perfetto per:
Questi strumenti vengono raccolti periodicamente tramite callback che registri:
Contatore Asincrono Per valori che solo aumentano, ma hai accesso solo al totale:
// Contatore asincrono per i byte ricevuti
bytesReceived, _ := meter.Int64ObservableCounter("network.bytes.received",
metric.WithDescription("Totale byte ricevuti"),
metric.WithUnit("By"))
// Utilizzo: Registra callback per raccogliere il valore corrente
meter.RegisterCallback([]instrument.Asynchronous{bytesReceived},
func(ctx context.Context) {
bytesReceived.Observe(ctx, getNetworkStats().TotalBytesReceived)
})
Perfetto per:
Contatore Bidirezionale Asincrono Per valori che possono cambiare in entrambe le direzioni, ma vedi solo lo stato corrente:
// Contatore bidirezionale asincrono per le goroutine
goroutines, _ := meter.Int64ObservableUpDownCounter("system.goroutines",
metric.WithDescription("Numero corrente di goroutine"),
metric.WithUnit("{goroutines}"))
// Utilizzo: Registra callback per raccogliere il valore corrente
meter.RegisterCallback([]instrument.Asynchronous{goroutines},
func(ctx context.Context) {
goroutines.Observe(ctx, int64(runtime.NumGoroutine()))
})
Perfetto per:
Gauge Asincrono Per misurazioni del valore corrente che campioni periodicamente:
// Gauge asincrono per l'utilizzo della CPU
cpuUsage, _ := meter.Float64ObservableGauge("system.cpu.usage",
metric.WithDescription("Percentuale di utilizzo CPU"),
metric.WithUnit("1"))
// Utilizzo: Registra callback per raccogliere il valore corrente
meter.RegisterCallback([]instrument.Asynchronous{cpuUsage},
func(ctx context.Context) {
cpuUsage.Observe(ctx, getCPUUsage())
})
Perfetto per:
Poniti queste domande:
Casi d’uso comuni:
Clue strumenta automaticamente diverse metriche chiave per il tuo servizio. Queste forniscono visibilità immediata senza scrivere codice:
Quando avvolgi i tuoi handler HTTP con il middleware OpenTelemetry:
handler = otelhttp.NewHandler(handler, "service")
Ottieni automaticamente:
Quando crei un server gRPC con strumentazione OpenTelemetry:
server := grpc.NewServer(
grpc.StatsHandler(otelgrpc.NewServerHandler()))
Ottieni automaticamente:
Mentre le metriche automatiche sono utili, spesso hai bisogno di tracciare misurazioni specifiche del business. Ecco come creare e utilizzare metriche personalizzate efficacemente:
Prima, ottieni un meter per il tuo servizio:
meter := otel.Meter("myservice")
Poi crea le metriche necessarie:
Esempio Counter: Traccia eventi di business
// Contatore per gli ordini totali
orderCounter, _ := meter.Int64Counter("orders.total",
metric.WithDescription("Numero totale di ordini elaborati"),
metric.WithUnit("{orders}"))
Esempio Histogram: Misura tempi di elaborazione
// Istogramma per i tempi di elaborazione
processingTime, _ := meter.Float64Histogram("order.processing_time",
metric.WithDescription("Tempo impiegato per elaborare gli ordini"),
metric.WithUnit("ms"))
Esempio Gauge: Monitora profondità coda
// Gauge per la profondità della coda
queueDepth, _ := meter.Int64UpDownCounter("orders.queue_depth",
metric.WithDescription("Numero corrente di ordini in coda"),
metric.WithUnit("{orders}"))
Vediamo un esempio completo che dimostra come utilizzare diversi tipi di metriche in uno scenario reale. Questo esempio mostra come monitorare un sistema di elaborazione ordini:
func processOrder(ctx context.Context, order *Order) error {
// Traccia ordini totali (counter)
// Incrementiamo il contatore di 1 per ogni ordine, aggiungendo attributi per l'analisi
orderCounter.Add(ctx, 1,
attribute.String("type", order.Type),
attribute.String("customer", order.CustomerID))
// Misura tempo di elaborazione (histogram)
// Usiamo un defer per assicurarci di registrare sempre la durata, anche se la funzione termina prima
start := time.Now()
defer func() {
processingTime.Record(ctx,
time.Since(start).Milliseconds(),
attribute.String("type", order.Type))
}()
// Monitora profondità coda (gauge)
// Tracciamo la dimensione della coda incrementando quando aggiungiamo e decrementando quando finito
queueDepth.Add(ctx, 1) // Incrementa quando aggiungi alla coda
defer queueDepth.Add(ctx, -1) // Decrementa quando finito
return processOrderInternal(ctx, order)
}
Questo esempio dimostra diverse best practice:
Gli Indicatori di Livello di Servizio (SLI) sono metriche chiave che aiutano a comprendere la salute e le prestazioni del tuo servizio. I quattro segnali d’oro (Latenza, Traffico, Errori e Saturazione) forniscono una visione completa del comportamento del tuo servizio. Vediamo come implementare ciascuno:
La latenza misura quanto tempo serve per gestire le richieste. Questo esempio mostra come tracciare la durata delle richieste in un middleware HTTP:
// Crea un istogramma per tracciare la durata delle richieste
requestDuration, _ := meter.Float64Histogram("http.request.duration",
metric.WithDescription("Durata delle richieste HTTP"),
metric.WithUnit("ms"))
// Middleware per misurare la durata delle richieste
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
// Registra la durata con il percorso della richiesta come attributo
requestDuration.Record(r.Context(),
time.Since(start).Milliseconds(),
attribute.String("path", r.URL.Path))
})
}
Il traffico misura la domanda sul tuo sistema. Questo esempio conta le richieste HTTP:
// Crea un contatore per le richieste in arrivo
requestCount, _ := meter.Int64Counter("http.request.count",
metric.WithDescription("Totale richieste HTTP"),
metric.WithUnit("{requests}"))
// Middleware per contare le richieste
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Incrementa il contatore con attributi metodo e percorso
requestCount.Add(r.Context(), 1,
attribute.String("method", r.Method),
attribute.String("path", r.URL.Path))
next.ServeHTTP(w, r)
})
}
Il tracciamento degli errori aiuta a identificare problemi nel tuo servizio. Questo esempio conta gli errori HTTP 5xx:
// Crea un contatore per gli errori del server
errorCount, _ := meter.Int64Counter("http.error.count",
metric.WithDescription("Totale errori HTTP"),
metric.WithUnit("{errors}"))
// Middleware per tracciare gli errori
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Usa un ResponseWriter personalizzato per catturare il codice di stato
sw := &statusWriter{ResponseWriter: w}
next.ServeHTTP(sw, r)
// Conta gli errori 5xx
if sw.status >= 500 {
errorCount.Add(r.Context(), 1,
attribute.Int("status_code", sw.status),
attribute.String("path", r.URL.Path))
}
})
}
La saturazione misura quanto è “pieno” il tuo servizio. Questo esempio monitora le risorse di sistema:
// Crea gauge per l'utilizzo di CPU e memoria
cpuUsage, _ := meter.Float64ObservableGauge("system.cpu.usage",
metric.WithDescription("Percentuale di utilizzo CPU"),
metric.WithUnit("1"))
memoryUsage, _ := meter.Int64ObservableGauge("system.memory.usage",
metric.WithDescription("Utilizzo memoria in bytes"),
metric.WithUnit("By"))
// Avvia una goroutine per raccogliere periodicamente le metriche di sistema
go func() {
ticker := time.NewTicker(time.Second)
for range ticker.C {
ctx := context.Background()
// Aggiorna utilizzo CPU
var cpu float64
cpuUsage.Observe(ctx, getCPUUsage())
// Aggiorna utilizzo memoria usando statistiche runtime
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
memoryUsage.Observe(ctx, int64(mem.Alloc))
}
}()
Una volta strumentato il codice con le metriche, devi esportarle in un sistema di monitoraggio. Ecco esempi di esportatori comuni:
Prometheus è una scelta popolare per la raccolta di metriche. Ecco come configurarlo:
// Crea un esportatore Prometheus con limiti personalizzati per gli istogrammi
exporter, err := prometheus.New(prometheus.Config{
DefaultHistogramBoundaries: []float64{
1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, // in millisecondi
},
})
I limiti degli istogrammi sono cruciali per misurazioni accurate della latenza. Scegli limiti che coprano il tuo intervallo di latenza previsto.
OTLP è il protocollo nativo per OpenTelemetry. Usalo per inviare metriche ai collettori:
// Crea un esportatore OTLP che si connette a un collettore
exporter, err := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithEndpoint("collector:4317"),
otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials()))
Ricorda di configurare TLS appropriatamente negli ambienti di produzione.
Segui un pattern consistente per rendere le metriche scopribili e comprensibili:
<namespace>.<tipo>.<nome>
Per esempio:
http.request.duration
- Latenza richieste HTTPdatabase.connection.count
- Numero di connessioni DBorder.processing.time
- Durata elaborazione ordiniIl pattern aiuta gli utenti a trovare e comprendere le metriche senza consultare la documentazione.
Specifica sempre le unità nelle descrizioni delle metriche per evitare ambiguità:
ms
(millisecondi), s
(secondi)By
(byte){requests}
, {errors}
1
(adimensionale)Usare unità consistenti rende le metriche comparabili e previene errori di conversione.
Considera questi fattori per mantenere buone prestazioni:
Intervalli di raccolta: Scegli intervalli appropriati basati sulla volatilità della metrica
Aggiornamenti in batch: Raggruppa gli aggiornamenti delle metriche quando possibile
// Invece di questo:
counter.Add(ctx, 1)
counter.Add(ctx, 1)
// Fai questo:
counter.Add(ctx, 2)
Crescita della cardinalità: Monitora il numero di serie temporali uniche
Aggregazione: Pre-aggrega metriche ad alto volume
// Invece di registrare ogni richiesta:
histogram.Record(ctx, duration)
// Raggruppa e registra sommari:
type window struct {
count int64
sum float64
}
Documenta ogni metrica accuratamente per aiutare gli utenti a comprenderle e utilizzarle efficacemente:
Documentazione richiesta:
Esempio di documentazione:
// http.request.duration misura il tempo impiegato per elaborare le richieste HTTP.
// Unità: millisecondi
// Attributi:
// - method: Metodo HTTP (GET, POST, ecc.)
// - path: Percorso della richiesta
// - status_code: Codice di stato HTTP
// Frequenza di aggiornamento: Per richiesta
// Conservazione: 30 giorni
requestDuration, _ := meter.Float64Histogram(
"http.request.duration",
metric.WithDescription("Tempo impiegato per elaborare le richieste HTTP"),
metric.WithUnit("ms"))
Per informazioni più dettagliate sulle metriche:
OpenTelemetry Metrics La guida ufficiale ai concetti e all’implementazione delle metriche OpenTelemetry.
Convenzioni Semantiche delle Metriche Nomi e attributi standard per metriche comuni.
Best Practice Prometheus Eccellente guida sulla denominazione delle metriche e le etichette.
Quattro Segnali d’Oro La guida di Google alle metriche essenziali dei servizi.
Queste risorse forniscono approfondimenti sull’implementazione delle metriche e le best practice.
Gli attributi forniscono contesto alle tue metriche, rendendole più utili per l’analisi. Tuttavia, scegliere gli attributi giusti richiede un’attenta considerazione per evitare problemi di prestazioni e mantenere la qualità dei dati.
Buoni attributi da includere:
customer_type
, order_status
, error_code
Questi attributi hanno un insieme limitato di valori possibili e forniscono raggruppamenti significativi.subscription_tier
, payment_method
Questi aiutano a correlare le metriche con i risultati di business.region
, datacenter
, instance_type
Questi permettono di analizzare le prestazioni per componente infrastrutturale.