Pular para o conteúdo principal
Versão: Em desenvolvimento — v0.2.7

Métricas

Use as métricas para medir volume, latência e saturação de forma agregada. Identificadores de requisição, de entidade e mensagens de erro pertencem aos traces e aos logs, não às métricas.

As métricas são habilitadas por padrão. Consulte Observabilidade para escolher entre /metrics e a exportação OTLP.

Criar instrumentos​

Crie os instrumentos uma vez, na inicialização do serviço, e mantenha a referência:

package payments

import (
"github.com/colibriproject-dev/colibri-sdk-go/pkg/base/monitoring"
monitoringbase "github.com/colibriproject-dev/colibri-sdk-go/pkg/base/monitoring/colibri-monitoring-base"
)

type paymentMetrics struct {
authorized monitoringbase.Counter
latency monitoringbase.HistogramRecorder
}

// newPaymentMetrics roda depois de colibri.InitializeApp, por exemplo no construtor do serviço.
func newPaymentMetrics() *paymentMetrics {
return &paymentMetrics{
authorized: monitoring.Counter(
"payments.authorized",
"Number of payment authorizations",
"{payment}",
),
latency: monitoring.Histogram(
"payments.authorization.duration",
"Duration of payment authorizations",
"s",
),
}
}
FunçãoInstrumentoRegistro
monitoring.Countercontador monotônicoAddAttrs(ctx, int64, attrs)
monitoring.Histogramdistribuição de valoresRecordAttrs(ctx, float64, attrs)
monitoring.Gaugevalor atual informado pela aplicaçãoRecordAttrs(ctx, float64, attrs)
monitoring.ObservableGaugevalor atual lido a cada coletacallback
Crie os instrumentos depois de colibri.InitializeApp()

Antes da inicialização do monitoramento, as funções retornam instrumentos noop. Uma variável de pacote como var c = monitoring.Counter(...) nunca registra nada. Com as métricas desligadas, os instrumentos também são noop, então o código não precisa verificar a configuração.

Atributos​

Atributos são passados como monitoringbase.Attrs. O valor guarda em cache sua representação no provider, então construa uma vez e reutilize: registrar com um Attrs reutilizado não aloca memória.

// Construídos uma vez, fora do caminho crítico: um valor por combinação.
var (
cardApproved = monitoringbase.NewAttrs("method", "card", "result", "success")
cardDeclined = monitoringbase.NewAttrs("method", "card", "result", "error")
)

func (s *PaymentService) authorize(ctx context.Context, order Order) error {
start := time.Now()
err := s.gateway.Authorize(ctx, order)

attrs := cardApproved
if err != nil {
attrs = cardDeclined
}
s.metrics.authorized.AddAttrs(ctx, 1, attrs)
s.metrics.latency.RecordAttrs(ctx, time.Since(start).Seconds(), attrs)

return err
}

NewAttrs recebe pares chave/valor alternados. Uma chave final sem valor é ignorada e chaves duplicadas mantêm o último valor.

Quando os valores só são conhecidos em tempo de execução, mas pertencem a um conjunto limitado, construa cada Attrs uma vez e guarde-os em um mapa.

Migrar de Add e Record​

Add e Record com map[string]string continuam funcionando, mas estão obsoletos: o mapa é convertido em cada chamada.

// Antes
requests.Add(ctx, 1, map[string]string{"route": "/api/users"})

// Depois: construa uma vez e reutilize
var usersRoute = monitoringbase.NewAttrs("route", "/api/users")
requests.AddAttrs(ctx, 1, usersRoute)

// Ou, para migrar mecanicamente a partir de um mapa existente
requests.AddAttrs(ctx, 1, monitoringbase.AttrsFromMap(attributes))

Gauges observáveis​

Gauge registra um valor que a aplicação informa. Para um valor amostrado, como tamanho de pool, profundidade de fila ou quantidade de entradas em cache, registre um gauge observável. O callback roda a cada coleta:

highPriority := monitoringbase.NewAttrs("priority", "high")

registration := monitoring.ObservableGauge(
"orders.pending",
"Number of orders waiting for payment",
"{order}",
func(ctx context.Context) []monitoringbase.Observation {
return []monitoringbase.Observation{
{Value: float64(queue.Len()), Attributes: highPriority},
}
},
)
defer registration.Unregister()

Registre cada nome uma única vez: cada chamada registra um callback próprio, e quem chama é responsável por encerrá-lo com Unregister quando o recurso observado deixar de existir.

Convenções​

Nomes​

  • Reutilize as convenções semânticas do OpenTelemetry quando elas definem a métrica. As métricas do servidor HTTP são http.server.request.duration e relacionadas, não um nome próprio.
  • Nos demais casos, use nomes de domínio: <domínio>.<coisa>[.<qualificador>], em minúsculas, separados por ponto e com snake_case dentro de um segmento, como messaging.process.duration e messaging.in_flight.
  • Não use o prefixo colibri.*. A origem da métrica fica no catálogo, não no nome.
  • Não coloque unidade ou tipo no nome. Evite _seconds, _bytes, _total e _count: a unidade vai no campo próprio, e o exportador Prometheus adiciona os sufixos.
  • Nomeie o que é medido, não o resultado. Separe sucesso e falha com o atributo result, não com duas métricas.

Nomes em /metrics​

O reader Prometheus traduz o nome OpenTelemetry: pontos viram sublinhados, a unidade é adicionada como sufixo e contadores recebem _total. Dashboards e regras de alerta consultam o nome traduzido.

Nome OpenTelemetryUnidadeNome no Prometheus
messaging.published (contador){message}messaging_published_total
messaging.process.duration (histograma)smessaging_process_duration_seconds
storage.transferred (histograma)Bystorage_transferred_bytes
messaging.in_flight (gauge){message}messaging_in_flight

Anotações como {message} não geram sufixo. Histogramas aparecem como as séries _bucket, _sum e _count do nome traduzido. Os atributos viram labels da mesma forma: http.response.status_code vira http_response_status_code.

/metrics também serve os coletores de Go e de processo do cliente Prometheus (go_gc_duration_seconds, go_memstats_*, process_*). Eles não são traduzidos e nunca chegam ao exportador OTLP.

Unidades​

As unidades seguem o UCUM, como exigem as convenções do OpenTelemetry.

MedidaUnidadeExemplo
Duraçõess, como float64messaging.process.duration
TamanhosBystorage.transferred
Contagensanotação entre chaves{message}, {request}
Razões sem dimensão1taxa de acerto de cache
Percentuais%go.config.gogc

Registre durações em segundos, nunca em milissegundos. Os histogramas de duração do SDK usam os limites de monitoringbase.DurationBucketsSeconds, que vão de 5 ms a uma hora.

Cardinalidade​

Cada combinação distinta de valores de atributos é uma série temporal armazenada e cobrada separadamente. Um atributo com valores ilimitados transforma uma métrica em milhões de séries, então todo atributo deve vir de um conjunto pequeno e fixo de valores.

Nunca use como atributo de métrica:

  • identificadores de requisição: correlationId, messageId, IDs de trace e span;
  • identificadores de entidade: userId, tenantId, IDs de pedido, pagamento ou documento, e-mails;
  • chaves de objetos no storage e nomes de arquivo;
  • caminhos de URL e query strings: use o template da rota (/users/:id), nunca o caminho (/users/42);
  • mensagens de erro, texto livre e timestamps.

Além disso:

  • use no máximo 5 atributos por métrica, menos quando possível;
  • mantenha cada métrica abaixo de cerca de mil séries por instância, multiplicando a quantidade de valores de cada atributo para estimar;
  • lembre que valores definidos pela aplicação também contam: o action de uma mensagem vira atributo das métricas de mensageria, então deve ser um nome de evento de um conjunto fixo, nunca um identificador;
  • represente resultados com result, usando success e error, e panic quando aplicável.
Exceções conhecidas

Duas métricas de fora do SDK flexibilizam essa política. db.sql.client.latency e db.sql.client.calls, do otelsql, carregam db.sql.error, a mensagem de uma chamada com falha. As métricas http.server.* carregam server.address, lido do cabeçalho Host: atrás de um ingress ou load balancer que só roteia hosts conhecidos ele permanece limitado, mas um serviço exposto diretamente pode receber qualquer valor.

Testar métricas​

monitoringtest.Install(t) substitui o monitoramento por um reader em memória durante o teste e o instala também como meter provider global, para que instrumentações de terceiros registrem nele. Instale-o antes de criar os instrumentos testados:

package orders_test

import (
"context"
"testing"

"github.com/colibriproject-dev/colibri-sdk-go/pkg/base/monitoring"
monitoringbase "github.com/colibriproject-dev/colibri-sdk-go/pkg/base/monitoring/colibri-monitoring-base"
"github.com/colibriproject-dev/colibri-sdk-go/pkg/base/monitoring/monitoringtest"
"github.com/stretchr/testify/assert"
)

func TestCreateOrder(t *testing.T) {
recorder := monitoringtest.Install(t)

// Em um teste real, crie aqui o serviço que cria os instrumentos e exercite-o.
counter := monitoring.Counter("orders.created", "Number of orders created", "{order}")
counter.AddAttrs(context.Background(), 1, monitoringbase.NewAttrs("channel", "web"))

created := recorder.Metric(t, "orders.created")
monitoringtest.AssertShape(t, created, "{order}", "channel")
assert.Equal(t, int64(1), monitoringtest.CounterValue(t, created, "channel", "web"))
}
FunçãoUso
recorder.Collect(t)Retorna todas as métricas registradas, indexadas pelo nome.
recorder.Metric(t, name)Retorna uma métrica e falha o teste se ela não existir.
monitoringtest.AssertShape(t, m, unit, keys...)Verifica a unidade e que cada ponto carrega exatamente as chaves informadas.
monitoringtest.CounterValue(t, m, kv...)Valor de um contador para um conjunto de atributos.
monitoringtest.HistogramCount(t, m, kv...)Quantidade e soma registradas em um histograma.
monitoringtest.GaugeValue(t, m, kv...)Valor atual de um gauge.

Tudo é restaurado ao final do teste.

Toda métrica emitida pelo SDK está registrada em um catálogo, com o nome como constante exportada (monitoring.MetricMessagingPublished), tipo, unidade, atributos e origem. monitoring.Catalog() e monitoring.LookupMetric(name) expõem o catálogo para ferramentas, como verificações de que dashboards consultam apenas métricas existentes.

Emitidas pelo SDK​

MétricaTipoUnidadeAtributosMódulo
http.server.request.durationhistogramashttp.request.method, http.response.status_code, http.route, server.address, url.schemerestserver
http.server.active_requestsup-down counter{request}http.request.method, server.address, url.schemerestserver
http.server.request.body.sizehistogramaByhttp.request.method, http.response.status_code, http.route, server.address, url.schemerestserver
http.server.response.body.sizehistogramaByhttp.request.method, http.response.status_code, http.route, server.address, url.schemerestserver
http.server.panic.recoveredcontador{panic}http.request.method, http.routerestserver
messaging.publishedcontador{message}topic, resultmessaging
messaging.consumedcontador{message}queue, action, resultmessaging
messaging.process.durationhistogramasqueue, action, resultmessaging
messaging.rejectedcontador{message}queue, action, reasonmessaging
messaging.in_flightgauge observável{message}queuemessaging
messaging.consume.laghistogramasqueue, actionmessaging
storage.operationcontador{operation}operation, resultstorage
storage.operation.durationhistogramasoperation, resultstorage
storage.transferredhistogramaByoperationstorage

Valores dos atributos​

  • result é success ou error; mensagens consumidas também usam panic. reason é error ou panic.
  • action é o nome do evento informado pela aplicação em Publish.
  • operation é upload, download ou delete.
  • http.route é o template da rota. Um panic disparado em um middleware, antes de o handler definir o template, é registrado com a rota correspondente, ou / para um middleware global.
  • messaging.consume.lag é medido do horário de publicação informado pelo broker até o momento em que o consumer recebe a mensagem. Mensagens sem horário de publicação não são registradas, e um relógio adiantado no produtor é registrado como zero. A profundidade da fila continua sendo reportada pelo broker.
  • messaging.rejected conta mensagens com Nack sem requeue. O SDK as deixa para o tratamento de dead-letter do broker, então se uma mensagem chegou de fato a uma DLQ é informação do broker, não do SDK.

Emitidas por instrumentações de terceiros​

PrefixoOrigemMóduloConteúdo
http.client.*otelhttprestclientduração e tamanho do corpo das requisições do cliente HTTP
db.sql.client.*otelsqlsqlDBlatência e quantidade de chamadas SQL
db.sql.connections.*otelsqlsqlDBconexões abertas, ociosas e ativas, esperas e fechamentos do pool
db.client.connections.*redisotelcacheDBuso, limites, esperas e timeouts do pool Redis
go.*runtimemonitoringmemória, alocações, GC, goroutines e GOMAXPROCS
go_*, process_*cliente Prometheusrestservercoletores de Go e de processo, apenas em /metrics

As métricas de runtime obsoletas (runtime.uptime, process.runtime.go.*) só são emitidas com OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=true.

A lista completa, com cada métrica de terceiros e seus atributos, está na referência de métricas do SDK.

Para visualizar e alertar sobre essas métricas, consulte Dashboards e alertas.