Metriken mit Micrometer
Micrometer als SLF4J für Metriken: Counter, Gauges, Timer und Distribution Summaries, dimensionale Tags richtig gemacht und der Export nach Prometheus.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
Micrometer is to metrics what SLF4J is to logging: a vendor-neutral facade you code against once, with pluggable backends (Prometheus, Datadog, CloudWatch...) chosen by a dependency. Spring Boot auto-configures it, so BookVault already emits dozens of metrics. This lesson is about instrumenting your own - picking the right meter type and, above all, tagging without blowing up your metrics backend.
The meter types
Micrometer offers a few meter types, and choosing the right one is most of the skill:
- Counter - a value that only ever increases: books borrowed, requests served, errors. You ask questions about its rate ("borrows per second"), not its raw total.
- Gauge - a value that goes up and down, sampled now: active loans, queue depth, cache size. A gauge observes a live value; you don't set it, you point it at something to read.
- Timer - records how long things take and how often: it captures count, total time, and max, and (configured) latency percentiles. The workhorse for request/operation latency.
- Distribution Summary - like a timer but for non-time distributions: payload sizes, batch counts.
@Component
class LoanMetrics {
private final Counter borrows;
private final Timer checkoutTimer;
LoanMetrics(MeterRegistry registry) {
this.borrows = Counter.builder("bookvault.loans.borrowed")
.description("Total books borrowed").register(registry);
this.checkoutTimer = Timer.builder("bookvault.checkout.duration")
.publishPercentiles(0.95, 0.99).register(registry);
// a gauge observes a live value - here, the current active-loan count
Gauge.builder("bookvault.loans.active", loanService, LoanService::activeCount)
.register(registry);
}
void recordBorrow() { borrows.increment(); }
<T> T timeCheckout(Supplier<T> op) { return checkoutTimer.record(op); }
}Tags: the dimensional model - and the cardinality trap
Modern metrics are dimensional: one metric name with tags (key-value labels) you slice by. Instead of
borrows_fiction, borrows_scifi, you record one bookvault.loans.borrowed with a genre tag and query
sum by (genre):
Counter.builder("bookvault.loans.borrowed")
.tag("genre", book.genre()) // good: bounded set of values
.tag("branch", book.branch()) // good: dozens of branches
.register(registry).increment();This is powerful - but tags are also the single biggest way to melt a metrics system. Every unique combination of tag values creates a separate stored time series. That's cardinality, and it multiplies:
genre (12) × branch (30) = 360 series ✅ fine
+ member_id (500,000) = 180,000,000 series 💥 catastropheNever tag with unbounded values
Tagging by user ID, email, book ID, request URL with IDs, or any high-cardinality value is the classic outage- maker - it can generate millions of time series and take down your metrics backend (and your bill). Rule: tags must have a small, bounded set of possible values (status, genre, region, HTTP method). High-cardinality identifiers belong in traces and logs, not metric tags.
What you already get free
Before writing custom meters, know Spring Boot auto-instruments a lot: http.server.requests (a Timer tagged
by URI, method, status - your latency and error rate), JVM memory and GC, thread pools, DataSource
connections, and cache stats. Export them to Prometheus by adding micrometer-registry-prometheus, which
exposes /actuator/prometheus for Prometheus to scrape. Add custom meters only for business signals
(borrows, checkout duration) the framework can't know about.
A counter is the odometer - only ever climbs; what you care about is the rate (km per hour) not the raw number. A gauge is the fuel or temperature needle - it rises and falls to show the value right now. A timer is a stopwatch on each trip that also tallies how many trips and their spread. Tags are labeling each reading by route and driver so you can compare - but label every reading by a unique trip GPS coordinate (unbounded) and your logbook becomes millions of one-off entries no one can read. Bounded labels group; unbounded ones explode.
BookVault adds fines for overdue returns. Product wants: (1) how many fines are charged, sliced by branch; (2) the current total unpaid fine amount across the system; (3) how long the fine-calculation step takes at p99. Pick the meter type for each, and name one tag you must NOT add.
Why must you avoid tagging a metric with a high-cardinality value like member_id?
Key takeaways
- Micrometer is a vendor-neutral metrics facade (like SLF4J for logs) with pluggable registries; Spring Boot auto-configures it.
- Choose the meter type: Counter (monotonic, ask for rate), Gauge (live up/down value), Timer (latency with percentiles), Distribution Summary (non-time distributions).
- Dimensional metrics use one name plus tags you slice by - but every unique tag-value combination is a separate time series.
- Never tag with unbounded values (member_id, URLs with IDs) - it causes a cardinality explosion that can take down the backend; tags must be low-cardinality.
- Spring Boot auto-instruments http.server.requests, JVM, pools, and caches; add custom meters only for business signals, and export via micrometer-registry-prometheus.