
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.
Pourquoi Micrometer ?
Section intitulée « Pourquoi Micrometer ? »| 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 |
À 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