Monitoramento em tempo real do Watermill usando o Prometheus
Métricas
O Watermill pode ser monitorado usando decoradores para publicadores/assinantes e middleware para manipuladores. Fornecemos uma implementação padrão usando o cliente oficial do Prometheus para Go.
O pacote components/metrics
exporta PrometheusMetricsBuilder
, que fornece funções convenientes para encapsular publicadores, assinantes e manipuladores para atualizar o registro do Prometheus relevante:
Código-fonte completo: github.com/ThreeDotsLabs/watermill/components/metrics/builder.go
// ...
// O PrometheusMetricsBuilder fornece métodos para decorar publicadores, assinantes e manipuladores.
type PrometheusMetricsBuilder struct {
// O PrometheusRegistry pode preencher um registro do Prometheus existente ou estar vazio para usar o registro padrão.
PrometheusRegistry prometheus.Registerer
Namespace string
Subsystem string
}
// AddPrometheusRouterMetrics é uma função conveniente para adicionar middleware métrico a todos os manipuladores no roteador de mensagens. Também decora os publicadores e assinantes dos manipuladores.
func (b PrometheusMetricsBuilder) AddPrometheusRouterMetrics(r *message.Router) {
// ...
Encapsulando Publicadores, Assinantes e Manipuladores
Se estiver usando o roteador do Watermill (o que é recomendado na maioria dos casos), você pode usar a função conveniente AddPrometheusRouterMetrics
para garantir que todos os manipuladores adicionados a este roteador sejam encapsulados para atualizar o registro do Prometheus, bem como seus publicadores e assinantes:
Código-fonte completo: github.com/ThreeDotsLabs/watermill/components/metrics/builder.go
// ...
// AddPrometheusRouterMetrics é uma função conveniente para adicionar middleware métrico a todos os manipuladores no roteador de mensagens. Também decora os publicadores e assinantes dos manipuladores.
func (b PrometheusMetricsBuilder) AddPrometheusRouterMetrics(r *message.Router) {
r.AddPublisherDecorators(b.DecoratePublisher)
r.AddSubscriberDecorators(b.DecorateSubscriber)
r.AddMiddleware(b.NewRouterMiddleware().Middleware)
}
// ...
Exemplo de uso de AddPrometheusRouterMetrics
:
Código-fonte completo: github.com/ThreeDotsLabs/watermill/_examples/basic/4-metrics/main.go
// ...
// Deixamos os parâmetros de namespace e subsistema vazios
metricsBuilder := metrics.NewPrometheusMetricsBuilder(prometheusRegistry, "", "")
metricsBuilder.AddPrometheusRouterMetrics(router)
// ...
No trecho de código acima, deixamos os parâmetros namespace
e subsystem
vazios. A biblioteca de cliente do Prometheus usa esses parâmetros para prefixar os nomes das métricas. Você pode querer usar namespace ou subsistema, mas observe que isso afetará os nomes das métricas, então você precisa ajustar o painel do Grafana de acordo.
Publicadores e assinantes independentes também podem ser decorados usando métodos dedicados de PrometheusMetricsBuilder
:
Código-fonte completo: github.com/ThreeDotsLabs/watermill/_examples/basic/4-metrics/main.go
// ...
subWithMetrics, err := metricsBuilder.DecorateSubscriber(pubSub)
if err != nil {
panic(err)
}
pubWithMetrics, err := metricsBuilder.DecoratePublisher(pub)
if err != nil {
panic(err)
}
// ...
Expor Endpoint /metrics
De acordo com o princípio de funcionamento do Prometheus, um serviço precisa expor um ponto de extremidade HTTP para coleta de dados. Conventionalmente, este é um ponto de extremidade GET e o caminho geralmente é /metrics
.
Para fornecer este ponto de extremidade, existem duas funções convenientes disponíveis, uma usando o Registro do Prometheus previamente criado e a outra criando simultaneamente um novo Registro:
Código fonte completo: github.com/ThreeDotsLabs/watermill/components/metrics/http.go
// ...
// CreateRegistryAndServeHTTP estabelece um servidor HTTP no endereço fornecido para expor o ponto de extremidade /metrics ao Prometheus.
// Retorna um novo Registro do Prometheus (para registro de métricas) e uma função de cancelamento para desligar o servidor.
func CreateRegistryAndServeHTTP(addr string) (registry *prometheus.Registry, cancel func()) {
registry = prometheus.NewRegistry()
return registry, ServeHTTP(addr, registry)
}
// ServeHTTP estabelece um servidor HTTP no endereço fornecido para expor o ponto de extremidade /metrics ao Prometheus.
// Aceita um Registro do Prometheus existente e retorna uma função de cancelamento para desligar o servidor.
func ServeHTTP(addr string, registry *prometheus.Registry) (cancel func()) {
// ...
Aqui está um exemplo de uso:
Código fonte completo: github.com/ThreeDotsLabs/watermill/_examples/basic/4-metrics/main.go
// ...
prometheusRegistry, closeMetricsServer := metrics.CreateRegistryAndServeHTTP(*metricsAddr)
defer closeMetricsServer()
// Deixamos o namespace e subsistema vazios
metricsBuilder := metrics.NewPrometheusMetricsBuilder(prometheusRegistry, "", "")
metricsBuilder.AddPrometheusRouterMetrics(router)
// ...
Exemplo de Aplicativo
Para entender como o painel funciona na prática, você pode se referir ao exemplo de métricas.
Siga as instruções no README do exemplo para executá-lo e adicionar a fonte de dados do Prometheus ao Grafana.
Painel do Grafana
Preparamos um painel do Grafana para usar com a implementação de métricas mencionada. Ele fornece informações básicas sobre taxa de transferência, taxa de falha e duração de publicação/processamento.
Se você quiser visualizar este painel localmente, pode usar o exemplo de aplicativo.
Para mais informações sobre as métricas exportadas para o Prometheus, consulte Métricas Exportadas.
Importando o Painel
Para importar o painel do Grafana, selecione Painel/Gerenciar no menu à esquerda e em seguida clique em +Import
.
Insira a URL do painel https://grafana.com/dashboards/9777 (ou apenas o ID, 9777), e depois clique em Carregar.
Em seguida, selecione a fonte de dados do Prometheus usada para coletar o ponto de extremidade /metrics
. Clique em Importar
e está feito!
Métricas Exportadas
A seguir, lista todas as métricas registradas no registro do Prometheus pelo PrometheusMetricsBuilder
.
Para obter mais informações sobre os tipos de métricas do Prometheus, consulte a documentação do Prometheus.
Objeto | Métrica | Descrição | Rótulos/Valores |
---|---|---|---|
Assinante | subscriber_messages_received_total |
Um contador do Prometheus. Conta o número de mensagens recebidas por um assinante. | acked é "acked" ou "nacked". |
Se o assinante operar dentro de um manipulador, defina handler_name ; caso contrário, " |
|||
subscriber_name identifica o assinante. Se implementar a interface fmt.Stringer , é o resultado de String() ; caso contrário, é package.structName . |
|||
Manipulador | handler_execution_time_seconds |
Um histograma do Prometheus. Registra o tempo de execução da função do manipulador envolvida pelo middleware. | handler_name é o nome do manipulador. |
success é "true" ou "false", dependendo se a função do manipulador envolvida retorna um erro. |
|||
Publicador | publish_time_seconds |
Um histograma do Prometheus. Registra o tempo de execução da função de publicação decorada do publicador. | success é "true" ou "false", dependendo se o publicador decorado retorna um erro. |
Se o publicador operar dentro de um manipulador, defina handler_name ; caso contrário, " |
|||
publisher_name identifica o publicador. Se implementar a interface fmt.Stringer , é o resultado de String() ; caso contrário, é package.structName . |
Além disso, cada métrica tem um rótulo node
fornecido pelo Prometheus, com seu valor correspondente à instância da fonte da métrica, e um job
especificado como o nome do trabalho no arquivo de configuração do Prometheus.
Observação: Conforme mencionado acima, o uso de um namespace
ou subsystem
não vazio resultará em um prefixo no nome da métrica. Você pode precisar fazer os ajustes correspondentes, como na definição de painel de um painel de controle do Grafana.
Personalização
Se acreditar que uma determinada métrica foi negligenciada, você pode facilmente estender esta implementação básica. A melhor maneira é usar o registro do Prometheus utilizado com o método ServeHTTP
e registrar as métricas de acordo com a documentação do cliente Prometheus aqui.
Um método conciso para atualizar essas métricas é usando decoradores. O código-fonte completo pode ser encontrado em github.com/ThreeDotsLabs/watermill/message/decorator.go.
// ...
// MessageTransformSubscriberDecorator cria um decorador de assinante que chama a função de transformação em cada mensagem passada pelo assinante.
func MessageTransformSubscriberDecorator(transform func(*Message)) SubscriberDecorator {
if transform == nil {
panic("função de transformação está nula")
}
return func(sub Subscriber) (Subscriber, error) {
return &messageTransformSubscriberDecorator{
sub: sub,
transform: transform,
}, nil
}
}
// MessageTransformPublisherDecorator cria um decorador de publicador que chama a função de transformação em cada mensagem passada pelo publicador.
func MessageTransformPublisherDecorator(transform func(*Message)) PublisherDecorator {
if transform == nil {
panic("função de transformação está nula")
}
return func(pub Publisher) (Publisher, error) {
return &messageTransformPublisherDecorator{
Publisher: pub,
transform: transform,
}, nil
}
}
type messageTransformSubscriberDecorator struct {
// ...
E/ou middleware em roteadores.
Um método mais simples é atualizar apenas as métricas necessárias nas funções do manipulador.