Spring Boot Micrometer i Prometheus: metryki Actuator na produkcji
Integracja Spring Boot Micrometer z Prometheus w Spring Boot 4: model dostępu Actuator, konfiguracja scrapowania, metryki HTTP, sondy zdrowia i reguły alertów.

Spring Boot Actuator udostępnia endpointy zdrowia, metryk i diagnostyki, a Micrometer jest fasadą metryk, na której się opierają. Dodanie zależności micrometer-registry-prometheus daje endpoint /actuator/prometheus, który Prometheus odpytuje w stałym interwale. Przykłady poniżej dotyczą Spring Boot 4.1 (aktualne wydanie GA, wersja 4.2 jest na etapie release candidate), Micrometer 1.16 i klienta Prometheus Java 1.x.
Integracja Spring Boot Micrometer z Prometheus to trzy kroki: dodanie spring-boot-starter-actuator i micrometer-registry-prometheus, dopisanie prometheus do management.endpoints.web.exposure.include, a następnie skierowanie scrape_config Prometheus na /actuator/prometheus. Metryki JVM, HTTP, HikariCP i systemowe są publikowane bez dodatkowego kodu.
Konfiguracja Spring Boot Micrometer: Actuator i registry Prometheus
Wymagane Zależności Maven
Starter Actuator aktywuje endpointy i dociąga Micrometer. Registry Prometheus formatuje każdy miernik w formacie ekspozycji Prometheus. Starter AspectJ jest potrzebny wyłącznie dla adnotacji @Timed i @Counted.
<!-- pom.xml -->
<!-- Actuator + Micrometer + Prometheus (Spring Boot 4.x) -->
<dependencies>
<!-- Spring Boot Actuator: monitoring endpoints, includes Micrometer -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Micrometer Registry Prometheus (Prometheus Java client 1.x) -->
<!-- Exposes metrics in Prometheus format at /actuator/prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<!-- AspectJ for @Timed and @Counted -->
<!-- Spring Boot 4 renamed spring-boot-starter-aop to spring-boot-starter-aspectj -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
</dependencies>Starsze tutoriale zawierają dwie pułapki związane z wersjami. Od Micrometer 1.13 (Spring Boot 3.3) micrometer-registry-prometheus bazuje na kliencie Prometheus Java 1.x; stary kod 0.x trafił do przestarzałego modułu micrometer-registry-prometheus-simpleclient, który dokumentacja Micrometer dla Prometheus przedstawia wyłącznie jako rozwiązanie przejściowe. Z kolei w Spring Boot 4 plik pom.xml, który nadal deklaruje spring-boot-starter-aop, nie daje się już rozwiązać: przewodnik migracji do Spring Boot 4.0 zmienia jego nazwę na spring-boot-starter-aspectj.
Konfiguracja Endpointów Actuator
Domyślnie po HTTP wystawiony jest wyłącznie endpoint health. Endpoint /actuator/prometheus istnieje od chwili, gdy registry znajdzie się na classpath, ale zwraca 404, dopóki nie zostanie dodany do listy ekspozycji.
Spring Boot 3.4 zastąpił przełączniki włącz/wyłącz (management.endpoints.enabled-by-default, management.endpoint.<id>.enabled) modelem dostępu, a stare właściwości są oznaczone jako przestarzałe. Wzorzec opt-in ustawia domyślny dostęp na none i przyznaje read-only dla każdego endpointu osobno:
# application.yml
# Actuator configuration for production (Spring Boot 3.4+ access model)
management:
endpoints:
# Every endpoint is inaccessible unless granted below
access:
default: none
web:
exposure:
# Endpoints exposed over HTTP
# health, info, prometheus are the minimum for monitoring
include: health,info,prometheus,metrics,env,loggers
base-path: /actuator
endpoint:
health:
access: read-only
show-details: when-authorized
show-components: when-authorized
info:
access: read-only
prometheus:
access: read-only
metrics:
access: read-only
env:
access: read-only
# Mask sensitive values for anonymous callers
show-values: when-authorized
loggers:
# unrestricted allows POST to change a log level at runtime
access: unrestrictedEkspozycja i dostęp to dwa odrębne mechanizmy. exposure.include decyduje, czy endpoint jest mapowany po HTTP; access decyduje, na co endpoint pozwala po zmapowaniu. Plik właściwości, który nadal używa enabled: true, działa dalej z ostrzeżeniem o przestarzałości i to jest właściwy moment na migrację.
Opcja show-details: when-authorized wyświetla szczegóły zdrowia tylko uwierzytelnionym użytkownikom o odpowiedniej roli. Same endpointy zabezpiecza dedykowany SecurityFilterChain:
// Securing Actuator endpoints (Spring Boot 4, Spring Security 7)
package com.example.monitoring.config;
import static org.springframework.security.web.access.IpAddressAuthorizationManager.hasIpAddress;
import org.springframework.boot.security.autoconfigure.actuate.web.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration(proxyBeanMethods = false)
public class ActuatorSecurityConfig {
@Bean
SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.securityMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(auth -> auth
// Health and info public for load balancers
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
// Prometheus accessible from the internal network only
.requestMatchers(EndpointRequest.to("prometheus")).access(hasIpAddress("10.0.0.0/8"))
// Other endpoints restricted to admins
.anyRequest().hasRole("ACTUATOR_ADMIN")
)
.httpBasic(basic -> {})
.build();
}
}W Spring Boot 4 klasa EndpointRequest została przeniesiona do org.springframework.boot.security.autoconfigure.actuate.web.servlet, co pokazuje dokumentacja endpointów Actuator. Javowe DSL AuthorizedUrl w authorizeHttpRequests nie ma metody hasIpAddress: reguła IP przechodzi przez IpAddressAuthorizationManager.hasIpAddress, dostępne od Spring Security 6.3.
Własne Metryki z Micrometer
Counter, Gauge i Timer Aplikacyjne
Micrometer oferuje kilka typów metryk dopasowanych do różnych przypadków użycia. Counter mierzy zdarzenia kumulatywne, gauge wartości chwilowe, a timer czas trwania operacji.
// Custom business metrics service
package com.example.monitoring.metrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Service;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
@Service
public class OrderMetricsService {
// Counter for orders created
private final Counter ordersCreatedCounter;
// Timer to measure processing duration
private final Timer orderProcessingTimer;
// Atomic value for pending orders gauge
private final AtomicInteger pendingOrdersCount = new AtomicInteger(0);
public OrderMetricsService(MeterRegistry registry) {
// Exported as orders_created_total: the _total suffix is added by the registry
this.ordersCreatedCounter = Counter.builder("orders.created")
.description("Total number of orders created")
.tag("channel", "web")
.register(registry);
// Timer with histogram buckets: percentiles computed server-side in PromQL
this.orderProcessingTimer = Timer.builder("orders.processing.duration")
.description("Order processing duration")
.publishPercentileHistogram()
.register(registry);
// Gauge linked to atomic value
// Read on each scrape
Gauge.builder("orders.pending.count", pendingOrdersCount, AtomicInteger::get)
.description("Number of orders pending processing")
.register(registry);
}
public void recordOrderCreated() {
ordersCreatedCounter.increment();
pendingOrdersCount.incrementAndGet();
}
public void recordOrderProcessed(Runnable processingLogic) {
// Measures execution duration
orderProcessingTimer.record(processingLogic);
pendingOrdersCount.decrementAndGet();
}
public <T> T recordOrderProcessedWithResult(Supplier<T> processingLogic) {
return orderProcessingTimer.record(processingLogic);
}
}Przy registry Prometheus liczą się dwa szczegóły nazewnictwa. Nazwy mierników Micrometer używają kropek i są konwertowane do snake case (orders.processing.duration staje się orders_processing_duration_seconds). Countery automatycznie otrzymują sufiks _total, więc nazwanie countera orders.created.total jedynie dodaje szum.
publishPercentiles(0.5, 0.95, 0.99) oblicza percentyle wewnątrz każdej JVM i tych wartości nie da się agregować między instancjami. publishPercentileHistogram() publikuje buckety, które histogram_quantile() agreguje dla całej floty, i to właśnie tę opcję należy wybrać przy Prometheus.
Adnotacje @Timed i @Counted
Aby uniknąć kodu boilerplate, Micrometer dostarcza adnotacje, które instrumentują metody przez AspectJ.
// Automatic instrumentation with annotations
package com.example.monitoring.service;
import io.micrometer.core.annotation.Counted;
import io.micrometer.core.annotation.Timed;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
private final PaymentValidator paymentValidator;
public PaymentService(PaymentValidator paymentValidator) {
this.paymentValidator = paymentValidator;
}
// @Timed creates a Timer
// Measures each call and publishes count, sum, max and histogram buckets
@Timed(
value = "payment.process.duration",
description = "Payment processing duration",
histogram = true
)
public PaymentResult processPayment(PaymentRequest request) {
// Call through another bean: the proxy intercepts it
paymentValidator.validate(request);
return executePayment(request);
}
// @Counted increments a counter on each call
// Useful for discrete events
@Counted(
value = "payment.refunds",
description = "Total number of refunds"
)
public void refundPayment(String transactionId) {
// Refund logic
}
}# application.yml
# Enables TimedAspect, CountedAspect and the other Micrometer annotation aspects
management:
observations:
annotations:
enabled: trueOd Spring Boot 3.2 ręczne deklarowanie beanów TimedAspect i CountedAspect nie jest już konieczne: ustawienie management.observations.annotations.enabled=true przy spring-boot-starter-aspectj na classpath konfiguruje je automatycznie. Właściwość ma domyślnie wartość false, co tłumaczy większość zgłoszeń typu „moje @Timed nic nie robi”.
Adnotacje @Timed i @Counted działają wyłącznie na publicznych metodach beanów Springa wywoływanych przez proxy. Wywołanie jednej metody z innej w tej samej klasie omija proxy i nie jest instrumentowane, a adnotacja na metodzie prywatnej jest ignorowana. Adnotowanie kontrolera Spring MVC, który jest już instrumentowany, daje zduplikowane obserwacje.
Gotowy na rozmowy o Spring Boot?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Własne Endpointy Zdrowia
Health Indicators Biznesowe
Health Indicators sprawdzają stan zewnętrznych zależności i krytycznych komponentów biznesowych. Spring Boot udostępnia domyślne wskaźniki dla baz danych, Redisa i innych typowych usług. W Spring Boot 4 API zdrowia znajduje się we własnym module spring-boot-health, a Health i HealthIndicator zostały przeniesione z org.springframework.boot.actuate.health do org.springframework.boot.health.contributor.
// Health indicator for payment gateway (Spring Boot 4 packages)
package com.example.monitoring.health;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import java.time.Duration;
import java.time.Instant;
@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {
private final RestClient restClient;
private final String gatewayHealthUrl;
public PaymentGatewayHealthIndicator(RestClient.Builder restClientBuilder) {
this.restClient = restClientBuilder.build();
this.gatewayHealthUrl = "https://api.payment-gateway.com/health";
}
@Override
public Health health() {
Instant start = Instant.now();
try {
// Call gateway health endpoint
var response = restClient.get()
.uri(gatewayHealthUrl)
.retrieve()
.toBodilessEntity();
Duration responseTime = Duration.between(start, Instant.now());
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up()
.withDetail("responseTime", responseTime.toMillis() + "ms")
.withDetail("statusCode", response.getStatusCode().value())
.build();
} else {
return Health.down()
.withDetail("statusCode", response.getStatusCode().value())
.withDetail("reason", "Unexpected status code")
.build();
}
} catch (Exception e) {
Duration responseTime = Duration.between(start, Instant.now());
return Health.down()
.withDetail("error", e.getClass().getSimpleName())
.withDetail("message", e.getMessage())
.withDetail("responseTime", responseTime.toMillis() + "ms")
.build();
}
}
}Wskaźnik ten pojawia się w /actuator/health pod nazwą paymentGateway: to nazwa beana bez sufiksu HealthIndicator.
Grupy Zdrowia dla Kubernetes
Spring Boot 4 domyślnie włącza sondy liveness i readiness na każdej platformie, a nie tylko po wykryciu Kubernetes. Grupy /actuator/health/liveness i /actuator/health/readiness istnieją bez żadnej konfiguracji, a ręczne deklarowanie beanów LivenessStateHealthIndicator czy ReadinessStateHealthIndicator jest zbędne. Konfiguracja jest potrzebna tylko do dodania komponentów do grupy lub zdefiniowania własnych grup:
# application.yml
# Health groups configuration for Kubernetes
management:
endpoint:
health:
probes:
# true by default in Spring Boot 4
enabled: true
# Also serve the groups at /livez and /readyz on the main server port
add-additional-paths: true
group:
# Readiness: can the application receive traffic?
readiness:
include: readinessState,db
# Custom group for critical dependencies
critical:
include: db,paymentGateway
show-details: when-authorizedGrupa liveness powinna ograniczać się do livenessState. Dodanie bazy danych lub zewnętrznej bramki do liveness sprawia, że Kubernetes restartuje każdy pod, gdy ta zależność przestaje działać, co zamienia częściową awarię w całkowitą. Zależności zewnętrzne należą do readiness, i to tylko wtedy, gdy aplikacja bez nich nie obsłuży żadnego żądania.
Sondy Kubernetes wskazują następnie na dedykowane endpointy:
# kubernetes-deployment.yml
# Kubernetes probes configuration
spec:
containers:
- name: order-service
livenessProbe:
httpGet:
path: /livez
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3Dzięki add-additional-paths: true sondy pozostają na porcie aplikacji nawet wtedy, gdy Actuator działa na osobnym porcie zarządzania, więc niedziałający serwer główny zostaje wykryty przez sondę.
Integracja Spring Boot z Prometheus i Grafana
Konfiguracja Scrapowania w Prometheus
Prometheus zbiera metryki, cyklicznie odpytując endpoint /actuator/prometheus. Konfiguracja scrapowania definiuje cele i ścieżkę metryk, ponieważ Prometheus domyślnie używa /metrics.
# prometheus.yml
# Prometheus configuration for Spring Boot
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'spring-boot-apps'
metrics_path: '/actuator/prometheus'
scrape_interval: 10s
static_configs:
- targets:
- 'order-service:9090'
- 'payment-service:9090'
- 'inventory-service:9090'
# Relabeling to strip the port from the instance label
relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: '([^:]+):\d+'
replacement: '${1}'
# Kubernetes service discovery
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
# Only scrape pods with annotation
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)Cele używają portu 9090, ponieważ konfiguracja produkcyjna opisana dalej przenosi Actuator na osobny port zarządzania. Bez management.server.port celem scrapowania jest port aplikacji.
Metryki Spring Boot Actuator Publikowane Domyślnie
Actuator z Micrometer udostępnia metryki JVM, procesu, HikariCP i HTTP bez żadnego kodu. Poniższe zapytania PromQL obejmują te, które na produkcji obserwuje się w pierwszej kolejności:
# PromQL queries for JVM monitoring
# Heap memory usage, summed across heap pools
sum by (instance) (jvm_memory_used_bytes{area="heap"})
# Heap usage percentage
sum by (instance) (jvm_memory_used_bytes{area="heap"})
/ sum by (instance) (jvm_memory_max_bytes{area="heap"}) * 100
# Live threads
jvm_threads_live_threads
# Garbage collection: seconds spent paused per second
rate(jvm_gc_pause_seconds_sum[5m])
# GC pauses per minute
rate(jvm_gc_pause_seconds_count[1m]) * 60
# CPU used by the JVM process
process_cpu_usage
# Active database connections
hikaricp_connections_active
# Connection pool utilization
hikaricp_connections_active / hikaricp_connections_max * 100jvm_memory_used_bytes{area="heap"} zwraca jedną serię na pulę pamięci (Eden, Survivor, Old Gen przy G1). Dzielenie jej przez maksimum bez sum by (instance) daje proporcje per pula, które na dashboardzie wyglądają błędnie.
Bean MeterBinder dodaje gauge na poziomie aplikacji, których nie obejmują wartości domyślne:
// Additional JVM metrics
package com.example.monitoring.metrics;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.springframework.stereotype.Component;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
@Component
public class CustomJvmMetrics implements MeterBinder {
@Override
public void bindTo(MeterRegistry registry) {
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
// System load average
Gauge.builder("system.load.average", osBean, OperatingSystemMXBean::getSystemLoadAverage)
.description("System load average over 1 minute")
.register(registry);
// Available processors count
Gauge.builder("system.cpu.count", osBean, OperatingSystemMXBean::getAvailableProcessors)
.description("Number of available processors")
.register(registry);
// Application uptime
Gauge.builder("application.uptime.seconds",
ManagementFactory.getRuntimeMXBean(),
bean -> bean.getUptime() / 1000.0)
.description("Application uptime in seconds")
.register(registry);
}
}system.cpu.count i process.uptime są już publikowane przez wbudowane bindery Micrometer. Przykład pokazuje wzorzec, a rzeczywisty binder powinien rejestrować tylko to, czego brakuje w wartościach domyślnych.
Gotowe Dashboardy Grafana
Grafana oferuje dashboardy społeczności dla Spring Boot, które działają na standardowych metrykach Actuator. Dashboard pisany ręcznie używa typów paneli timeseries i stat:
{
"annotations": {
"list": []
},
"panels": [
{
"title": "Request Rate",
"type": "timeseries",
"targets": [
{
"expr": "sum by (method, uri, status) (rate(http_server_requests_seconds_count{application=\"$application\"}[5m]))",
"legendFormat": "{{method}} {{uri}} - {{status}}"
}
]
},
{
"title": "Response Time P99",
"type": "timeseries",
"targets": [
{
"expr": "histogram_quantile(0.99, sum by (le, method, uri) (rate(http_server_requests_seconds_bucket{application=\"$application\"}[5m])))",
"legendFormat": "{{method}} {{uri}}"
}
]
},
{
"title": "Error Rate",
"type": "stat",
"targets": [
{
"expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\",status=~\"5..\"}[5m])) / sum(rate(http_server_requests_seconds_count{application=\"$application\"}[5m])) * 100"
}
]
}
]
}Wiele dashboardów skopiowanych ze starszych tutoriali nadal używa typów paneli graph i singlestat. Singlestat usunięto w Grafana 8, a Grafana 12 całkowicie usunęła obsługę AngularJS, więc takie panele renderują się dopiero po migracji do timeseries i stat.
Aby zaimportować dashboard społeczności: Grafana > Dashboards > New > Import, a następnie ID 4701 (JVM Micrometer) lub 12900 (SpringBoot APM). Po imporcie warto sprawdzić typy paneli: starsze rewizje dashboardów społeczności mogą nadal zawierać panele Angular, które Grafana 12 musi zmigrować.
Alerty z Prometheus
Kluczowe Reguły Alertów
Reguły alertów Prometheus wyzwalają powiadomienia, gdy metryki przekraczają progi krytyczne.
# alerting-rules.yml
# Alert rules for Spring Boot applications
groups:
- name: spring-boot-alerts
rules:
# Alert if application is down
- alert: ApplicationDown
expr: up{job="spring-boot-apps"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Application {{ $labels.instance }} is down"
description: "{{ $labels.instance }} has been down for more than 1 minute"
# Alert on HTTP error rate
- alert: HighErrorRate
expr: |
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (application)
/
sum(rate(http_server_requests_seconds_count[5m])) by (application)
> 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate on {{ $labels.application }}"
description: "Error rate is {{ $value | humanizePercentage }}"
# Alert on P99 latency, aggregated per application
- alert: HighLatency
expr: |
histogram_quantile(0.99,
sum by (le, application) (rate(http_server_requests_seconds_bucket[5m]))
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "High latency on {{ $labels.application }}"
description: "P99 latency is {{ $value | humanizeDuration }}"
# Heap memory alert
- alert: HighHeapUsage
expr: |
sum by (instance) (jvm_memory_used_bytes{area="heap"})
/ sum by (instance) (jvm_memory_max_bytes{area="heap"})
> 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "High heap memory usage on {{ $labels.instance }}"
description: "Heap usage is at {{ $value | humanizePercentage }}"
# Database connection pool exhausted alert
- alert: DatabaseConnectionPoolExhausted
expr: |
hikaricp_connections_active
/ hikaricp_connections_max
> 0.9
for: 2m
labels:
severity: critical
annotations:
summary: "Database connection pool nearly exhausted"
description: "{{ $value | humanizePercentage }} of connections in use"
# Excessive GC alert
- alert: HighGCPause
expr: |
rate(jvm_gc_pause_seconds_sum[5m])
/ rate(jvm_gc_pause_seconds_count[5m])
> 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "High GC pause time"
description: "Average GC pause is {{ $value | humanizeDuration }}"Alerty te pokrywają najczęstsze problemy produkcyjne: dostępność, wydajność i zasoby. Reguła HighLatency wymaga sum by (le, ...): bez tego histogram_quantile oblicza osobny P99 dla każdej serii (per URI, metoda i status), a jeden wolny endpoint z trzema wywołaniami wyzwala alert.
Metryki HTTP i Bazy Danych w Spring Boot
Automatyczna Instrumentacja Żądań HTTP
Spring Boot instrumentuje każde przychodzące żądanie Spring MVC i WebFlux przez Observation API i publikuje timer http.server.requests z tagami method, uri, status, outcome i exception.
# application.yml
# HTTP metrics configuration
management:
metrics:
distribution:
# Histogram buckets for server-side percentiles
percentiles-histogram:
http.server.requests: true
# SLO buckets: count requests faster than each boundary
slo:
http.server.requests: 100ms, 500ms, 1s, 2s
tags:
# Common tags added to every meter
application: ${spring.application.name}
environment: ${spring.profiles.active:default}Interfejsy WebMvcTagsContributor i WebMvcTagsProvider, obecne w wielu przykładach, zostały usunięte w Spring Boot 3.0. Własne tagi dodaje się teraz przez rozszerzenie DefaultServerRequestObservationConvention, zgodnie z dokumentacją metryk Spring Boot:
// Adds custom tags to http.server.requests (Spring Boot 3.0+)
package com.example.monitoring.config;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
@Component
public class TenantObservationConvention extends DefaultServerRequestObservationConvention {
@Override
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
HttpServletRequest request = context.getCarrier();
String userId = request.getHeader("X-User-Id");
String tenantId = request.getHeader("X-Tenant-Id");
// Keep the default tags and add two bounded ones
return super.getLowCardinalityKeyValues(context).and(
KeyValue.of("user.type", userId != null ? "authenticated" : "anonymous"),
KeyValue.of("tenant", tenantId != null ? tenantId : "default")
);
}
}Etykietami Prometheus stają się wyłącznie pary klucz-wartość o niskiej kardynalności. Tag tenant jest akceptowalny przy kilkudziesięciu tenantach, a przy tysiącach staje się problemem kardynalności. Wartości o wysokiej kardynalności (ID użytkownika, ID zamówienia) należą do getHighCardinalityKeyValues, które zasila wyłącznie trace'y. Stronę tracingu Observation API opisuje artykuł observability Spring Boot z OpenTelemetry.
Metryki HikariCP i Zapytań SQL
Metryki puli HikariCP są udostępniane automatycznie z prefiksem hikaricp i tagiem z nazwą puli, obok ogólnych gauge jdbc.connections. Ustawienie nazwy puli sprawia, że tag jest czytelny:
# application.yml
# HikariCP pool configuration
spring:
datasource:
hikari:
# Becomes the "pool" tag on hikaricp_* metrics
pool-name: OrderServicePool
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000Spring Boot nie mierzy czasu na poziomie pojedynczych zapytań. Projekt datasource-micrometer opakowuje DataSource i tworzy obserwację dla każdego połączenia, zapytania i result setu, co daje jednocześnie metryki i spany trace'ów. Linia 2.x jest przeznaczona dla Spring Boot 4, linia 1.x dla Spring Boot 3:
<!-- pom.xml -->
<!-- JDBC observations: 2.x for Spring Boot 4, 1.x for Spring Boot 3 -->
<dependency>
<groupId>net.ttddyy.observation</groupId>
<artifactId>datasource-micrometer-spring-boot</artifactId>
<version>2.0.1</version>
</dependency>Zastępuje to ręczne podejście, w którym DataSource opakowywano w ProxyDataSourceBuilder z własnym listenerem: omijało ono autokonfigurację DataSource w Spring Boot i wymagało synchronizacji przy każdej aktualizacji Boota. Wolne zapytania zwykle wskazują na wzorzec N+1, opisany w artykule rozwiązania problemu N+1 w Spring Data JPA.
Najlepsze Praktyki Produkcyjne
Kardynalność Metryk
Nadmierna kardynalność degraduje wydajność Prometheus. Każda unikalna kombinacja tagów tworzy odrębną serię czasową.
// AVOID: unbounded cardinality
package com.example.monitoring.antipattern;
@Service
public class AntiPatternHighCardinality {
private final MeterRegistry registry;
// BAD: userId creates one series per user
public void trackUserAction(String userId, String action) {
Counter.builder("user.actions")
.tag("userId", userId) // Millions of possible values
.tag("action", action)
.register(registry)
.increment();
}
}// Controlled cardinality
package com.example.monitoring.bestpractice;
@Service
public class GoodPracticeCardinality {
private final MeterRegistry registry;
// GOOD: user category instead of ID
public void trackUserAction(User user, String action) {
Counter.builder("user.actions")
.tag("userType", user.getSubscriptionType()) // FREE, PREMIUM, ENTERPRISE
.tag("action", action)
.register(registry)
.increment();
}
// GOOD: let a timer histogram bucket durations
public void trackResponseTime(long responseTimeMs) {
Timer.builder("response.time")
.publishPercentileHistogram()
.register(registry)
.record(Duration.ofMillis(responseTimeMs));
}
}Timer z bucketami histogramu odpowiada na pytania o latencję lepiej niż counter z ręcznie budowanym tagiem bucket: PromQL oblicza z bucketów dowolny percentyl, a granice bucketów pozostają spójne między usługami.
Konfiguracja Gotowa na Produkcję
# application-production.yml
# Production configuration (Spring Boot 4)
management:
server:
# Separate port for management endpoints, not exposed publicly
port: 9090
endpoints:
access:
default: none
web:
exposure:
include: health,info,prometheus
endpoint:
health:
access: read-only
show-details: when-authorized
probes:
add-additional-paths: true
info:
access: read-only
prometheus:
access: read-only
observations:
annotations:
enabled: true
metrics:
distribution:
percentiles-histogram:
http.server.requests: true
minimum-expected-value:
http.server.requests: 1ms
maximum-expected-value:
http.server.requests: 30s
tags:
application: ${spring.application.name}
environment: production
version: ${app.version:unknown}Przy access.default: none endpointy takie jak env, beans, configprops i mappings są niedostępne bez wymieniania ich po kolei. management.prometheus.metrics.export.enabled ma domyślnie wartość true, a ustawienie step nie ma wpływu na Prometheus: to registry typu pull, a interwał scrapowania ustawia się w prometheus.yml.
Częste Błędy z Actuator i Prometheus
| Objaw | Przyczyna | Rozwiązanie |
|---|---|---|
/actuator/prometheus zwraca 404 | Endpoint nie jest na liście ekspozycji lub brakuje registry | Dodać prometheus do management.endpoints.web.exposure.include i sprawdzić, czy micrometer-registry-prometheus jest na classpath |
| Cel w Prometheus pokazuje „server returned HTTP status 401” | Spring Security chroni wszystkie endpointy poza health | Otworzyć prometheus dla sieci scrapera lub skonfigurować basic auth w konfiguracji scrapowania |
@Timed nie generuje metryki | management.observations.annotations.enabled ma domyślnie wartość false lub brakuje AspectJ | Ustawić właściwość na true i dodać spring-boot-starter-aspectj |
spring-boot-starter-aop nie zostaje znaleziony po aktualizacji | Zmiana nazwy w Spring Boot 4 | Użyć spring-boot-starter-aspectj |
Import Health lub HealthIndicator nie działa w Boot 4 | Pakiet przeniesiony do org.springframework.boot.health.contributor | Zaktualizować importy |
Ostrzeżenia o przestarzałości management.endpoint.*.enabled | Model włącz/wyłącz zastąpiony w Spring Boot 3.4 | Użyć management.endpoints.access.default i management.endpoint.<id>.access |
| Percentyle na dashboardzie mocno odbiegają od rzeczywistości | Wartości publishPercentiles uśredniane między podami | Użyć publishPercentileHistogram() i histogram_quantile() |
Pytania Rekrutacyjne o Spring Boot Micrometer
Actuator pojawia się w większości rozmów rekrutacyjnych ze Spring Boot na stanowiska mid i senior. Odpowiedzi, które odróżniają doświadczony profil od juniora:
- Różnica między liveness a readiness: liveness mówi Kubernetes, że należy zrestartować pod, readiness, że należy przestać kierować do niego ruch. Kandydat, który umieszcza bazę danych w grupie liveness, nigdy nie przeszedł z tym przez awarię.
- Dlaczego Micrometer, a nie bezpośrednio klient Prometheus: Micrometer jest fasadą. Ten sam kod
CounteriTimereksportuje do Prometheus, Datadog lub OTLP po zmianie jednej zależności, a instrumentacja samego Springa (HTTP, pula JDBC, cache) jest napisana właśnie pod nią. - Percentyle po stronie klienta a histogramy: percentyli liczonych po stronie klienta nie da się agregować między instancjami, histogramy tak. To najczęstsze podchwytliwe pytanie w tym temacie.
- Czym jest kardynalność i jak psuje Prometheus: jedna seria czasowa na każdą unikalną kombinację etykiet; tag z ID użytkownika mnoży zużycie pamięci przez liczbę użytkowników.
- Jak zabezpieczyć Actuator: ograniczyć ekspozycję, ustawić poziomy dostępu, uruchomić zarządzanie na osobnym porcie i chronić je dedykowanym
SecurityFilterChain.
Więcej pytań tego typu zawiera moduł pytania rekrutacyjne o Spring Boot Actuator.
Źródła
- Spring Boot 4.0 Migration Guide: zmiana nazwy
spring-boot-starter-aopnaspring-boot-starter-aspectj, sondy włączone domyślnie, nowy modułspring-boot-health - Spring Boot 3.4 Release Notes:
enabled-by-defaultiendpoint.<id>.enabledprzestarzałe na rzecz modelu dostępu - Dokumentacja endpointów Spring Boot Actuator: właściwości dostępu, pakiet
EndpointRequest, health indicators worg.springframework.boot.health.contributor, grupy sond i dodatkowe ścieżki - Dokumentacja metryk Spring Boot:
DefaultServerRequestObservationConvention, metryki HikariCP ijdbc.connections, przestarzałość simpleclient - Dokumentacja observability Spring Boot:
management.observations.annotations.enabledi wymóg AspectJ - Dokumentacja Micrometer dla Prometheus: registry na kliencie Prometheus Java 1.x i starszy moduł simpleclient
- datasource-micrometer na GitHub: 2.x dla Spring Boot 4, 1.x dla Spring Boot 3
- Grafana: usunięcie AngularJS w Grafana 12: panele Graph i Singlestat zastąpione przez Time series i Stat
- Wydania Spring Boot: 4.1.2 jako aktualne wydanie GA, 4.2.0-RC1 jako pre-release
Co Zapamiętać o Spring Boot Micrometer i Prometheus
- Trzy kroki do działającego scrapowania: starter Actuator,
micrometer-registry-prometheus,prometheusna liście ekspozycji - Model dostępu od wersji 3.4:
management.endpoints.access.default: noneplusread-onlydla każdego endpointu zastępuje przestarzałe flagienabled - Zmiany w Spring Boot 4:
spring-boot-starter-aspectj, API zdrowia worg.springframework.boot.health.contributor,EndpointRequestworg.springframework.boot.security.autoconfigure.actuate.web.servlet, sondy włączone domyślnie - Adnotacje wymagają przełącznika:
management.observations.annotations.enabled=true, bez ręcznego beanaTimedAspect - Tagi HTTP przez Observation API: rozszerzenie
DefaultServerRequestObservationConvention, etykiety o niskiej kardynalności - Histogramy zamiast percentyli po stronie klienta:
publishPercentileHistogram()isum by (le)w każdymhistogram_quantile - Liveness pozostaje lokalne: zależności zewnętrzne trafiają do readiness lub własnej grupy, nigdy do liveness
Te ustawienia domyślne dają usłudze Spring Boot 4 metryki, które poprawnie agregują się między podami, endpointy, które nie ujawniają niczego w sieci publicznej, oraz alerty reagujące na rzeczywiste incydenty, a nie na jeden wolny URI.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Znajdziesz błąd w Spring Boot?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 26 października 2026
Tagi
Udostępnij
Powiązane artykuły

Obserwowalność w Spring Boot 2026: OpenTelemetry, Distributed Tracing i pytania rekrutacyjne
Kompleksowy przewodnik po obserwowalności Spring Boot z OpenTelemetry i Micrometer Tracing. Konfiguracja distributed tracing, Observation API, eksport OTLP oraz przygotowanie do rozmów kwalifikacyjnych.

Strukturalne logowanie Spring Boot 2026: logi JSON produkcyjne z Logback i OpenTelemetry
Kompletny przewodnik po strukturalnym logowaniu Spring Boot 4.x. Natywne wsparcie JSON, starter OpenTelemetry, MDC tracing i integracja z ELK Stack dla obserwowalności produkcyjnej.

Praca freelance jako Java Spring Boot developer w 2026: gdzie szukać kontraktów, stawki dzienne, jak się przygotować
Gdzie szukać kontraktów freelance Spring Boot w 2026 roku, aktualne stawki dzienne w Europie oraz jak przygotować się do rozmów technicznych.