Spring Boot Observability: OpenTelemetry ve Grafana Kurulumu
Log dosyası olması gözlemlenebilirlik değildir; üretimde 'bu istek neden yavaş' sorusuna dakikalar içinde cevap verebilmektir. Spring Boot 3'te Micrometer metriklerini, OpenTelemetry ile dağıtık izlemeyi, traceId ile log korelasyonunu, Grafana panolarını ve SLO tabanlı alarmları kurulum adımları ve kod örnekleriyle anlatıyoruz.
"Loglarımız var" cümlesi, üretimde bir sorun çıktığında şu anlama gelir: birisi sunucuya bağlanacak, dosyada saat aralığına göre arayacak, üç servisin logunu elle eşleştirmeye çalışacak. Gözlemlenebilirlik (observability) bunun tersidir: "bu istek neden 4 saniye sürdü" sorusuna, hangi servisin hangi sorgusunda ne kadar beklendiğini gösteren tek bir ekranla, dakikalar içinde cevap verebilmek.
Spring Boot 3 bunu neredeyse sıfır kodla mümkün kılar: Micrometer metrikleri, Micrometer Tracing üzerinden OpenTelemetry izleri ve traceId taşıyan yapılandırılmış loglar. Bu yazıda üç sinyalin ne işe yaradığını, kurulum adımlarını, kendi iş metriklerinizi nasıl ekleyeceğinizi, Grafana panolarını ve SLO tabanlı alarmları ve sahada en sık gördüğümüz maliyet ve kardinalite tuzaklarını anlatıyoruz.
Üç sinyal, üç soru
Sinyal | Cevapladığı soru | Spring Boot tarafı | Depo / görselleştirme |
|---|---|---|---|
Metrik | Sistem genel olarak nasıl? (istek hızı, hata oranı, gecikme dağılımı, havuz doluluğu) | Micrometer → Prometheus uç noktası | Prometheus → Grafana |
İz (trace) | Bu istek nereden geçti, nerede bekledi? | Micrometer Tracing → OpenTelemetry (OTLP) | Tempo / Jaeger → Grafana |
Log | O anda tam olarak ne oldu? | Yapılandırılmış JSON + traceId/spanId | Loki / Elasticsearch → Grafana |
Üçünü birbirine bağlayan şey traceId'dir: Grafana'da yavaş bir istek grafiğinden ize, izden o isteğin loglarına tek tıkla geçilir. Korelasyon yoksa üç ayrı araçta üç ayrı arama vardır.
Kurulum: bağımlılıklar ve yapılandırma
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
<dependency><groupId>io.micrometer</groupId><artifactId>micrometer-registry-prometheus</artifactId></dependency>
<dependency><groupId>io.micrometer</groupId><artifactId>micrometer-tracing-bridge-otel</artifactId></dependency>
<dependency><groupId>io.opentelemetry</groupId><artifactId>opentelemetry-exporter-otlp</artifactId></dependency>
<dependency><groupId>net.ttddyy.observation</groupId><artifactId>datasource-micrometer-spring-boot</artifactId></dependency> <!-- JDBC sorguları izde görünsün -->management:
endpoints:
web:
exposure:
include: health, prometheus, info # actuator uçları; dışarıya açık olmamalı
tracing:
sampling:
probability: 1.0 # üretimde 0.1–0.2; hata izleri için tail sampling
otlp:
tracing:
endpoint: http://otel-collector:4318/v1/traces
metrics:
tags:
application: siparis-servisi # her metriğe uygulama etiketi
spring:
application:
name: siparis-servisi
kafka:
template:
observation-enabled: true # Kafka üretici/tüketici izleri
listener:
observation-enabled: true
logging:
structured:
format:
console: ecs # Spring Boot 3.4+: JSON log, traceId/spanId dahilBu kadarıyla, kod yazmadan şunlar gelir: her HTTP uç noktası için istek sayısı, hata oranı ve gecikme histogramı; RestClient/WebClient çağrıları, JDBC sorguları ve Kafka mesajları için iz parçaları (span); JVM bellek, GC, thread ve HikariCP havuz metrikleri; traceId ve spanId içeren JSON loglar.
Kendi gözlemlerinizi eklemek
Altyapı metrikleri "sunucu sağlıklı mı" sorusunu cevaplar; "iş sağlıklı mı" sorusu için iş metrikleri gerekir. İki araç yeterlidir:
@Service
public class SiparisServisi {
private final Counter olusturulan;
private final Timer onaySuresi;
public SiparisServisi(MeterRegistry registry) {
this.olusturulan = Counter.builder("siparis.olusturuldu")
.description("Oluşturulan sipariş sayısı").register(registry);
this.onaySuresi = Timer.builder("siparis.onay.suresi")
.publishPercentileHistogram().register(registry);
}
@Observed(name = "siparis.olustur", contextualName = "sipariş-oluştur") // metrik + iz parçası birlikte
public SiparisNo olustur(SiparisTalebi talep, Kanal kanal) {
SiparisNo no = ...;
olusturulan.increment();
registry.counter("siparis.olusturuldu.kanal", "kanal", kanal.name()).increment(); // düşük kardinaliteli etiket
return no;
}
}@Observed için bir ObservedAspect bean'i tanımlanır; anotasyon aynı anda bir Timer ve bir span üretir. Etiket kuralı kritiktir: etiket değerleri sınırlı bir küme olmalıdır (kanal, durum, bölge). Kullanıcı kimliği, sipariş numarası ya da e-posta etiket olamaz; her farklı değer yeni bir zaman serisi açar ve Prometheus'u çökertir. Bu tür bilgiler ize (span attribute) ya da loga yazılır, metriğe değil.
Panolar: RED ve USE
İyi bir pano soru cevaplar, grafik sergilemez. İki standart yöntem yeterlidir:
RED (her servis ve uç nokta için): Rate — saniyede istek; Errors — 5xx oranı; Duration — p50/p95/p99 gecikme.
USE (her kaynak için): Utilization — CPU, bellek, bağlantı havuzu doluluğu; Saturation — kuyruk ve bekleme; Errors — havuz zaman aşımları, GC baskısı.
# p99 gecikme, uç nokta bazında (Prometheus, histogram üzerinden)
histogram_quantile(0.99,
sum by (le, uri) (rate(http_server_requests_seconds_bucket{application="siparis-servisi"}[5m])))
# Hata oranı
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
/ sum(rate(http_server_requests_seconds_count[5m]))
# HikariCP: bağlantı bekleyen istek sayısı
hikaricp_connections_pending{application="siparis-servisi"}Alarmlar: CPU'ya değil, kullanıcıya bakın
"CPU %80" alarmı gece üçte uyandırır ama kullanıcıya bir şey olup olmadığını söylemez. Alarmlar SLO (hizmet seviyesi hedefi) üzerine kurulur: "isteklerin %99,5'i 500 ms altında ve başarılı" gibi bir hedef, hata bütçesi (ayda kabul edilebilir başarısız istek payı) ve bütçenin tükenme hızına bağlı alarm. Bütçe iki saatte %5 harcanıyorsa sayfa, iki günde %5 harcanıyorsa bilet. Kaynak metrikleri (CPU, havuz) alarm değil, teşhis panosudur.
Log korelasyonu ve disiplin
Her log satırında traceId: Yapılandırılmış log açıkken otomatiktir; kendi thread'lerinizde MDC'nin taşınması için
micrometer-context-propagationgerekir.Olay logu, anlatı logu değil: "Sipariş oluşturuluyor…", "Sipariş oluşturuldu" yerine tek satır: olay, sipariş no, süre, sonuç.
Kişisel veri yok: Log filtresi telefon, e-posta ve kimlik numarasını maskeler.
Seviye disiplini: ERROR yalnızca birinin bakması gereken durumlar; iş kuralı reddi (stok yetersiz) WARN bile değil, INFO.
Maliyet: kardinalite, örnekleme, hacim
Gözlemlenebilirlik altyapısının maliyeti üç şeyle büyür. Kardinalite: yüksek çeşitlilikli etiket metrik depolamasını patlatır; etiket değer sayısını yüzlerle sınırlı tutun. İz örnekleme: her isteği izlemek pahalıdır; head sampling (%10–20) ucuz ama hatalı istekleri kaçırabilir, OpenTelemetry Collector'da tail sampling ("hatalı ve yavaş izleri hep tut, geri kalanın %5'i") en iyi dengeyi verir. Log hacmi: DEBUG üretimde kapalı, sağlık kontrolü istekleri loglanmaz, saklama süresi sinyale göre (metrik 13 ay, iz 7–14 gün, log 30 gün gibi).
Tuzaklar
Actuator'ı internete açmak:
/actuator/prometheusve/actuator/envyalnızca iç ağdan ya da kimlik doğrulamayla; Spring Security'de ayrı bir kural.Asenkron sınırlarda bağlamı kaybetmek:
@Async,CompletableFutureve sanal thread'lerde traceId kopmaz — bağlam yayılımı kütüphanesi veContextExecutorServiceile taşınır.Kafka'da başlıkları düşürmek: İz bağlamı mesaj başlıklarında taşınır; elle üretilen mesajlarda başlık kopyalanmazsa iz tüketici tarafında kopar.
Zaman aşımını ölçmemek: Dış çağrılarda süre metriği zaman aşımı hatalarını içermeli; aksi hâlde en yavaş istekler grafikte görünmez.
Pano var, bakan yok: Panolar sürüm sonrası kontrol listesinin ve haftalık operasyon toplantısının parçası olmalıdır.
Sık sorulan sorular
OpenTelemetry Java agent mı, Micrometer Tracing mi?
Spring Boot 3 projelerinde Micrometer Tracing + OTel köprüsü, Spring'in kendi gözlem noktalarıyla (Observation API) bütünleşik olduğu için önerimizdir. Kod değiştirmenin mümkün olmadığı eski uygulamalarda OTel Java agent (bytecode enstrümantasyonu) tek seçenektir; ikisi aynı anda kullanılmaz.
Küçük bir ekip için bu altyapı fazla mı?
Grafana, Prometheus, Tempo ve Loki tek bir Docker Compose ile ayağa kalkar ya da yönetilen Grafana Cloud kullanılır. Kurulum bir günlük iştir; ilk üretim sorununda kendini öder.
Modüler monolitte iz neden gerekli?
Tek dağıtım biriminde bile istek beş modül ve on sorgu geçer; iz, hangi modülün zaman harcadığını gösterir. Spring Modulith'in observability modülü modül girişlerini otomatik span'a çevirir.
Sonuç
Gözlemlenebilirlik, Spring Boot 3'te birkaç bağımlılık ve bir yapılandırma dosyasıyla başlar; değeri ise disiplinde ortaya çıkar: düşük kardinaliteli iş metrikleri, traceId ile bağlanmış loglar, RED/USE panoları ve SLO tabanlı alarmlar. Bu dört alışkanlık, üretim sorunlarını saatler yerine dakikalar içinde çözülür kılar.
Üretim uygulamanız için gözlemlenebilirlik altyapısını kurmak ve SLO tabanlı izlemeye geçmek için bakım ve destek hizmetimize göz atabilirsiniz.