Métricas personalizadas de Datadog vía Micrometer
Las pruebas demuestran que el código es correcto una vez, en el CI; las métricas demuestran que sigue siendo correcto ahora mismo, en producción, a un dashboard de distancia de buscar en los logs.
Micrometer es para las métricas lo que SLF4J es para los logs: una fachada neutral respecto al proveedor de la que depende tu código de aplicación, de modo que cambiar el proveedor subyacente nunca implique tocar la lógica de negocio. Instrumentas `VehicleValuationService` y sus clientes de proveedores una sola vez, contra los tipos `Counter`, `Timer` y `Gauge` de Micrometer, y el `MeterRegistry` que esté en el classpath decide a dónde van realmente esos números. Apuntas el mismo código instrumentado a Prometheus, CloudWatch o Datadog cambiando una dependencia y un puñado de propiedades — ninguna de las llamadas a `Counter.builder(...)` repartidas por el código cambia.
Para este servicio, ese registro es el de Datadog: añadir `micrometer-registry-datadog` al classpath y configurar la clave de API y el intervalo de paso le da a la autoconfiguración de Spring Boot todo lo necesario para armar un `DatadogMeterRegistry` automáticamente. Vale la pena saber que este registro empuja datos — en un intervalo fijo, llama directamente a la API HTTP de Datadog — que es un modelo distinto al de scraping por sondeo (pull) de Prometheus, y por eso la propiedad `step` (cada cuánto se envían los datos) importa: demasiado corto y haces llamadas HTTP innecesarias, demasiado largo y tus dashboards se atrasan respecto a la realidad.
El primer instrumento al que recurrir es un `Counter`: un número que solo sube, perfecto para contar eventos discretos como las llamadas a proveedores. `Counter.builder("valuation.provider.calls").tag("provider", providerName).tag("outcome", "success").register(registry).increment()` llamado una vez después de cada llamada a un proveedor — éxito o fallo — te da un total acumulado que puedes segmentar por cualquiera de las dos etiquetas en un dashboard sin escribir tú mismo ni una línea de código de agregación.
El segundo es un `Timer`, que mide tanto el conteo como la distribución de cuánto tardó algo — exactamente lo que quieres para la latencia de `VehicleValuationService.valuate()` en su conjunto. `Timer.start(registry)` al inicio de la llamada y `.stop(...)` cuando termina registra una muestra por solicitud, y como un `Timer` rastrea percentiles y no solo un promedio, puedes ver tu p99 con claridad en lugar de tener un valor atípico lento escondido dentro de una media que se ve cómoda.
El tercero es un `Gauge`, y tiene una forma completamente distinta: un `Counter` solo incrementa, pero un `Gauge` reporta cualquiera que sea el valor actual de algo en el momento en que se muestrea — el tamaño de una cola, el número de conexiones abiertas, o aquí, el estado actual del disyuntor de Failsafe para cada proveedor. `Gauge.builder("valuation.provider.circuit_breaker.state", breaker) { it.state.ordinal.toDouble() }.tag("provider", providerName).register(registry)` deja que el registro lea el estado en vivo del disyuntor directamente, en lugar de que tú recuerdes empujar una actualización cada vez que cambia.
Cada uno de estos ejemplos etiqueta por `provider` y por `outcome` — ambos conjuntos pequeños, fijos y finitos de valores — y nunca por algo como el VIN o el id de la solicitud. Esa distinción es todo el juego: etiquetar por una dimensión acotada como el nombre de un proveedor (tres valores posibles) se convierte en tres series temporales, baratas de almacenar y triviales de graficar; etiquetar por algo no acotado como un VIN crea una serie temporal nueva por cada vehículo único, que es exactamente cómo una factura de métricas explota en silencio y cómo un registro empieza a descartar datos silenciosamente bajo alta cardinalidad.
Bien hecho, esta es la diferencia entre depurar por instinto y depurar con evidencia. Cuando Carfax tiene una mala tarde, nadie necesita grepear logs en tres servicios y adivinar una tasa de fallos — el contador `valuation.provider.calls`, agrupado por `provider` y `outcome` en un solo widget de Datadog, muestra "Carfax está fallando en el 40% de las llamadas" en el instante en que empieza a pasar, y ese mismo contador es lo que dispara una alerta antes de que un humano note que algo anda mal.
@Componentclass ProviderMetrics(private val registry: MeterRegistry) {fun recordProviderCall(providerName: String, outcome: String) {Counter.builder("valuation.provider.calls").tag("provider", providerName).tag("outcome", outcome).description("Number of calls made to each vehicle-valuation provider").register(registry).increment()}suspend fun <T> timeValuationRequest(block: suspend () -> T): T {val sample = Timer.start(registry)var outcome = "error"try {val result = block()outcome = "success"return result} finally {sample.stop(Timer.builder("valuation.request.latency").tag("outcome", outcome).publishPercentileHistogram().register(registry))}}}
A small wrapper around MeterRegistry exposing the two most common instruments the valuation service needs: a call counter and a request timer.
class MeteredValuationProviderClient(private val providerName: String,private val delegate: ValuationProviderClient,private val metrics: ProviderMetrics,) : ValuationProviderClient {override suspend fun fetchValuation(vin: String): ProviderQuote {return try {val quote = delegate.fetchValuation(vin)metrics.recordProviderCall(providerName, "success")quote} catch (ex: Exception) {metrics.recordProviderCall(providerName, "failure")throw ex}}}
A provider client wrapper recording success/failure outcomes on every call, so a Datadog dashboard can group calls by provider and outcome.
@Componentclass CircuitBreakerGauges(private val registry: MeterRegistry,private val providerBreakers: Map<String, CircuitBreaker<Any>>,) {@PostConstructfun registerGauges() {providerBreakers.forEach { (providerName, breaker) ->Gauge.builder("valuation.provider.circuit_breaker.state", breaker) {when {it.isClosed -> 0.0it.isHalfOpen -> 1.0else -> 2.0}}.tag("provider", providerName).description("0=closed, 1=half-open, 2=open").register(registry)}}}
A Gauge per provider that reads the live Failsafe circuit-breaker state, so a dashboard shows which providers are currently open without any manual push.
🧠 Comprueba tu comprensión
0/1 · 0/1 answered1. The provider-call Counter is tagged by provider name and outcome. Why should it never also be tagged by VIN, even though VIN is a natural piece of context for a valuation event?