Micrometer dan Prometheus di Spring Boot: Metrik Actuator di Produksi
Integrasi Micrometer dan Prometheus di Spring Boot 4: model akses Actuator, konfigurasi scrape, metrik HTTP, health probe, dan aturan alert.

Spring Boot Actuator menyediakan endpoint health, metrik, dan diagnostik, sementara Micrometer adalah facade metrik di baliknya. Menambahkan dependensi micrometer-registry-prometheus menghadirkan endpoint /actuator/prometheus yang di-scrape Prometheus pada interval tetap. Contoh-contoh berikut menargetkan Spring Boot 4.1 (GA saat ini, dengan 4.2 dalam tahap release candidate), Micrometer 1.16, dan Prometheus Java client 1.x.
Integrasi Micrometer Spring Boot dengan Prometheus terdiri dari tiga langkah: tambahkan spring-boot-starter-actuator dan micrometer-registry-prometheus, tambahkan prometheus ke management.endpoints.web.exposure.include, lalu arahkan scrape_config Prometheus ke /actuator/prometheus. Metrik JVM, HTTP, HikariCP, dan sistem dipublikasikan tanpa kode tambahan.
Setup Micrometer di Spring Boot: Actuator dan Registry Prometheus
Dependensi Maven yang Diperlukan
Starter Actuator mengaktifkan endpoint dan sekaligus membawa Micrometer. Registry Prometheus memformat setiap meter dalam format eksposisi Prometheus. Starter AspectJ hanya diperlukan untuk anotasi @Timed dan @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>Dua jebakan versi masih muncul di tutorial lama. Sejak Micrometer 1.13 (Spring Boot 3.3), micrometer-registry-prometheus dibangun di atas Prometheus Java client 1.x; kode 0.x yang lama berada di modul micrometer-registry-prometheus-simpleclient yang sudah deprecated, dan dokumentasi Micrometer Prometheus menyebutnya hanya sebagai solusi sementara. Selain itu, di Spring Boot 4, pom.xml yang masih mendeklarasikan spring-boot-starter-aop tidak lagi dapat di-resolve: panduan migrasi Spring Boot 4.0 mengganti namanya menjadi spring-boot-starter-aspectj.
Konfigurasi Endpoint Actuator
Secara default hanya endpoint health yang diekspos via HTTP. Endpoint /actuator/prometheus sudah ada begitu registry berada di classpath, tetapi mengembalikan 404 sampai endpoint tersebut ditambahkan ke daftar exposure.
Spring Boot 3.4 mengganti sakelar on/off (management.endpoints.enabled-by-default, management.endpoint.<id>.enabled) dengan model akses, dan properti lama kini deprecated. Pola opt-in menetapkan akses default ke none lalu memberikan read-only per endpoint:
# 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: unrestrictedExposure dan akses adalah dua kontrol yang terpisah. exposure.include menentukan apakah sebuah endpoint dipetakan ke HTTP; access menentukan apa yang diizinkan endpoint tersebut setelah dipetakan. File properti yang masih memakai enabled: true tetap berjalan dengan peringatan deprecation, dan itulah saat yang tepat untuk bermigrasi.
Opsi show-details: when-authorized menampilkan detail kesehatan hanya kepada pengguna terautentikasi dengan peran yang sesuai. Endpoint itu sendiri diamankan dengan SecurityFilterChain khusus:
// 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();
}
}Di Spring Boot 4, EndpointRequest berpindah ke org.springframework.boot.security.autoconfigure.actuate.web.servlet, seperti terlihat di referensi endpoint Actuator. DSL Java AuthorizedUrl pada authorizeHttpRequests tidak memiliki metode hasIpAddress: aturan IP melewati IpAddressAuthorizationManager.hasIpAddress, yang tersedia sejak Spring Security 6.3.
Metrik Kustom dengan Micrometer
Counter, Gauge, dan Timer Aplikatif
Micrometer menyediakan beberapa tipe metrik yang sesuai dengan kasus penggunaan berbeda. Counter mengukur peristiwa kumulatif, gauge mengukur nilai sesaat, dan timer mengukur durasi sebuah operasi.
// 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);
}
}Dua detail penamaan penting saat memakai registry Prometheus. Nama meter Micrometer memakai titik dan dikonversi ke snake case (orders.processing.duration menjadi orders_processing_duration_seconds). Counter otomatis mendapat sufiks _total, sehingga menamai counter orders.created.total hanya menambah noise.
publishPercentiles(0.5, 0.95, 0.99) menghitung persentil di dalam setiap JVM, dan nilai tersebut tidak dapat diagregasi antar-instance. publishPercentileHistogram() mempublikasikan bucket yang dapat diagregasi histogram_quantile() di seluruh armada instance, sehingga opsi inilah yang dipilih ketika memakai Prometheus.
Anotasi @Timed dan @Counted
Untuk menghindari kode boilerplate, Micrometer menyediakan anotasi yang menginstrumentasi metode melalui 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: trueSejak Spring Boot 3.2, bean TimedAspect dan CountedAspect tidak perlu lagi dideklarasikan manual: mengatur management.observations.annotations.enabled=true dengan spring-boot-starter-aspectj di classpath sudah mengonfigurasinya secara otomatis. Nilai default properti ini adalah false, yang menjelaskan sebagian besar laporan "@Timed saya tidak menghasilkan apa-apa".
Anotasi @Timed dan @Counted hanya bekerja pada metode public dari bean Spring yang dipanggil melalui proxy. Panggilan dari satu metode ke metode lain di kelas yang sama melewati proxy sehingga tidak terinstrumentasi, dan anotasi pada metode private diabaikan. Menganotasi controller Spring MVC yang sudah terinstrumentasi menghasilkan observasi ganda.
Siap menguasai wawancara Spring Boot Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Endpoint Kesehatan Kustom
Health Indicator Bisnis
Health Indicator memverifikasi status dependensi eksternal dan komponen bisnis kritis. Spring Boot menyediakan indicator default untuk basis data, Redis, dan layanan umum lainnya. Di Spring Boot 4, API health berada di modul tersendiri spring-boot-health, dan Health serta HealthIndicator berpindah dari org.springframework.boot.actuate.health ke 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();
}
}
}Indicator ini muncul di /actuator/health dengan nama paymentGateway, yaitu nama bean tanpa sufiks HealthIndicator.
Grup Kesehatan untuk Kubernetes
Spring Boot 4 mengaktifkan probe liveness dan readiness secara default di semua platform, tidak hanya ketika mendeteksi Kubernetes. Grup /actuator/health/liveness dan /actuator/health/readiness sudah tersedia tanpa konfigurasi apa pun, dan bean LivenessStateHealthIndicator atau ReadinessStateHealthIndicator tidak perlu dideklarasikan manual. Konfigurasi hanya diperlukan untuk menambahkan komponen ke sebuah grup atau mendefinisikan grup kustom:
# 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-authorizedGrup liveness sebaiknya tetap terbatas pada livenessState. Menambahkan basis data atau gateway eksternal ke liveness membuat Kubernetes me-restart semua pod ketika dependensi tersebut mati, sehingga gangguan parsial berubah menjadi gangguan total. Dependensi eksternal masuk ke readiness, dan hanya jika aplikasi tidak dapat melayani permintaan apa pun tanpanya.
Probe Kubernetes kemudian diarahkan ke endpoint khusus:
# 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: 3Dengan add-additional-paths: true, probe tetap berada di port aplikasi meskipun Actuator berjalan di port manajemen terpisah, sehingga server utama yang bermasalah tetap terdeteksi oleh probe.
Integrasi Prometheus dengan Spring Boot dan Grafana
Konfigurasi Scraping Prometheus
Prometheus mengumpulkan metrik dengan menanyakan endpoint /actuator/prometheus secara berkala. Konfigurasi scrape menentukan target dan path metrik, karena Prometheus secara default memakai /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: (.+)Target memakai port 9090 karena konfigurasi produksi di bagian selanjutnya memindahkan Actuator ke port manajemen terpisah. Tanpa management.server.port, target scrape adalah port aplikasi.
Metrik Spring Boot Actuator yang Dipublikasikan secara Default
Actuator dengan Micrometer menyajikan metrik JVM, proses, HikariCP, dan HTTP tanpa kode apa pun. Kueri PromQL berikut mencakup metrik yang pertama kali dipantau di produksi:
# 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"} mengembalikan satu deret per memory pool (Eden, Survivor, Old Gen pada G1). Membaginya dengan nilai max tanpa sum by (instance) menghasilkan rasio per pool yang tampak salah di dashboard.
Bean MeterBinder menambahkan gauge tingkat aplikasi yang tidak dicakup oleh metrik default:
// 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 dan process.uptime sudah dipublikasikan oleh binder bawaan Micrometer. Contoh di atas memperlihatkan polanya, sedangkan binder nyata sebaiknya hanya mendaftarkan apa yang tidak tersedia secara default.
Dashboard Grafana Siap Pakai
Grafana menyediakan dashboard komunitas untuk Spring Boot yang bekerja dengan metrik Actuator standar. Dashboard yang ditulis manual memakai tipe panel timeseries dan 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"
}
]
}
]
}Banyak dashboard yang disalin dari tutorial lama masih memakai tipe panel graph dan singlestat. Singlestat dihapus di Grafana 8, dan Grafana 12 menghapus dukungan AngularJS sepenuhnya, sehingga panel tersebut hanya tampil setelah dimigrasikan ke timeseries dan stat.
Untuk mengimpor dashboard komunitas: Grafana > Dashboards > New > Import, lalu masukkan ID 4701 (JVM Micrometer) atau 12900 (SpringBoot APM). Periksa tipe panel setelah impor: revisi lama dashboard komunitas mungkin masih berisi panel Angular yang harus dimigrasikan oleh Grafana 12.
Alerting dengan Prometheus
Aturan Alert Penting
Aturan alert Prometheus memicu notifikasi ketika metrik melewati ambang batas kritis.
# 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 }}"Alert tersebut mencakup masalah produksi yang paling umum: ketersediaan, performa, dan sumber daya. Aturan HighLatency membutuhkan sum by (le, ...): tanpanya, histogram_quantile menghitung satu P99 per deret (per URI, metode, dan status), dan satu endpoint lambat dengan tiga panggilan sudah cukup untuk memicu alert.
Metrik HTTP dan Metrik Basis Data di Spring Boot
Instrumentasi Otomatis Permintaan HTTP
Spring Boot menginstrumentasi setiap permintaan Spring MVC dan WebFlux yang masuk melalui Observation API dan mempublikasikan timer http.server.requests, dengan tag method, uri, status, outcome, dan 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}Interface WebMvcTagsContributor dan WebMvcTagsProvider yang muncul di banyak contoh sudah dihapus di Spring Boot 3.0. Tag kustom kini ditambahkan dengan meng-extend DefaultServerRequestObservationConvention, sebagaimana didokumentasikan di referensi metrik 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")
);
}
}Hanya key-value berkardinalitas rendah yang menjadi label Prometheus. Tag tenant masih wajar dengan beberapa puluh tenant dan menjadi masalah kardinalitas dengan ribuan tenant. Nilai berkardinalitas tinggi (ID pengguna, ID pesanan) ditempatkan di getHighCardinalityKeyValues, yang hanya mengisi trace. Sisi tracing dari Observation API dibahas di observability Spring Boot dengan OpenTelemetry.
Metrik HikariCP dan Kueri SQL
Metrik pool HikariCP diekspos otomatis dengan prefiks hikaricp, diberi tag nama pool, berdampingan dengan gauge generik jdbc.connections. Mengatur nama pool membuat tag tersebut mudah dibaca:
# 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: 1800000Pengukuran waktu per kueri tidak dicakup oleh Spring Boot. Proyek datasource-micrometer membungkus DataSource dan membuat satu observasi per koneksi, kueri, dan result set, yang menghasilkan metrik sekaligus span trace. Lini 2.x menargetkan Spring Boot 4, lini 1.x menargetkan 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>Cara ini menggantikan pendekatan manual yang membungkus DataSource dengan ProxyDataSourceBuilder dan listener buatan sendiri, yang melewati auto-configuration DataSource milik Spring Boot dan harus disesuaikan setiap kali Boot di-upgrade. Kueri lambat biasanya menunjukkan pola N+1, yang dibahas di solusi kueri N+1 Spring Data JPA.
Praktik Terbaik untuk Produksi
Kardinalitas Metrik
Kardinalitas yang berlebihan menurunkan performa Prometheus. Setiap kombinasi tag yang unik menciptakan deret waktu tersendiri.
// 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 dengan bucket histogram menjawab pertanyaan latensi lebih baik daripada counter dengan tag bucket buatan sendiri: PromQL dapat menghitung persentil apa pun dari bucket tersebut, dan batas bucket tetap konsisten di semua layanan.
Konfigurasi Siap Produksi
# 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}Dengan access.default: none, endpoint seperti env, beans, configprops, dan mappings tidak dapat diakses tanpa perlu dicantumkan satu per satu. management.prometheus.metrics.export.enabled bernilai default true, dan pengaturan step tidak berpengaruh pada Prometheus: registry ini berbasis pull, dan interval scrape diatur di prometheus.yml.
Kesalahan Umum pada Actuator dan Prometheus
| Gejala | Penyebab | Solusi |
|---|---|---|
/actuator/prometheus mengembalikan 404 | Endpoint tidak ada di daftar exposure, atau registry tidak ada | Tambahkan prometheus ke management.endpoints.web.exposure.include dan pastikan micrometer-registry-prometheus ada di classpath |
| Target Prometheus menampilkan "server returned HTTP status 401" | Spring Security melindungi semua endpoint kecuali health | Buka prometheus untuk jaringan scraper atau konfigurasikan basic auth di konfigurasi scrape |
@Timed tidak menghasilkan metrik | management.observations.annotations.enabled bernilai false secara default, atau AspectJ tidak ada | Atur properti ke true dan tambahkan spring-boot-starter-aspectj |
spring-boot-starter-aop tidak ditemukan setelah upgrade | Diganti namanya di Spring Boot 4 | Gunakan spring-boot-starter-aspectj |
Import Health atau HealthIndicator gagal di Boot 4 | Package berpindah ke org.springframework.boot.health.contributor | Perbarui import |
Peringatan deprecation pada management.endpoint.*.enabled | Model on/off diganti di Spring Boot 3.4 | Gunakan management.endpoints.access.default dan management.endpoint.<id>.access |
| Persentil di dashboard jauh berbeda dari kenyataan | Nilai publishPercentiles dirata-ratakan antar-pod | Gunakan publishPercentileHistogram() dan histogram_quantile() |
Pertanyaan Wawancara tentang Micrometer di Spring Boot
Actuator muncul di sebagian besar wawancara Spring Boot untuk posisi mid-level dan senior. Jawaban yang membedakan profil berpengalaman dari junior:
- Perbedaan liveness dan readiness: liveness memberi tahu Kubernetes untuk me-restart pod, readiness memberi tahu Kubernetes untuk berhenti mengarahkan trafik. Kandidat yang memasukkan basis data ke grup liveness belum pernah mengalaminya saat terjadi gangguan.
- Mengapa Micrometer dan bukan client Prometheus secara langsung: Micrometer adalah facade. Kode
CounterdanTimeryang sama dapat mengekspor ke Prometheus, Datadog, atau OTLP hanya dengan mengganti dependensi, dan instrumentasi bawaan Spring (HTTP, pool JDBC, cache) ditulis di atasnya. - Persentil sisi klien versus histogram: persentil sisi klien tidak dapat diagregasi antar-instance, histogram dapat. Ini pertanyaan jebakan yang paling sering muncul untuk topik ini.
- Arti kardinalitas dan bagaimana ia merusak Prometheus: satu deret waktu per kombinasi label yang unik; tag ID pengguna melipatgandakan penggunaan memori sebanyak jumlah pengguna.
- Cara mengamankan Actuator: batasi exposure, tetapkan level akses, jalankan manajemen di port terpisah, dan lindungi dengan
SecurityFilterChainkhusus.
Pertanyaan sejenis lainnya dikumpulkan di modul pertanyaan wawancara Spring Boot Actuator.
Sumber
- Spring Boot 4.0 Migration Guide:
spring-boot-starter-aopdiganti namanya menjadispring-boot-starter-aspectj, probe aktif secara default, modul baruspring-boot-health - Spring Boot 3.4 Release Notes:
enabled-by-defaultdanendpoint.<id>.enableddeprecated dan digantikan model akses - Referensi endpoint Spring Boot Actuator: properti akses, package
EndpointRequest, health indicator diorg.springframework.boot.health.contributor, grup probe dan path tambahan - Referensi metrik Spring Boot:
DefaultServerRequestObservationConvention, metrik HikariCP danjdbc.connections, deprecation simpleclient - Referensi observability Spring Boot:
management.observations.annotations.enableddan kebutuhan AspectJ - Dokumentasi Micrometer Prometheus: registry Prometheus Java client 1.x dan modul simpleclient lama
- datasource-micrometer di GitHub: 2.x untuk Spring Boot 4, 1.x untuk Spring Boot 3
- Grafana: penghapusan AngularJS di Grafana 12: panel Graph dan Singlestat digantikan Time series dan Stat
- Rilis Spring Boot: 4.1.2 sebagai rilis GA terkini, 4.2.0-RC1 dalam tahap pra-rilis
Hal yang Perlu Diingat tentang Micrometer Spring Boot dan Prometheus
- Tiga langkah menuju scrape yang berfungsi: starter Actuator,
micrometer-registry-prometheus,prometheusdi daftar exposure - Model akses sejak 3.4:
management.endpoints.access.default: noneditambahread-onlyper endpoint menggantikan flagenabledyang deprecated - Perubahan di Spring Boot 4:
spring-boot-starter-aspectj, API health diorg.springframework.boot.health.contributor,EndpointRequestdiorg.springframework.boot.security.autoconfigure.actuate.web.servlet, probe aktif secara default - Anotasi butuh sakelar:
management.observations.annotations.enabled=true, tanpa beanTimedAspectmanual - Tag HTTP melalui Observation API: extend
DefaultServerRequestObservationConvention, jaga label tetap berkardinalitas rendah - Histogram, bukan persentil sisi klien:
publishPercentileHistogram()dansum by (le)di setiaphistogram_quantile - Liveness tetap lokal: dependensi eksternal masuk ke readiness atau grup kustom, tidak pernah ke liveness
Default ini memberi layanan Spring Boot 4 metrik yang teragregasi dengan benar antar-pod, endpoint yang tidak membocorkan apa pun ke jaringan publik, dan alert yang terpicu oleh insiden nyata, bukan oleh satu URI yang lambat.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Bisakah kamu menemukan bug di Spring Boot?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 26 Oktober 2026
Tag
Bagikan
Artikel terkait

Observability Spring Boot 2026: OpenTelemetry, Distributed Tracing, dan Pertanyaan Interview
Pelajari observability Spring Boot dengan OpenTelemetry dan Micrometer Tracing. Panduan lengkap konfigurasi distributed tracing, Observation API, ekspor OTLP, serta persiapan interview teknis.

Spring Boot Structured Logging 2026: Log JSON untuk Produksi dengan Logback dan OpenTelemetry
Panduan lengkap structured logging Spring Boot 4.x. Dukungan JSON native, OpenTelemetry starter, MDC tracing, dan integrasi ELK Stack untuk observability produksi.

Pekerjaan Freelance Developer Java Spring Boot 2026: Cara Mencari Kontrak, Tarif Harian, dan Persiapan
Panduan lengkap untuk developer freelance Java Spring Boot di tahun 2026: platform pencarian kontrak, tarif harian di berbagai negara, dan cara mempersiapkan wawancara teknis.