Monitoreo en tiempo real de Watermill usando Prometheus

Métricas

Watermill se puede monitorear mediante el uso de decoradores para publicadores/suscriptores y middleware para controladores. Proporcionamos una implementación predeterminada utilizando el cliente oficial de Prometheus para Go.

El paquete components/metrics exporta PrometheusMetricsBuilder, que proporciona funciones convenientes para envolver publicadores, suscriptores y controladores para actualizar el registro relevante de Prometheus:

Código fuente completo: github.com/ThreeDotsLabs/watermill/components/metrics/builder.go

// ...
// PrometheusMetricsBuilder proporciona métodos para decorar publicadores, suscriptores y controladores.
type PrometheusMetricsBuilder struct {
    // PrometheusRegistry puede completar un registro de Prometheus existente o estar vacío para usar el registro predeterminado.
    PrometheusRegistry prometheus.Registerer

    Namespace string
    Subsystem string
}

// AddPrometheusRouterMetrics es una función conveniente para agregar un middleware métrico a todos los controladores en el enrutador de mensajes. También decora los publicadores y suscriptores de los controladores.
func (b PrometheusMetricsBuilder) AddPrometheusRouterMetrics(r *message.Router) {
// ...

Envolver Publicadores, Suscriptores y Controladores

Si estás usando el enrutador de Watermill (lo cual se recomienda en la mayoría de los casos), puedes usar la función conveniente AddPrometheusRouterMetrics para asegurarte de que todos los controladores agregados a este enrutador estén envueltos para actualizar el registro de Prometheus, al igual que sus publicadores y suscriptores:

Código fuente completo: github.com/ThreeDotsLabs/watermill/components/metrics/builder.go

// ...
// AddPrometheusRouterMetrics es una función conveniente para agregar un middleware métrico a todos los controladores en el enrutador de mensajes. También decora los publicadores y suscriptores de los controladores.
func (b PrometheusMetricsBuilder) AddPrometheusRouterMetrics(r *message.Router) {
    r.AddPublisherDecorators(b.DecoratePublisher)
    r.AddSubscriberDecorators(b.DecorateSubscriber)
    r.AddMiddleware(b.NewRouterMiddleware().Middleware)
}
// ...

Ejemplo de uso de AddPrometheusRouterMetrics:

Código fuente completo: github.com/ThreeDotsLabs/watermill/_examples/basic/4-metrics/main.go

// ...
// Dejamos los parámetros de namespace y subsystem vacíos
metricsBuilder := metrics.NewPrometheusMetricsBuilder(prometheusRegistry, "", "")
metricsBuilder.AddPrometheusRouterMetrics(router)
// ...

En el fragmento de código anterior, dejamos los parámetros namespace y subsystem vacíos. La biblioteca cliente de Prometheus utiliza estos parámetros para prefijar los nombres de las métricas. Es posible que quieras usar namespace o subsystem, pero ten en cuenta que esto afectará los nombres de las métricas, por lo que deberás ajustar el panel de control de Grafana en consecuencia.

Los publicadores y suscriptores independientes también se pueden decorar utilizando métodos dedicados de PrometheusMetricsBuilder:

Código fuente 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)
}
// ...

Exponer el punto final /metrics

Según el principio de funcionamiento de Prometheus, un servicio necesita exponer un punto final HTTP para el scraping de datos. Convencionalmente, se trata de un punto final GET, y la ruta suele ser /metrics.

Para proporcionar este punto final, hay dos funciones convenientes disponibles, una utilizando el Registro de Prometheus previamente creado y la otra creando simultáneamente un nuevo Registro:

Código fuente completo: github.com/ThreeDotsLabs/watermill/components/metrics/http.go

// ...
// CreateRegistryAndServeHTTP establece un servidor HTTP en la dirección dada para exponer el punto final /metrics a Prometheus.
// Devuelve un nuevo Registro de Prometheus (para el registro de métricas) y una función de cancelación para apagar el servidor.
func CreateRegistryAndServeHTTP(addr string) (registry *prometheus.Registry, cancel func()) {
	registry = prometheus.NewRegistry()
	return registry, ServeHTTP(addr, registry)
}

// ServeHTTP establece un servidor HTTP en la dirección dada para exponer el punto final /metrics a Prometheus.
// Acepta un Registro de Prometheus existente y devuelve una función de cancelación para apagar el servidor.
func ServeHTTP(addr string, registry *prometheus.Registry) (cancel func()) {
// ...

Aquí tienes un ejemplo de uso:

Código fuente completo: github.com/ThreeDotsLabs/watermill/_examples/basic/4-metrics/main.go

// ...
	prometheusRegistry, closeMetricsServer := metrics.CreateRegistryAndServeHTTP(*metricsAddr)
	defer closeMetricsServer()

	// Dejamos el espacio de nombres y el subsistema en blanco
	metricsBuilder := metrics.NewPrometheusMetricsBuilder(prometheusRegistry, "", "")
	metricsBuilder.AddPrometheusRouterMetrics(router)
// ...

Aplicación de ejemplo

Para entender cómo funciona el panel en la práctica, puedes consultar el ejemplo de métricas.

Sigue las instrucciones en el README del ejemplo para ejecutarlo y añadir la fuente de datos de Prometheus a Grafana.

Panel de Grafana

Hemos preparado un panel de Grafana para usar con la implementación de métricas antes mencionada. Proporciona información básica sobre el rendimiento, la tasa de fallos y la duración de publicación/procesamiento.

Si deseas ver este panel localmente, puedes utilizar la aplicación de ejemplo.

Para obtener más información sobre las métricas exportadas a Prometheus, consulta Métricas exportadas.

Importar el panel

Para importar el panel de Grafana, selecciona Dashboard/Manage en el menú de la izquierda, luego haz clic en +Import.

Introduce la URL del panel https://grafana.com/dashboards/9777 (o solo el ID, 9777), luego haz clic en Cargar.

Importar Panel

Luego selecciona la fuente de datos de Prometheus utilizada para scrappear el punto final /metrics. ¡Haz clic en Import y listo!

Métricas exportadas

A continuación se enumeran todas las métricas registradas en el registro de Prometheus por PrometheusMetricsBuilder.

Para obtener más información sobre los tipos de métricas de Prometheus, consulte la documentación de Prometheus.

Objeto Métrica Descripción Etiquetas/Valores
Suscriptor suscriptor_mensajes_recibidos_total Un contador de Prometheus. Cuenta el número de mensajes recibidos por un suscriptor. acked es "acked" o "nacked".
Si el suscriptor opera dentro de un manipulador, establezca handler_name; de lo contrario, "".
subscriber_name identifica al suscriptor. Si implementa la interfaz fmt.Stringer, es el resultado de String(); de lo contrario, es package.structName.
Manipulador manipulador_tiempo_ejecucion_segundos Un histograma de Prometheus. Registra el tiempo de ejecución de la función del manipulador envuelto por el middleware. handler_name es el nombre del manipulador.
success es "true" o "false", dependiendo de si la función del manipulador envuelto devuelve un error.
Publicador tiempo_publicacion_segundos Un histograma de Prometheus. Registra el tiempo de ejecución de la función de publicación decorada del publicador. success es "true" o "false", dependiendo de si el publicador decorado devuelve un error.
Si el publicador opera dentro de un manipulador, establezca handler_name; de lo contrario, "".
publisher_name identifica al publicador. Si implementa la interfaz fmt.Stringer, es el resultado de String(); de lo contrario, es package.structName.

Además, cada métrica tiene una etiqueta node proporcionada por Prometheus, con su valor correspondiente a la instancia de la fuente de la métrica, y un job especificado como el nombre del trabajo en el archivo de configuración de Prometheus.

Nota: Como se mencionó anteriormente, el uso de un namespace o subsystem no vacío resultará en un prefijo de nombre de métrica. Es posible que necesite realizar ajustes correspondientes, como en la definición del panel de un panel de control de Grafana.

Personalización

Si cree que se ha pasado por alto alguna métrica, puede ampliar fácilmente esta implementación básica. La mejor manera es utilizar el registro de Prometheus con el método ServeHTTP y registrar las métricas de acuerdo con la documentación del cliente de Prometheus aquí.

Un método conciso para actualizar estas métricas es mediante el uso de decoradores. El código fuente completo se puede encontrar en github.com/ThreeDotsLabs/watermill/message/decorator.go.

// ...
// MessageTransformSubscriberDecorator crea un decorador de suscriptor que llama a la función de transformación en cada mensaje que pasa por el suscriptor.
func MessageTransformSubscriberDecorator(transform func(*Message)) SubscriberDecorator {
	if transform == nil {
		panic("función de transformación nula")
	}
	return func(sub Subscriber) (Subscriber, error) {
		return &messageTransformSubscriberDecorator{
			sub:       sub,
			transform: transform,
		}, nil
	}
}

// MessageTransformPublisherDecorator crea un decorador de publicador que llama a la función de transformación en cada mensaje que pasa por el publicador.
func MessageTransformPublisherDecorator(transform func(*Message)) PublisherDecorator {
	if transform == nil {
		panic("función de transformación nula")
	}
	return func(pub Publisher) (Publisher, error) {
		return &messageTransformPublisherDecorator{
			Publisher: pub,
			transform: transform,
		}, nil
	}
}

type messageTransformSubscriberDecorator struct {
// ...

Y/o middleware en enrutadores.

Un método más sencillo es actualizar solo las métricas requeridas en las funciones del manipulador.