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ção | Instrumento | Registro |
|---|---|---|
monitoring.Counter | contador monotônico | AddAttrs(ctx, int64, attrs) |
monitoring.Histogram | distribuição de valores | RecordAttrs(ctx, float64, attrs) |
monitoring.Gauge | valor atual informado pela aplicação | RecordAttrs(ctx, float64, attrs) |
monitoring.ObservableGauge | valor atual lido a cada coleta | callback |
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.duratione 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 comsnake_casedentro de um segmento, comomessaging.process.durationemessaging.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,_totale_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 OpenTelemetry | Unidade | Nome no Prometheus |
|---|---|---|
messaging.published (contador) | {message} | messaging_published_total |
messaging.process.duration (histograma) | s | messaging_process_duration_seconds |
storage.transferred (histograma) | By | storage_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.
| Medida | Unidade | Exemplo |
|---|---|---|
| Durações | s, como float64 | messaging.process.duration |
| Tamanhos | By | storage.transferred |
| Contagens | anotação entre chaves | {message}, {request} |
| Razões sem dimensão | 1 | taxa 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
actionde 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, usandosuccesseerror, epanicquando aplicável.
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ção | Uso |
|---|---|
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.
Catálogo
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étrica | Tipo | Unidade | Atributos | Módulo |
|---|---|---|---|---|
http.server.request.duration | histograma | s | http.request.method, http.response.status_code, http.route, server.address, url.scheme | restserver |
http.server.active_requests | up-down counter | {request} | http.request.method, server.address, url.scheme | restserver |
http.server.request.body.size | histograma | By | http.request.method, http.response.status_code, http.route, server.address, url.scheme | restserver |
http.server.response.body.size | histograma | By | http.request.method, http.response.status_code, http.route, server.address, url.scheme | restserver |
http.server.panic.recovered | contador | {panic} | http.request.method, http.route | restserver |
messaging.published | contador | {message} | topic, result | messaging |
messaging.consumed | contador | {message} | queue, action, result | messaging |
messaging.process.duration | histograma | s | queue, action, result | messaging |
messaging.rejected | contador | {message} | queue, action, reason | messaging |
messaging.in_flight | gauge observável | {message} | queue | messaging |
messaging.consume.lag | histograma | s | queue, action | messaging |
storage.operation | contador | {operation} | operation, result | storage |
storage.operation.duration | histograma | s | operation, result | storage |
storage.transferred | histograma | By | operation | storage |
Valores dos atributos
resultésuccessouerror; mensagens consumidas também usampanic.reasonéerroroupanic.actioné o nome do evento informado pela aplicação emPublish.operationéupload,downloadoudelete.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.rejectedconta mensagens comNacksem 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
| Prefixo | Origem | Módulo | Conteúdo |
|---|---|---|---|
http.client.* | otelhttp | restclient | duração e tamanho do corpo das requisições do cliente HTTP |
db.sql.client.* | otelsql | sqlDB | latência e quantidade de chamadas SQL |
db.sql.connections.* | otelsql | sqlDB | conexões abertas, ociosas e ativas, esperas e fechamentos do pool |
db.client.connections.* | redisotel | cacheDB | uso, limites, esperas e timeouts do pool Redis |
go.* | runtime | monitoring | memória, alocações, GC, goroutines e GOMAXPROCS |
go_*, process_* | cliente Prometheus | restserver | coletores 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.