
Micrometer est la façade de métriques des applications JVM : comme SLF4J unifie le logging, Micrometer unifie les métriques. Vous instrumentez une seule fois et vous exportez vers Prometheus, Datadog, InfluxDB ou OTLP, sans changer votre code.
Ce guide s'adresse aux profils intermédiaires et avancés en écosystème Java/Spring. Vous irez de votre premier endpoint /actuator/prometheus jusqu'à une configuration prête pour la production : métriques métier, tags, cardinalité, histogrammes et export OTLP.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Exposer un endpoint
/actuator/prometheusdepuis Spring Boot. - Créer des métriques métier : Counter, Gauge, Timer.
- Maîtriser les tags et éviter l'explosion de cardinalité.
- Configurer histogrammes et percentiles pour les SLO.
- Exporter en OTLP vers un OpenTelemetry Collector.
Pourquoi Micrometer ?
Section intitulée « Pourquoi Micrometer ? »Sans façade, chaque bibliothèque de métriques lie votre code à un backend précis. Passer de Prometheus à Datadog imposerait de réécrire l'instrumentation. Micrometer casse ce couplage : votre code appelle une API neutre, et le choix du backend se règle par une simple dépendance. C'est pourquoi Spring Boot l'a adopté nativement.
| Question | Ré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égration | Natif dans Spring Boot depuis 2.0 (via Actuator) |
| Backends | Prometheus, Datadog, CloudWatch, InfluxDB, OTLP... |
| Frameworks | Spring Boot, Quarkus, Micronaut |
Mental model : comment Micrometer s'emboîte
Section intitulée « Mental model : comment Micrometer s'emboîte »Micrometer n'est pas un backend, c'est une API + un Registry.
| Composant | Rôle |
|---|---|
| Micrometer API | Ce que votre code appelle (Counter, Timer, Gauge) |
| MeterRegistry | L'implémentation qui décide où envoyer (Prometheus, OTLP...) |
| Binders | Métriques automatiques : JVM, HTTP, pools, caches |
| Actuator | Exposition des endpoints (Spring Boot) |
| Observation API | Instrumentation unifiée métriques + traces (Spring Boot 3) |
Quickstart : Spring Boot → Prometheus (10 min)
Section intitulée « Quickstart : Spring Boot → Prometheus (10 min) »C'est le cas d'usage le plus courant. En 3 étapes, vous avez des métriques dans Prometheus.
-
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> -
Exposez l'endpoint Prometheus
Par défaut,
/actuator/prometheusn'est pas exposé. Ajoutez :application.yml management:endpoints:web:exposure:include: health,info,prometheus,metricsprometheus:metrics:export:enabled: true -
Configurez le scrape Prometheus
prometheus.yml scrape_configs:- job_name: 'spring-app'metrics_path: '/actuator/prometheus'static_configs:- targets: ['localhost:8080']
Vérification :
# L'endpoint doit répondrecurl http://localhost:8080/actuator/prometheus
# Vous verrez des métriques comme :# jvm_memory_used_bytes{...}# http_server_requests_seconds_count{...}Créer des métriques métier
Section intitulée « Créer des métriques métier »Les Binders fournissent des métriques "infra" (JVM, HTTP). Pour la logique métier, créez vos propres métriques.
Counter : événements qui s'accumulent
Section intitulée « Counter : événements qui s'accumulent »import io.micrometer.core.instrument.Counter;import io.micrometer.core.instrument.MeterRegistry;
@Servicepublic 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; } }}Gauge : valeur instantanée
Section intitulée « Gauge : valeur instantanée »import io.micrometer.core.instrument.Gauge;
@Servicepublic 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); }}Timer : durée + count
Section intitulée « Timer : durée + count »import io.micrometer.core.instrument.Timer;
@Servicepublic 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(); }); }}Annotation @Timed
Section intitulée « Annotation @Timed »import io.micrometer.core.annotation.Timed;
@Servicepublic 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) { // ... }}Tags : les dimensions de vos métriques
Section intitulée « Tags : les dimensions de vos métriques »Les tags permettent de filtrer et grouper. Mais attention à la cardinalité.
Tags statiques vs dynamiques
Section intitulée « Tags statiques vs dynamiques »// 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();}Tags communs (global)
Section intitulée « Tags communs (global) »@Beanpublic MeterRegistryCustomizer<MeterRegistry> commonTags() { return registry -> registry.config() .commonTags("application", "order-service") .commonTags("environment", "production");}Anti-pattern : haute cardinalité
Section intitulée « Anti-pattern : haute cardinalité »| ❌ Mauvais | ✅ Correct | Pourquoi |
|---|---|---|
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 |
Histogrammes et percentiles
Section intitulée « Histogrammes et percentiles »Quand les activer ?
Section intitulée « Quand les activer ? »| Besoin | Configuration |
|---|---|
| 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);Impact sur le nombre de séries
Section intitulée « Impact sur le nombre de séries »| Configuration | Séries générées |
|---|---|
| Counter simple | 1 |
| Timer avec 3 percentiles | ~4 (count, sum, p50, p95, p99) |
| Timer avec histogram (14 buckets) | ~17 |
| Tout × 5 tags × 4 valeurs chacun | × 1024 |
Filtrage des métriques
Section intitulée « Filtrage des métriques »Activer/désactiver par préfixe
Section intitulée « Activer/désactiver par préfixe »management: metrics: enable: jvm: true process: true http: true logback: false # Désactiver tomcat: false # DésactiverMeterFilter (programmatique)
Section intitulée « MeterFilter (programmatique) »@Beanpublic 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; } };}Kubernetes : ServiceMonitor
Section intitulée « Kubernetes : ServiceMonitor »Pour que Prometheus Operator scrape automatiquement vos pods :
apiVersion: monitoring.coreos.com/v1kind: ServiceMonitormetadata: name: order-service namespace: monitoringspec: selector: matchLabels: app: order-service namespaceSelector: matchNames: - production endpoints: - port: http path: /actuator/prometheus interval: 30sOTLP Registry : Micrometer → OTel Collector
Section intitulée « OTLP Registry : Micrometer → OTel Collector »Si vous standardisez sur OTLP (plateforme OTel), Micrometer peut exporter directement vers le Collector.
<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-otlp</artifactId></dependency>management: otlp: metrics: export: enabled: true url: http://otel-collector:4318/v1/metrics step: 1mArchitecture résultante :
App (Micrometer) → OTLP → OTel Collector → Prometheus/Mimir/Datadog...Spring Boot 3 : Observation API + Tracing
Section intitulée « Spring Boot 3 : Observation API + Tracing »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;
@Servicepublic 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); }); }}Activer le tracing
Section intitulée « Activer le tracing »<!-- 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>management: tracing: sampling: probability: 0.1 # 10% en prod otlp: tracing: endpoint: http://otel-collector:4318/v1/tracesAnti-patterns
Section intitulée « Anti-patterns »| Anti-pattern | Conséquence | Solution |
|---|---|---|
| Tags à haute cardinalité | OOM, coûts explosifs | Utiliser des catégories (tier, status_class) |
| Pas de tags communs | Répétition, inconsistance | MeterRegistryCustomizer |
| Histogrammes partout | Explosion de séries | Activer uniquement sur les métriques RED |
| Percentiles sans histogram | Non agrégeables en cluster | Activer publishPercentileHistogram() |
| Versions explicites avec Spring | Conflits de versions | Laisser le BOM gérer |
Dépannage
Section intitulée « Dépannage »Checklist "je ne vois pas mes métriques"
Section intitulée « Checklist "je ne vois pas mes métriques" »-
L'endpoint répond-il ?
Fenêtre de terminal curl http://localhost:8080/actuator/prometheus# 404 → endpoint non exposé -
L'endpoint est-il exposé ?
management.endpoints.web.exposure.include: prometheus -
Le registry est-il présent ?
Vérifiez la dépendance
micrometer-registry-prometheus. -
Les métriques sont-elles créées ?
Fenêtre de terminal curl http://localhost:8080/actuator/metrics# Liste toutes les métriques disponibles -
Prometheus scrape-t-il ?
Vérifiez
http://prometheus:9090/targets.
Erreurs courantes
Section intitulée « Erreurs courantes »| Symptôme | Cause probable | Solution |
|---|---|---|
/actuator/prometheus 404 | Endpoint non exposé | Ajouter à exposure.include |
| Métrique absente | Counter jamais incrémenté | Vérifier que le code est appelé |
| OOM côté Prometheus | Haute cardinalité | Auditer les tags |
| @Timed sans effet | TimedAspect absent | Ajouter le bean |
| Versions incompatibles | Version explicite | Utiliser le BOM Spring Boot |
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »/actuator/prometheus que Prometheus vient scraper. Les deux sont complémentaires, pas concurrents.management.endpoints.web.exposure.include=prometheus. Prometheus scrape ensuite /actuator/prometheus, qui publie les métriques JVM et HTTP automatiquement.À retenir
Section intitulée « À retenir »- 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/prometheusn'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