Spring Boot Micrometer ve Prometheus: Üretimde Actuator Metrikleri

Spring Boot 4 üzerinde Spring Boot Micrometer ve Prometheus entegrasyonu: Actuator erişim modeli, scrape yapılandırması, HTTP metrikleri, sağlık probe'ları ve uyarı kuralları.

Spring Boot Actuator izleme: Micrometer ve Prometheus

Spring Boot Actuator sağlık, metrik ve tanılama endpointleri sunar; Micrometer ise bunların arkasındaki metrik cephesidir. micrometer-registry-prometheus bağımlılığı eklendiğinde, Prometheus'un sabit bir aralıkla scrape ettiği /actuator/prometheus endpointi oluşur. Aşağıdaki örnekler Spring Boot 4.1 (güncel GA sürümü, 4.2 release candidate aşamasında), Micrometer 1.16 ve Prometheus Java istemcisi 1.x'i hedefler.

Kısa cevap

Spring Boot Micrometer ile Prometheus entegrasyonu üç adımdan oluşur: spring-boot-starter-actuator ve micrometer-registry-prometheus eklenir, prometheus değeri management.endpoints.web.exposure.include listesine yazılır, ardından bir Prometheus scrape_config tanımı /actuator/prometheus adresine yönlendirilir. JVM, HTTP, HikariCP ve sistem metrikleri ek kod gerekmeden yayımlanır.

Spring Boot Micrometer Kurulumu: Actuator ve Prometheus Registry

Gerekli Maven Bağımlılıkları

Actuator starter'ı endpointleri etkinleştirir ve Micrometer'ı projeye dahil eder. Prometheus registry her ölçeri Prometheus ekspozisyon formatında biçimlendirir. AspectJ starter'ı yalnızca @Timed ve @Counted anotasyonları için gereklidir.

xml
<!-- 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>

Eski tutoriallarda sürümle ilgili iki tuzak bulunur. Micrometer 1.13'ten (Spring Boot 3.3) bu yana micrometer-registry-prometheus, Prometheus Java istemcisi 1.x üzerine kuruludur; eski 0.x kodu, Micrometer Prometheus dokümantasyonunun yalnızca geçici bir çözüm olarak sunduğu, kullanımdan kaldırılmış micrometer-registry-prometheus-simpleclient modülünde yer alır. Spring Boot 4'te ise hâlâ spring-boot-starter-aop bildiren bir pom.xml artık çözümlenmez: Spring Boot 4.0 geçiş rehberi bu starter'ın adını spring-boot-starter-aspectj olarak değiştirir.

Actuator Endpointlerinin Yapılandırılması

Varsayılan olarak HTTP üzerinden yalnızca health endpointi açıktır. /actuator/prometheus endpointi registry classpath'e eklendiği anda var olur, ancak ekspozisyon listesine eklenene kadar 404 döner.

Spring Boot 3.4, açma/kapama anahtarlarını (management.endpoints.enabled-by-default, management.endpoint.<id>.enabled) bir erişim modeliyle değiştirdi ve eski özellikler kullanımdan kaldırıldı. Opt-in kalıbı varsayılan erişimi none olarak ayarlar ve her endpoint için ayrı ayrı read-only erişim verir:

yaml
# 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: unrestricted

Ekspozisyon ve erişim iki ayrı denetimdir. exposure.include, bir endpointin HTTP üzerinden eşlenip eşlenmeyeceğine karar verir; access ise eşlendikten sonra endpointin neye izin verdiğini belirler. Hâlâ enabled: true kullanan bir özellik dosyası, kullanımdan kaldırma uyarısıyla çalışmaya devam eder ve geçiş için doğru an budur.

show-details: when-authorized seçeneği sağlık ayrıntılarını yalnızca uygun role sahip kimliği doğrulanmış kullanıcılara gösterir. Endpointlerin kendisi özel bir SecurityFilterChain ile korunur:

ActuatorSecurityConfig.javajava
// 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();
    }
}

EndpointRequest, Spring Boot 4'te Actuator endpoint referansında gösterildiği gibi org.springframework.boot.security.autoconfigure.actuate.web.servlet paketine taşındı. authorizeHttpRequests içindeki Java AuthorizedUrl DSL'inde hasIpAddress metodu yoktur: IP kuralı, Spring Security 6.3'ten beri mevcut olan IpAddressAuthorizationManager.hasIpAddress üzerinden tanımlanır.

Micrometer ile Özel Metrikler

Uygulama Sayaçları, Gauge'ları ve Timer'ları

Micrometer, farklı kullanım senaryolarına uygun çeşitli metrik türleri sunar. Counter'lar kümülatif olayları, gauge'lar anlık değerleri ve timer'lar bir işlemin süresini ölçer.

OrderMetricsService.javajava
// 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);
    }
}

Prometheus registry ile iki adlandırma ayrıntısı önemlidir. Micrometer ölçer adları nokta kullanır ve snake case'e dönüştürülür (orders.processing.duration, orders_processing_duration_seconds olur). Counter'lara _total soneki otomatik eklenir; bu nedenle bir counter'ı orders.created.total olarak adlandırmak yalnızca gürültü ekler.

publishPercentiles(0.5, 0.95, 0.99) yüzdelikleri her JVM'in içinde hesaplar ve bu değerler instance'lar arasında toplanamaz. publishPercentileHistogram() ise histogram_quantile() fonksiyonunun tüm filo genelinde topladığı bucket'ları yayımlar; Prometheus kullanıldığında seçilmesi gereken seçenek budur.

@Timed ve @Counted Anotasyonları

Kalıp kodu önlemek için Micrometer, metotları AspectJ aracılığıyla araçsallaştıran anotasyonlar sunar.

PaymentService.javajava
// 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
    }
}
yaml
# application.yml
# Enables TimedAspect, CountedAspect and the other Micrometer annotation aspects
management:
  observations:
    annotations:
      enabled: true

Spring Boot 3.2'den bu yana TimedAspect ve CountedAspect bean'lerini elle tanımlamak gerekmez: classpath'te spring-boot-starter-aspectj varken management.observations.annotations.enabled=true ayarı bunları otomatik yapılandırır. Özelliğin varsayılan değeri false olduğundan "@Timed hiçbir şey yapmıyor" şikâyetlerinin çoğu buradan kaynaklanır.

AOP Sınırlaması

@Timed ve @Counted anotasyonları yalnızca proxy üzerinden çağrılan Spring bean'lerinin public metotlarında çalışır. Aynı sınıf içinde bir metottan diğerine yapılan çağrı proxy'yi atlar ve araçsallaştırılmaz; private bir metottaki anotasyon ise yok sayılır. Zaten araçsallaştırılmış bir Spring MVC controller'ına anotasyon eklemek yinelenen gözlemler üretir.

Spring Boot mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Özel Sağlık Endpointleri

İş Sağlık Göstergeleri

Health Indicator'lar dış bağımlılıkların ve kritik iş bileşenlerinin durumunu doğrular. Spring Boot, veritabanları, Redis ve diğer yaygın hizmetler için varsayılan göstergeler sunar. Spring Boot 4'te sağlık API'si kendi spring-boot-health modülünde yer alır; Health ve HealthIndicator, org.springframework.boot.actuate.health paketinden org.springframework.boot.health.contributor paketine taşındı.

PaymentGatewayHealthIndicator.javajava
// 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();
        }
    }
}

Bu gösterge /actuator/health altında paymentGateway adıyla görünür: bu ad, HealthIndicator soneki çıkarılmış bean adıdır.

Kubernetes için Sağlık Grupları

Spring Boot 4, liveness ve readiness probe'larını yalnızca Kubernetes algılandığında değil, her platformda varsayılan olarak etkinleştirir. /actuator/health/liveness ve /actuator/health/readiness grupları hiçbir yapılandırma olmadan mevcuttur; LivenessStateHealthIndicator veya ReadinessStateHealthIndicator bean'lerini elle tanımlamak gereksizdir. Yapılandırma yalnızca bir gruba bileşen eklemek veya özel gruplar tanımlamak için gerekir:

yaml
# 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-authorized

Liveness grubu livenessState ile sınırlı kalmalıdır. Liveness grubuna bir veritabanı veya dış ağ geçidi eklemek, bu bağımlılık çöktüğünde Kubernetes'in tüm pod'ları yeniden başlatmasına yol açar ve kısmi bir kesintiyi tam kesintiye dönüştürür. Dış bağımlılıklar readiness grubuna aittir; o da yalnızca uygulama bunlar olmadan hiçbir isteğe yanıt veremiyorsa.

Kubernetes probe'ları daha sonra özel endpointleri işaret eder:

yaml
# 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: 3

add-additional-paths: true ile probe'lar, Actuator ayrı bir yönetim portunda çalışsa bile uygulama portunda kalır; böylece bozulan ana sunucu probe tarafından tespit edilir.

Spring Boot Prometheus Entegrasyonu ve Grafana

Prometheus Scraping Yapılandırması

Prometheus, /actuator/prometheus endpointini periyodik olarak sorgulayarak metrikleri toplar. Prometheus varsayılan olarak /metrics yolunu kullandığından scrape yapılandırması hedefleri ve metrik yolunu tanımlar.

yaml
# 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: (.+)

Hedefler 9090 portunu kullanır, çünkü aşağıdaki üretim yapılandırması Actuator'ı ayrı bir yönetim portuna taşır. management.server.port olmadan scrape hedefi uygulama portudur.

Varsayılan Olarak Yayımlanan Spring Boot Actuator Metrikleri

Micrometer ile birlikte Actuator, JVM, süreç, HikariCP ve HTTP metriklerini hiç kod yazmadan sunar. Aşağıdaki PromQL sorguları, üretimde ilk izlenen metrikleri kapsar:

promql
# 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 * 100

jvm_memory_used_bytes{area="heap"}, bellek havuzu başına bir seri döndürür (G1 ile Eden, Survivor, Old Gen). Bunu sum by (instance) olmadan maksimuma bölmek, dashboard'da yanlış görünen havuz bazlı oranlar üretir.

Bir MeterBinder bean'i, varsayılanların kapsamadığı uygulama düzeyindeki gauge'ları ekler:

CustomJvmMetrics.javajava
// 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 ve process.uptime zaten Micrometer'ın yerleşik binder'ları tarafından yayımlanır. Örnek yalnızca kalıbı gösterir; gerçek bir binder yalnızca varsayılanlarda eksik olanı kaydetmelidir.

Kullanıma Hazır Grafana Dashboard'ları

Grafana, standart Actuator metrikleri üzerinde çalışan Spring Boot topluluk dashboard'ları sunar. Elle yazılan bir dashboard timeseries ve stat panel türlerini kullanır:

json
{
  "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"
        }
      ]
    }
  ]
}

Eski tutoriallardan kopyalanan birçok dashboard hâlâ graph ve singlestat panel türlerini kullanır. Singlestat Grafana 8'de kaldırıldı ve Grafana 12 AngularJS desteğini tamamen kaldırdı; bu nedenle bu paneller ancak timeseries ve stat türlerine geçirildikten sonra görüntülenir.

Grafana'ya Aktarım

Bir topluluk dashboard'unu içe aktarmak için: Grafana > Dashboards > New > Import, ardından ID 4701 (JVM Micrometer) veya 12900 (SpringBoot APM) girilir. İçe aktarmadan sonra panel türleri kontrol edilmelidir: topluluk dashboard'larının eski revizyonları, Grafana 12'nin dönüştürmesi gereken Angular paneller içerebilir.

Prometheus ile Uyarılar

Temel Uyarı Kuralları

Prometheus uyarı kuralları, metrikler kritik eşikleri aştığında bildirimleri tetikler.

yaml
# 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 }}"

Bu uyarılar en yaygın üretim sorunlarını kapsar: erişilebilirlik, performans ve kaynaklar. HighLatency kuralı sum by (le, ...) gerektirir: bu olmadan histogram_quantile her seri için (URI, metot ve durum başına) ayrı bir P99 hesaplar ve üç çağrılı tek bir yavaş endpoint uyarıyı tetikler.

Spring Boot HTTP Metrikleri ve Veritabanı Metrikleri

HTTP İsteklerinin Otomatik Araçsallaştırılması

Spring Boot, gelen her Spring MVC ve WebFlux isteğini Observation API üzerinden araçsallaştırır ve method, uri, status, outcome ve exception etiketleriyle http.server.requests timer'ını yayımlar.

yaml
# 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}

Birçok örnekte görülen WebMvcTagsContributor ve WebMvcTagsProvider arayüzleri Spring Boot 3.0'da kaldırıldı. Özel etiketler artık Spring Boot metrik referansında belgelendiği gibi DefaultServerRequestObservationConvention genişletilerek eklenir:

TenantObservationConvention.javajava
// 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")
        );
    }
}

Yalnızca düşük kardinaliteli anahtar-değer çiftleri Prometheus etiketine dönüşür. tenant etiketi birkaç düzine tenant ile kabul edilebilir, binlerce tenant ile ise bir kardinalite sorununa dönüşür. Yüksek kardinaliteli değerler (kullanıcı ID'si, sipariş ID'si) yalnızca trace'leri besleyen getHighCardinalityKeyValues içine aittir. Observation API'nin tracing tarafı OpenTelemetry ile Spring Boot gözlemlenebilirliği makalesinde ayrıntılı olarak ele alınır.

HikariCP ve SQL Sorgu Metrikleri

HikariCP havuz metrikleri, genel jdbc.connections gauge'larının yanında, havuz adıyla etiketlenmiş olarak hikaricp önekiyle otomatik yayımlanır. Havuz adının ayarlanması etiketi okunabilir kılar:

yaml
# 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: 1800000

Sorgu düzeyinde süre ölçümü Spring Boot tarafından sağlanmaz. datasource-micrometer projesi DataSource'u sarmalar ve her bağlantı, sorgu ve result set için bir gözlem oluşturur; bu da hem metrik hem trace span'leri üretir. 2.x serisi Spring Boot 4'ü, 1.x serisi Spring Boot 3'ü hedefler:

xml
<!-- 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>

Bu yaklaşım, DataSource'u elle yazılmış bir listener ile ProxyDataSourceBuilder içine saran manuel yöntemin yerini alır; o yöntem Spring Boot'un DataSource otomatik yapılandırmasını atlıyor ve her Boot yükseltmesinde senkronize tutulmak zorundaydı. Yavaş sorgular genellikle Spring Data JPA N+1 sorgu çözümleri makalesinde ele alınan N+1 kalıbına işaret eder.

Üretim için En İyi Uygulamalar

Metrik Kardinalitesi

Aşırı kardinalite Prometheus'un performansını düşürür. Her benzersiz etiket kombinasyonu ayrı bir zaman serisi oluşturur.

AntiPatternHighCardinality.javajava
// 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();
    }
}
GoodPracticeCardinality.javajava
// 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));
    }
}

Histogram bucket'larına sahip bir Timer, elle oluşturulmuş bir bucket etiketi taşıyan counter'a göre gecikme sorularını daha iyi yanıtlar: PromQL bucket'lardan herhangi bir yüzdeliği hesaplayabilir ve bucket sınırları servisler arasında tutarlı kalır.

Üretime Hazır Yapılandırma

yaml
# 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}

access.default: none ile env, beans, configprops ve mappings gibi endpointler tek tek listelemeye gerek kalmadan erişilemez olur. management.prometheus.metrics.export.enabled varsayılan olarak true değerindedir ve step ayarının Prometheus üzerinde hiçbir etkisi yoktur: bu pull tabanlı bir registry'dir ve scrape aralığı prometheus.yml içinde belirlenir.

Actuator ve Prometheus ile Sık Karşılaşılan Hatalar

BelirtiNedenÇözüm
/actuator/prometheus 404 dönerEndpoint ekspozisyon listesinde değil veya registry eksikprometheus değerini management.endpoints.web.exposure.include listesine eklemek ve micrometer-registry-prometheus bağımlılığının classpath'te olduğunu doğrulamak
Prometheus hedefi "server returned HTTP status 401" gösterirSpring Security, health dışındaki tüm endpointleri korurprometheus endpointini scraper'ın ağına açmak veya scrape yapılandırmasında basic auth tanımlamak
@Timed hiçbir metrik üretmezmanagement.observations.annotations.enabled varsayılan olarak false veya AspectJ eksikÖzelliği true yapmak ve spring-boot-starter-aspectj eklemek
Yükseltmeden sonra spring-boot-starter-aop bulunamazSpring Boot 4'te adı değiştispring-boot-starter-aspectj kullanmak
Boot 4'te Health veya HealthIndicator importu başarısız olurPaket org.springframework.boot.health.contributor altına taşındıImportları güncellemek
management.endpoint.*.enabled için kullanımdan kaldırma uyarılarıAçma/kapama modeli Spring Boot 3.4'te değiştirildimanagement.endpoints.access.default ve management.endpoint.<id>.access kullanmak
Dashboard'daki yüzdelikler gerçeklikten çok farklıpublishPercentiles değerleri pod'lar arasında ortalanıyorpublishPercentileHistogram() ve histogram_quantile() kullanmak

Spring Boot Micrometer Mülakat Soruları

Actuator, orta ve kıdemli seviye pozisyonlara yönelik Spring Boot mülakatlarının çoğunda gündeme gelir. Deneyimli bir profili junior bir profilden ayıran yanıtlar:

  • Liveness ile readiness arasındaki fark: liveness Kubernetes'e pod'u yeniden başlatmasını, readiness ise trafiği yönlendirmeyi durdurmasını söyler. Veritabanını liveness grubuna koyan bir aday, bunu hiç bir kesinti sırasında yaşamamıştır.
  • Neden doğrudan Prometheus istemcisi değil de Micrometer: Micrometer bir cephedir. Aynı Counter ve Timer kodu, tek bir bağımlılık değiştirilerek Prometheus, Datadog veya OTLP'ye aktarılır; Spring'in kendi araçsallaştırması (HTTP, JDBC havuzu, önbellekler) da bu cepheye göre yazılmıştır.
  • İstemci tarafı yüzdelikler ile histogramlar: istemci tarafında hesaplanan yüzdelikler instance'lar arasında toplanamaz, histogramlar toplanabilir. Bu, konuyla ilgili en yaygın tuzak sorudur.
  • Kardinalite nedir ve Prometheus'u nasıl bozar: her benzersiz etiket kombinasyonu için bir zaman serisi; kullanıcı ID'si etiketi bellek kullanımını kullanıcı sayısıyla çarpar.
  • Actuator nasıl güvenli hale getirilir: ekspozisyonu sınırlamak, erişim seviyelerini ayarlamak, yönetimi ayrı bir portta çalıştırmak ve özel bir SecurityFilterChain ile korumak.

Bu türden daha fazla soru Spring Boot Actuator mülakat soruları modülünde yer alır.

Kaynaklar

Spring Boot Micrometer ve Prometheus Hakkında Akılda Tutulması Gerekenler

  • Çalışan bir scrape için üç adım: Actuator starter'ı, micrometer-registry-prometheus, ekspozisyon listesinde prometheus
  • 3.4'ten beri erişim modeli: management.endpoints.access.default: none ile endpoint başına read-only, kullanımdan kaldırılan enabled bayraklarının yerini alır
  • Spring Boot 4 değişiklikleri: spring-boot-starter-aspectj, org.springframework.boot.health.contributor içinde sağlık API'si, org.springframework.boot.security.autoconfigure.actuate.web.servlet içinde EndpointRequest, varsayılan olarak etkin probe'lar
  • Anotasyonlar bir anahtar gerektirir: management.observations.annotations.enabled=true, elle tanımlanan TimedAspect bean'i yok
  • Observation API üzerinden HTTP etiketleri: DefaultServerRequestObservationConvention genişletilir, etiketler düşük kardinaliteli tutulur
  • İstemci yüzdelikleri yerine histogramlar: publishPercentileHistogram() ve her histogram_quantile içinde sum by (le)
  • Liveness yerel kalır: dış bağımlılıklar readiness grubuna veya özel bir gruba girer, asla liveness grubuna girmez

Bu varsayılanlar bir Spring Boot 4 servisine pod'lar arasında doğru toplanan metrikler, genel ağa hiçbir şey sızdırmayan endpointler ve tek bir yavaş URI yerine gerçek olaylarda tetiklenen uyarılar sağlar.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Günün meydan okuması

Spring Boot kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

26 Ekim 2026 tarihinde güncellendi

Etiketler

#spring boot actuator
#micrometer
#prometheus
#monitoring
#observability

Paylaş

İlgili makaleler