Careeroza
Metrics with Micrometer
Built-in Metrics
Actuator uses Micrometer, a "SLF4J for metrics": you record metrics once and export them to Prometheus, Datadog, New Relic or CloudWatch. Many metrics are automatic: http.server.requests, jvm.memory.used, hikaricp.connections.active, cache.gets.
Example
curl -s "localhost:8081/actuator/metrics/http.server.requests?tag=uri:/api/products/{id}"Example output
{"name":"http.server.requests","baseUnit":"seconds","measurements":[{"statistic":"COUNT","value":15234.0},{"statistic":"TOTAL_TIME","value":182.4},{"statistic":"MAX","value":0.41}],"availableTags":[{"tag":"status","values":["200","404"]},{"tag":"method","values":["GET"]}]}Custom Business Metrics
@Service
public class CheckoutService {
private final Counter ordersPlaced;
private final Timer paymentTimer;
private final DistributionSummary orderValue;
public CheckoutService(MeterRegistry registry, CartRepository carts) {
this.ordersPlaced = Counter.builder("shopease.orders.placed")
.description("Orders placed").tag("channel", "web").register(registry);
this.paymentTimer = Timer.builder("shopease.payment.duration")
.publishPercentiles(0.5, 0.95, 0.99).register(registry);
this.orderValue = DistributionSummary.builder("shopease.order.value")
.baseUnit("inr").register(registry);
Gauge.builder("shopease.carts.active", carts, CartRepository::countActive).register(registry);
}
public OrderResponse checkout(Cart cart) {
PaymentResult result = paymentTimer.record(() -> paymentGateway.charge(cart));
ordersPlaced.increment();
orderValue.record(cart.total().doubleValue());
...
}
}| Meter | Use |
|---|---|
| Counter | only goes up: orders placed, failed payments |
| Gauge | a current value: active carts, queue size |
| Timer | how long something takes, plus count |
| DistributionSummary | distribution of values: order amount, file size |
A Mini Meter Registry
Example
Counters and timers are simple ideas. This plain-Java registry records them with tags and prints Prometheus-style lines:
import java.util.*;
public class Main {
public static void main(String[] args) {
Registry registry = new Registry();
registry.increment("shopease_orders_placed_total", "channel=web");
registry.increment("shopease_orders_placed_total", "channel=web");
registry.increment("shopease_orders_placed_total", "channel=app");
for (long ms : new long[] { 120, 340, 95, 410 }) registry.time("shopease_payment_seconds", ms);
registry.print();
}
}
class Registry {
private final Map<String, Double> counters = new TreeMap<>();
private final Map<String, List<Long>> timers = new TreeMap<>();
void increment(String name, String tags) { counters.merge(name + "{" + tags + "}", 1.0, Double::sum); }
void time(String name, long millis) { timers.computeIfAbsent(name, k -> new ArrayList<>()).add(millis); }
void print() {
counters.forEach((k, v) -> System.out.println(k + " " + v));
timers.forEach((name, list) -> {
long total = list.stream().mapToLong(Long::longValue).sum();
long max = Collections.max(list);
System.out.println(name + "_count " + list.size());
System.out.println(name + "_sum " + total / 1000.0);
System.out.println(name + "_max " + max / 1000.0);
});
}
}Output
shopease_orders_placed_total{channel=app} 1.0
shopease_orders_placed_total{channel=web} 2.0
shopease_payment_seconds_count 4
shopease_payment_seconds_sum 0.965
shopease_payment_seconds_max 0.41