Aller au contenu
Outils medium

Micrometer : Métriques JVM multi-backend

14 min de lecture

logo micrometer

Micrometer est la façade de métriques pour les applications JVM. Comme SLF4J unifie le logging, Micrometer unifie les métriques : vous écrivez une fois, vous exportez vers Prometheus, Datadog, InfluxDB, OTLP… sans changer votre code.

Cette page vous guide de votre premier endpoint /actuator/prometheus jusqu’à une configuration production-ready.

QuestionRéponse
C’est quoi ?Une façade de métriques “vendor-neutral” pour la JVM
Analogie”SLF4J des métriques” — une API, plusieurs backends
IntégrationNatif dans Spring Boot depuis 2.0 (via Actuator)
BackendsPrometheus, Datadog, CloudWatch, InfluxDB, OTLP…
FrameworksSpring Boot, Quarkus, Micronaut

Micrometer n’est pas un backend — c’est une API + un Registry.

Architecture Micrometer : de votre code aux backends

ComposantRôle
Micrometer APICe que votre code appelle (Counter, Timer, Gauge)
MeterRegistryL’implémentation qui décide envoyer (Prometheus, OTLP…)
BindersMétriques automatiques : JVM, HTTP, pools, caches
ActuatorExposition des endpoints (Spring Boot)
Observation APIInstrumentation unifiée métriques + traces (Spring Boot 3)

C’est le cas d’usage le plus courant. En 3 étapes, vous avez des métriques dans Prometheus.

  1. Ajoutez les dépendances

    pom.xml
    <!-- Actuator (inclut Micrometer) -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- Registry Prometheus -->
    <dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
  2. Exposez l’endpoint Prometheus

    Par défaut, /actuator/prometheus n’est pas exposé. Ajoutez :

    application.yml
    management:
    endpoints:
    web:
    exposure:
    include: health,info,prometheus,metrics
    prometheus:
    metrics:
    export:
    enabled: true
  3. Configurez le scrape Prometheus

    prometheus.yml
    scrape_configs:
    - job_name: 'spring-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
    - targets: ['localhost:8080']

Vérification :

Fenêtre de terminal
# L'endpoint doit répondre
curl http://localhost:8080/actuator/prometheus
# Vous verrez des métriques comme :
# jvm_memory_used_bytes{...}
# http_server_requests_seconds_count{...}

Les Binders fournissent des métriques “infra” (JVM, HTTP). Pour la logique métier, créez vos propres métriques.

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
@Service
public class OrderService {
private final Counter orderCounter;
private final Counter errorCounter;
public OrderService(MeterRegistry registry) {
this.orderCounter = Counter.builder("orders.created.total")
.description("Total orders created")
.tag("channel", "web")
.register(registry);
this.errorCounter = Counter.builder("orders.errors.total")
.description("Total order errors")
.tag("type", "validation")
.register(registry);
}
public void createOrder(Order order) {
try {
// ... création
orderCounter.increment();
} catch (ValidationException e) {
errorCounter.increment();
throw e;
}
}
}
import io.micrometer.core.instrument.Gauge;
@Service
public class QueueService {
private final Queue<Task> taskQueue = new ConcurrentLinkedQueue<>();
public QueueService(MeterRegistry registry) {
Gauge.builder("queue.size", taskQueue, Queue::size)
.description("Current queue size")
.tag("queue", "orders")
.register(registry);
}
}
import io.micrometer.core.instrument.Timer;
@Service
public class PaymentService {
private final Timer paymentTimer;
public PaymentService(MeterRegistry registry) {
this.paymentTimer = Timer.builder("payment.duration")
.description("Payment processing time")
.publishPercentiles(0.5, 0.95, 0.99) // p50, p95, p99
.register(registry);
}
public PaymentResult processPayment(Payment payment) {
return paymentTimer.record(() -> {
// ... traitement
return new PaymentResult();
});
}
}
import io.micrometer.core.annotation.Timed;
@Service
public class UserService {
@Timed(value = "user.create.duration",
description = "Time to create a user",
percentiles = {0.5, 0.95, 0.99})
public User createUser(CreateUserRequest request) {
// ...
}
}

Les tags permettent de filtrer et grouper. Mais attention à la cardinalité.

// Tags statiques (à la création)
Counter.builder("http.requests.total")
.tag("application", "order-service")
.tag("environment", "production")
.register(registry);
// Tags dynamiques (à l'appel)
public void recordRequest(String method, String route, int status) {
registry.counter("http.requests.total",
"method", method, // GET, POST, PUT...
"route", route, // /api/orders/{id}
"status", String.valueOf(status / 100) + "xx" // 2xx, 4xx, 5xx
).increment();
}
@Bean
public MeterRegistryCustomizer<MeterRegistry> commonTags() {
return registry -> registry.config()
.commonTags("application", "order-service")
.commonTags("environment", "production");
}
❌ Mauvais✅ CorrectPourquoi
tag("user_id", userId)tag("user_tier", "premium")user_id = millions de valeurs
tag("request_id", reqId)(pas de tag)Chaque requête = nouvelle série
tag("path", "/api/orders/123")tag("route", "/api/orders/{id}")Path avec ID = cardinalité infinie
BesoinConfiguration
Distribution de latence (p50, p95, p99)publishPercentiles(0.5, 0.95, 0.99)
SLO buckets (< 100ms, < 500ms, < 1s)serviceLevelObjectives(100, 500, 1000)
Agrégation serveur-side (Prometheus)publishPercentileHistogram()
Timer.builder("http.server.requests")
.publishPercentiles(0.5, 0.95, 0.99) // Client-side percentiles
.publishPercentileHistogram() // Histogram pour agrégation
.serviceLevelObjectives( // SLO buckets
Duration.ofMillis(100),
Duration.ofMillis(500),
Duration.ofSeconds(1)
)
.register(registry);
ConfigurationSéries générées
Counter simple1
Timer avec 3 percentiles~4 (count, sum, p50, p95, p99)
Timer avec histogram (14 buckets)~17
Tout × 5 tags × 4 valeurs chacun× 1024
application.yml
management:
metrics:
enable:
jvm: true
process: true
http: true
logback: false # Désactiver
tomcat: false # Désactiver
@Bean
public MeterFilter customFilter() {
return new MeterFilter() {
@Override
public MeterFilterReply accept(Meter.Id id) {
// Exclure les métriques internes
if (id.getName().startsWith("jvm.gc.")) {
return MeterFilterReply.DENY;
}
return MeterFilterReply.NEUTRAL;
}
};
}

Pour que Prometheus Operator scrape automatiquement vos pods :

servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: order-service
namespace: monitoring
spec:
selector:
matchLabels:
app: order-service
namespaceSelector:
matchNames:
- production
endpoints:
- port: http
path: /actuator/prometheus
interval: 30s

Si vous standardisez sur OTLP (plateforme OTel), Micrometer peut exporter directement vers le Collector.

pom.xml
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-otlp</artifactId>
</dependency>
application.yml
management:
otlp:
metrics:
export:
enabled: true
url: http://otel-collector:4318/v1/metrics
step: 1m

Architecture résultante :

App (Micrometer) → OTLP → OTel Collector → Prometheus/Mimir/Datadog...

Spring Boot 3 introduit l’Observation API : vous instrumentez une fois, vous obtenez métriques + traces.

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
@Service
public class OrderService {
private final ObservationRegistry observationRegistry;
public Order processOrder(OrderRequest request) {
return Observation.createNotStarted("order.process", observationRegistry)
.lowCardinalityKeyValue("channel", "web")
.observe(() -> {
// Ce code est tracé ET mesuré
return doProcessOrder(request);
});
}
}
pom.xml
<!-- Bridge vers OpenTelemetry -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
application.yml
management:
tracing:
sampling:
probability: 0.1 # 10% en prod
otlp:
tracing:
endpoint: http://otel-collector:4318/v1/traces
Anti-patternConséquenceSolution
Tags à haute cardinalitéOOM, coûts explosifsUtiliser des catégories (tier, status_class)
Pas de tags communsRépétition, inconsistanceMeterRegistryCustomizer
Histogrammes partoutExplosion de sériesActiver uniquement sur les métriques RED
Percentiles sans histogramNon agrégeables en clusterActiver publishPercentileHistogram()
Versions explicites avec SpringConflits de versionsLaisser le BOM gérer
  1. L’endpoint répond-il ?

    Fenêtre de terminal
    curl http://localhost:8080/actuator/prometheus
    # 404 → endpoint non exposé
  2. L’endpoint est-il exposé ?

    management.endpoints.web.exposure.include: prometheus
  3. Le registry est-il présent ?

    Vérifiez la dépendance micrometer-registry-prometheus.

  4. Les métriques sont-elles créées ?

    Fenêtre de terminal
    curl http://localhost:8080/actuator/metrics
    # Liste toutes les métriques disponibles
  5. Prometheus scrape-t-il ?

    Vérifiez http://prometheus:9090/targets.

SymptômeCause probableSolution
/actuator/prometheus 404Endpoint non exposéAjouter à exposure.include
Métrique absenteCounter jamais incrémentéVérifier que le code est appelé
OOM côté PrometheusHaute cardinalitéAuditer les tags
@Timed sans effetTimedAspect absentAjouter le bean
Versions incompatiblesVersion expliciteUtiliser le BOM Spring Boot
  • Micrometer = SLF4J des métriques — une API, plusieurs backends
  • Spring Boot Actuator inclut Micrometer — ajoutez juste le registry (Prometheus, OTLP…)
  • Exposez l’endpoint — par défaut /actuator/prometheus n’est pas accessible
  • Cardinalité = coût — jamais d’IDs uniques en tags
  • OTLP Registry pour unifier avec OTel Collector
  • Observation API (Spring Boot 3) = métriques + traces en une instrumentation

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.