Observability: Logging, Monitoring & Tracing
Ketika aplikasi Anda berjalan di produksi, tidak ada yang bisa Anda "lihat" secara langsung: yang ada hanyalah output yang dikeluarkan sistem — log, angka metrik, dan data tracing. Observability adalah kemampuan untuk memahami kondisi internal sebuah sistem hanya dari output luarnya, tanpa harus membaca kode atau menebak-nebak. Artikel ini membahas tiga pilar observability — logs, metrics, dan traces — lalu menurunkan praktik nyatanya di backend Java/Spring Boot: structured logging, Actuator, Prometheus + Grafana, dan distributed tracing dengan OpenTelemetry.
Artikel ini ditujukan untuk backend engineer yang sudah membangun service (satu atau lebih) dan mulai menyadari bahwa "tampaknya error di produksi" bukanlah strategi debugging yang andal.
Apa Itu Observability
Observability dan monitoring sering dipakai bergantian, padahal keduanya berbeda. Monitoring menjawab pertanyaan "apakah sistem sedang bermasalah?" — biasanya lewat grafik dan alarm yang sudah Anda pasang sebelumnya. Observability menjawab pertanyaan "kenapa sistem bermasalah?" — lewat data yang cukup untuk menelusuri akar masalah, bahkan untuk masalah yang belum pernah Anda antisipasi.
| Aspek | Monitoring | Observability |
|---|---|---|
| Fokus | Mengukur hal yang sudah diketahui (known unknowns) | Memungkinkan penyelidikan hal yang belum diketahui (unknown unknowns) |
| Pertanyaan | Apakah sistem sehat? Apakah ada yang error? | Kenapa lambat? Kenapa gagal? Di mana tepatnya? |
| Pendekatan | Pasang alert + dashboard untuk metrik tertentu | Ekspor data kaya (log, metrik, trace) yang bisa dieksplorasi bebas |
| Hasil | Tahu "ada masalah" | Tahu "akar masalah" |
| Prinsip dasar | Tidak mungkin memonitor sesuatu yang tidak Anda ketahui keberadaannya | Jangan berasumsi — sediakan bukti |
"Sistem tak terlihat = sistem tak terpelihara." Jika Anda tidak bisa menjawab kenapa sebuah request lambat atau ke mana data hilang, maka sistem itu berjalan tanpa pengawas — dan pada akhirnya akan mengejutkan Anda saat insiden terjadi.
Tujuan akhirnya sederhana: setiap perilaku aneh di produksi harus bisa ditelusuri dari output sistem, bukan dari ingatan seseorang atau dugaan.
Tiga Pilar Observability
Semua data yang keluar dari sistem bisa dikelompokkan ke tiga kategori besar — sering disebut the three pillars of observability:
| Pilar | Apa itu | Menjawab pertanyaan | Bentuk data |
|---|---|---|---|
Logs | Catatan peristiwa diskret | Kapan ini terjadi? Apa yang terjadi? | Teks/JSON, satu entri per peristiwa |
Metrics | Angka terukur yang diagregasi | Seberapa banyak? Seberapa cepat? | Time series angka (counter, gauge, histogram) |
Traces | Perjalanan satu request lintas komponen | Request ini lewat mana saja? Di langkah mana lambat? | Graf terhubung dari span |
Logs: Catatan Peristiwa
Logs adalah catatan peristiwa yang terjadi pada suatu waktu tertentu — sebuah entri yang menyatakan kapan dan apa yang terjadi, misalnya "request dimulai", "koneksi database gagal", atau "user login". Log adalah pilar yang paling mudah dimengerti karena menyerupai catatan harian sistem:
2026-08-05 10:23:41.558 ERROR 1842 --- [http-nio-8080-exec-3] c.example.OrderService
: Koneksi ke database gagal: Connection refused (10.0.0.15:5432)
Log sangat berguna untuk detail: traceback error, nilai parameter, status proses panjang. Kelemahannya: jumlahnya bisa luar biasa banyak, dan tanpa struktur ia sulit dicari dan diagregasi.
Metrics: Angka Terukur
Metrics adalah pengukuran numerik yang diagregasi selama interval waktu: berapa request per detik, berapa persen error, berapa lama waktu respons, berapa besar penggunaan CPU dan memori. Karena berupa angka, metrik bisa disimpan efisien, diagregasi, dan di-alert (misal "error rate di atas 5% selama 5 menit"). Kelemahannya: metrik memberi tahu ada masalah, tapi tidak memberi detail kenapa.
http_server_requests_seconds_count{method="GET",status="500",uri="/api/orders"} 42
http_server_requests_seconds_sum{method="GET",status="200",uri="/api/orders"} 18.4
Traces: Perjalanan Satu Request
Traces menggambarkan perjalanan sebuah request saat melewati berbagai komponen — dari API gateway, masuk ke service A, memanggil service B, lalu query database. Satu trace terdiri dari banyak span: setiap span adalah satuan kerja dengan nama, waktu mulai, durasi, dan status. Dengan trace, Anda bisa melihat langkah mana yang menghabiskan waktu — inilah pilar yang paling berguna di arsitektur microservices.
trace ─────────────────────────────────────────────
├── span: "GET /api/orders" (gateway) 45 ms
├── span: "GET /api/orders" (order-svc) 38 ms
│ ├── span: "GET /api/customers/7" (customer-svc) 22 ms
│ │ └── span: "SELECT customer" (postgres) 18 ms
│ └── span: "SELECT orders" (postgres) 12 ms
└── span: "HTTP 200 to client" (gateway) 0 ms
Bagaimana Ketiganya Saling Melengkapi
Ketiga pilar ini bukan pengganti satu sama lain, melainkan pelengkap. Metrik bilang "error rate naik", log bilang "koneksi database gagal di service X", dan trace bilang "penundaan berasal dari query ke database". Di sistem yang matang, ketiganya disatukan oleh satu pengenal yang sama — traceId:
Request masuk
│ traceId = a1b2c3d4 di-set di entry point
▼
┌───────────────────────────────┐
│ TRACE: span "GET /orders" │ a1b2c3d4
└───────────────────────────────┘
│
├──── LOG (berlabel a1b2c3d4): "query orders: 12 ms"
│
└──── METRIC (berlabel traceId=a1b2c3d4): latency bucket
Karena traceId yang sama menempel di log dan metrik, Anda bisa "menghubungkan" sebuah titik di grafik metrik ke log yang relevan, lalu melacak sampai ke trace lengkap. Inilah inti dari correlation yang akan dibahas nanti.
Structured Logging
Log teks bebas seperti "Order gagal untuk user 42" terlihat ramah, tetapi menyedihkan saat dicari dalam skala besar. Bagaimana Anda mengekstrak user dari teks itu? Bagaimana log system memfilter "semua request dengan status=500"? Anda akan berakhir menulis grep dengan regex yang rapuh.
Jawabannya adalah structured logging: menulis log sebagai data terstruktur (biasanya JSON) dengan field yang tetap, sehingga bisa difilter, diagregasi, dan di-query oleh tool seperti Loki, Elasticsearch, atau Grafana Cloud:
{
"timestamp": "2026-08-05T10:23:41.558Z",
"level": "ERROR",
"logger": "com.example.OrderService",
"message": "Koneksi ke database gagal",
"traceId": "a1b2c3d4e5f60718",
"userId": "u_9f2e",
"dbHost": "10.0.0.15:5432"
}
Perhatikan bahwa setiap field punya nama yang jelas dan bisa dicari: traceId, userId, dbHost. Inilah pembeda utama structured logging: mesin bisa mengerti struktur log tanpa perlu parsing teks.
Log Level: Kapan Menggunakan Level yang Mana
Level log menunjukkan kepentingan sebuah peristiwa. Salah memilih level membuat log bising (semua INFO) atau buta (semua ERROR).
| Level | Kapan dipakai | Contoh |
|---|---|---|
TRACE | Detail internal yang sangat verbose (hampir selalu untuk debugging) | Nilai variabel dalam loop |
DEBUG | Detail untuk memahami alur saat investigasi | "masuk ke method applyDiscount dengan nilai X" |
INFO | Peristiwa penting yang "normal" dan perlu dilacak | Service start, order berhasil dibuat, user login |
WARN | Kondisi tidak ideal tapi masih berjalan | Retry pertama gagal, cache miss, config kedaluwarsa |
ERROR | Kegagalan nyata yang membutuhkan perhatian | Exception tidak tertangani, query database gagal |
Jangan pernah log rahasia. Password,
token,API key, dan data kartu pembayaran tidak boleh masuk ke log apa pun — baik diDEBUGmaupunTRACE. Log sering lebih lama hidup daripada sistem itu sendiri; sebuahtokenyang tertulis di log adalah kebocoran yang menunggu ditemukan. Tunjukkan hanyauserId(bukan password), hanyaorderId(bukan nomor kartu).
Aturan praktis: log di level yang berguna di produksi dengan biaya rendah. INFO untuk jejak jalan penting, DEBUG untuk detail — dan aktifkan DEBUG hanya saat investigasi.
Konteks dengan MDC: Menempelkan traceId ke Semua Log
Di dunia nyata Anda ingin setiap baris log membawa konteks yang sama — traceId, userId, requestId — tanpa harus menuliskannya manual di setiap panggilan log. Di Java, alatnya adalah MDC (Mapped Diagnostic Context) dari org.slf4j, yang menempelkan key-value ke thread saat ini dan otomatis disisipkan ke setiap log yang dihasilkan thread itu:
import org.slf4j.MDC;
public class OrderService {
public Order createOrder(OrderRequest req) {
MDC.put("userId", req.userId());
MDC.put("traceId", TraceContext.currentTraceId());
try {
return doCreateOrder(req);
} finally {
MDC.remove("userId");
MDC.remove("traceId");
}
}
}
MDC bekerja per-thread, jadi pastikan Anda membersihkannya di finally — jika tidak, nilai lama bisa bocor ke request berikutnya (atau memakai MDC.clear() di filter terpusat). Dalam ekosistem tracing, traceId biasanya sudah diisi otomatis oleh instrumentation (baca bagian OpenTelemetry).
SLF4J + Logback: Default di Spring Boot
Spring Boot menggunakan SLF4J sebagai facade (antarmuka) dan Logback sebagai implementasinya. Anda cukup memakai org.slf4j.Logger di kode, dan logback-spring.xml di src/main/resources mengatur format dan tujuan log:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public Order createOrder(OrderRequest req) {
log.info("Membuat order untuk userId={}", req.userId());
// ...
}
}
Contoh pola logback-spring.xml untuk structured logging (format JSON) dengan pattern:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.JsonEncoder"/>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
Versi pola teks biasa jika Anda belum siap pindah ke JSON:
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
Untuk volume log yang besar, log juga bisa ditulis secara asinkron supaya I/O tidak memperlambat aplikasi. Dengan AsyncAppender, log dikirim lewat antrean buffer di memori dan ditulis oleh thread terpisah:
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
</appender>
Perlu diingat: log asinkron memakai
queuedi memori; jika ukurannya kecil dan aplikasi dibanjiri log, ada risiko log dibuang saat antrean penuh. AturqueueSizesesuai kebutuhan dan jangan menaruh logERRORdi jalur yang sepenuhnya best-effort.
Actuator: Health Check & Metrics Siap Pakai
Spring Boot menyediakan Actuator — sekumpulan endpoint siap pakai untuk memeriksa dan memonitor aplikasi. Dengan menambahkan satu dependency, aplikasi Anda langsung punya health, metrics, info, dan banyak endpoint lain tanpa menulis kode.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Sebagian besar endpoint Actuator berstatus sensitive dan sengaja tidak diekspos oleh default. Anda perlu mengeksposnya lewat konfigurasi di application.yml:
management:
endpoints:
web:
exposure:
include: health,metrics,info
endpoint:
health:
show-details: always
Endpoint yang paling sering dipakai:
| Endpoint | Fungsi | Contoh respons |
|---|---|---|
/actuator/health | Status kesehatan aplikasi (untuk liveness/readiness probe) | {"status":"UP"} |
/actuator/metrics | Daftar metrik yang tersedia | http.server.requests, jvm.memory.used |
/actuator/metrics/{name} | Detail satu metrik | kuantil, total, count |
/actuator/info | Informasi bebas (versi, commit, git) | {"app":{"version":"1.2.0"}} |
/actuator/prometheus | Metrik dalam format Prometheus | baris time series |
Custom Health Indicator
Health bawaan sudah mengecek database (koneksi DataSource), tetapi sering Anda butuh indikator khusus — misalnya "apakah saya bisa menjangkau Redis" atau "apakah antrean pesan sehat?". Implementasikan HealthIndicator:
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Component;
@Component
public class RedisHealthIndicator implements HealthIndicator {
private final RedisConnectionFactory connectionFactory;
public RedisHealthIndicator(RedisConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
@Override
public Health health() {
try (var conn = connectionFactory.getConnection()) {
conn.ping();
return Health.up().withDetail("redis", "PONG").build();
} catch (Exception ex) {
return Health.down().withDetail("error", ex.getMessage()).build();
}
}
}
Dengan begitu /actuator/health menampilkan agregasi status semua indikator — jika Redis mati, status keseluruhan berubah menjadi DOWN dan orkestrator (misalnya Kubernetes) tahu untuk memutus trafik.
Monitoring: Prometheus + Grafana
Memiliki endpoint metrik saja belum cukup — Anda perlu sistem yang mengumpulkan, menyimpan, dan memvisualisasikan angka-angka itu. Pasangan yang paling umum di ekosistem backend adalah Prometheus dan Grafana.
Prometheus adalah sistem monitoring yang bekerja dengan model scrape: secara berkala (misal tiap 15 detik) ia menarik (scrape) metrik dari endpoint HTTP, lalu menyimpannya sebagai time series. Grafana adalah platform visualisasi yang membaca data dari berbagai sumber (termasuk Prometheus) dan menampilkannya sebagai dashboard + alert.
┌─────────────┐ scrape /actuator/prometheus ┌─────────────┐
│ Spring Boot │ ───────────────────────────────▶ │ Prometheus │
│ (service) │ │ (penyimpanan │
│ :8080 │ ◀─────────────────────────────── │ & query) │
└─────────────┘ HTTP 200: baris metrik └──────┬──────┘
│ PromQL query
▼
┌─────────────┐
│ Grafana │ dashboard + alert
└─────────────┘
Untuk mengekspos metrik ke format Prometheus, tambahkan dependency micrometer-registry-prometheus (Micrometer adalah pustaka metrik bawaan Spring Boot):
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
Lalu arahkan Prometheus ke endpoint tersebut lewat konfigurasi scrape:
scrape_configs:
- job_name: "order-service"
scrape_interval: 15s
metrics_path: "/actuator/prometheus"
static_configs:
- targets: ["localhost:8080"]
Query di Prometheus ditulis dengan bahasa PromQL. Contoh: laju request per detik selama 5 menit terakhir:
rate(http_server_requests_seconds_count[5m])
Metrik Penting yang Dipantau
Sebaiknya Anda memulai dengan metrik golden signals — empat sinyal yang biasanya menunjukkan masalah lebih dulu:
| Metrik | Pertanyaan yang dijawab | Contoh PromQL |
|---|---|---|
| Request rate (traffic) | Berapa banyak permintaan per detik? | rate(http_server_requests_seconds_count[5m]) |
| Latency (p50/p95/p99) | Seberapa cepat respons untuk mayoritas pengguna? | histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) |
| Error rate | Berapa persen request gagal? | rate(http_server_requests_seconds_count{status=~"5.."}[5m]) / rate(http_server_requests_seconds_count[5m]) |
| Saturation | Seberapa penuh sistem? (antrean, koneksi DB, heap) | jvm_memory_used_bytes, hikaricp_connections_active |
Tambahan penting di backend Java:
- JVM heap (
jvm.memory.used) — tanda kebocoran memori atau ukuran heap yang kurang. - Connection pool database (
hikaricp.connections.active,hikaricp.connections.pending) — antrean request menunggu koneksi. - Queue depth — jumlah pesan menunggu diproses (contohnya antrean Kafka/RabbitMQ).
- GC pause time — lamanya
garbage collectionyang menghentikan thread.
Alerting: Tahu Lebih Cepat dari Pengguna
Dashboard berguna untuk penyelidikan, tetapi tidak ada yang mengawasi dashboard 24 jam. Karena itu Prometheus mendukung alerting rules: jika suatu query bernilai benar selama for waktu tertentu, Alertmanager akan mengirim notifikasi (email, Slack, PagerDuty).
groups:
- name: order-service
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
/ sum(rate(http_server_requests_seconds_count[5m]))
> 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "Error rate di atas 5% selama 5 menit"
Hati-hati dengan alert yang terlalu sensitif. Alert harus menandakan masalah yang bisa ditindaklanjuti dan berdurasi cukup (
for: 5m) agar tidak membuat tim mati rasa karena alarm palsu. Aturan main yang sehat: lebih baik selektif pada metrik yang benar-benar kritis daripada bising pada semua metrik.
Distributed Tracing
Logs dan metrics sudah cukup untuk sistem monolit. Namun saat request melintasi banyak service — gateway → order-service → payment-service → database — muncul pertanyaan yang sulit dijawab dengan dua pilar tadi: di service mana request ini kehilangan waktu? Jawabannya ada di distributed tracing.
Tanpa tracing, Anda hanya bisa berspekulasi: "mungkin lambat di payment-service?" Dengan tracing, Anda melihat perjalanan lengkap satu request:
Client ──▶ API Gateway ──▶ Order Service ──▶ Payment Service
│ │ │
span: 120ms span: 85ms span: 250ms
└──────────────┴────────────────────┘
satu trace, root span di gateway, child span di tiap service
Setiap span membawa traceId (pengenal seluruh perjalanan) dan parentSpanId (span mana yang memanggilnya), sehingga tersusun hierarki seperti pohon keluarga: root span → child span → grandchild span. Di titik mana pun Anda melihat traceId: a1b2c3d4, Anda tahu itu bagian dari perjalanan request yang sama.
OpenTelemetry
OpenTelemetry (OTel) adalah standar open source untuk menghasilkan dan mengumpulkan logs, metrics, dan traces secara terpadu. Di OTel, aplikasi memakai SDK yang menginstrumentasi kode (misal lewat filter di Spring Boot), lalu mengekspor data ke backend pilihan Anda.
Konsep utamanya:
| Konsep | Penjelasan |
|---|---|
span | Satu unit kerja bernama, punya durasi dan status |
trace | Kumpulan span yang saling terhubung untuk satu request |
context propagation | Mekanisme meneruskan traceId + spanId dari satu service ke service berikutnya |
traceparent | Header HTTP standar yang membawa konteks trace antar service |
exporter | Pengirim data ke backend (Jaeger, Tempo, Prometheus, dll.) |
Context propagation adalah bagian yang paling penting: tanpa itu, service kedua tidak tahu bahwa request-nya adalah kelanjutan dari service pertama. Konteks dilewatkan lewat header traceparent:
GET /api/payments HTTP/1.1
Host: payment-service:8081
traceparent: 00-a1b2c3d4e5f60718192a3b4c5d6e7f8-9a8b7c6d5e4f3a2b-01
traceparent berisi versi, trace-id, parent-id, dan flag sample. Otomatisasi (SDK) yang membaca dan menulis header ini yang membuat tracing bekerja tanpa campur tangan manual di tiap service.
Instrumentasi Otomatis vs Manual
Ada dua cara menginstrumentasi kode:
Instrumentasi otomatis — memakai agent atau library yang menempel otomatis ke framework (Spring MVC, JDBC, HTTP client). Ini cara termudah: hampir tanpa ubah kode, dan sudah mencakup traceId yang disisipkan ke log via MDC.
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=order-service \
-Dotel.exporter.otlp.endpoint=http://collector:4317 \
-jar order-service.jar
Instrumentasi manual — menulis span sendiri di kode untuk bagian yang otomatisasi tidak jangkau, misalnya langkah bisnis yang panjang atau batas proses yang tidak masuk akal:
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.SpanKind;
public class PaymentProcessor {
private final Tracer tracer;
public void process(String orderId) {
Span span = tracer.spanBuilder("process.payment")
.setSpanKind(SpanKind.INTERNAL)
.setAttribute("order.id", orderId)
.startSpan();
try (var ignored = span.makeCurrent()) {
// logika pembayaran
} finally {
span.end();
}
}
}
Praktik terbaik: mulai dari instrumentasi otomatis, lalu tambahkan manual hanya untuk titik yang benar-benar perlu detail.
Backend Visualisasi Trace
Data trace perlu disimpan dan divisualisasikan di tool khusus. Dua yang paling populer:
- Jaeger — backend tracing dari CNCF, dengan UI untuk menjelajahi trace, mencari per
traceId, dan melihat waterfall view per-span. - Grafana Tempo — backend tracing yang terintegrasi erat dengan ekosistem Grafana, sehingga Anda bisa berpindah dari dashboard metrik ke trace dengan sekali klik.
Dengan backend ini, penelusuran insiden menjadi: lihat traceId di log → buka trace di Jaeger/Tempo → lihat span mana yang menghabiskan 200ms → temukan query yang lambat.
Correlation: Menghubungkan Log, Metrik, dan Trace
Kekuatan observability muncul saat ketiga pilar saling terhubung. Satu traceId yang sama hadir di log (lewat MDC), di metrik (sebagai label), dan di trace (sebagai trace-id). Workflow penyelidikan insiden yang ideal:
- Dashboard Grafana menunjukkan lonjakan latensi p99 pada
order-service. - Anda klik titik waktu tersebut → buka log
order-servicepada jam itu, difilterlevel=ERROR. - Log error membawa
traceId: a1b2c3d4. - Anda buka trace dengan
traceIditu di Tempo/Jaeger → terlihat bahwa lambatnya berasal darispan "SELECT orders"ke database. - Anda periksa metrik database pada waktu yang sama (misal
connection poolpenuh).
Kunci correlation:
traceIdadalah "benang merah" yang menjahit log, metrik, dan trace menjadi satu cerita. Pastikan tooling Anda — log aggregator, Prometheus, dan tracing backend — semua mengertitraceIdsehingga perpindahan dari satu pilar ke pilar lain terjadi tanpa gesekan.
Best Practice dan Jebakan
Pemasangan telemetry juga bisa salah arah. Berikut beberapa jebakan yang paling sering dialami tim:
| Jebakan | Dampak | Solusi |
|---|---|---|
Log semuanya (DEBUG di produksi) | noise membanjiri sinyal, storage boros | Log ringkas + level yang tepat; perbanyak di investigasi |
| Log berisi PII | Melanggar privasi/regulasi (GDPR, UU PDP) | Jangan log data pribadi; gunakan userId pseudonim |
| Log berisi secret | Bocornya password/token lewat log | Filterisasi otomatis + review, jangan pernah log credential |
| Label metrik tak terkendali | Cardinality meledak (misal userId jadi label) | Batasi label; jangan masukkan nilai unik ke label |
| Hanya lihat p50 | 50% pengguna bisa merasakan latensi jauh lebih buruk | Pantau p95/p99 dan max |
| Alert terlalu banyak | Alert fatigue — insiden nyata terabaikan | Sedikit alert berkualitas, for yang cukup |
| Pasang telemetry setelah insiden | Tidak ada data saat dibutuhkan | Pasang sejak awal — "sejak hari pertama, bukan hari pertama insiden" |
Satu prinsip terakhir yang menyatukan semuanya: observability bukan fitur yang dipasang saat ada masalah — ia infrastruktur yang dibangun bersama aplikasi. Dimulai dari structured logging dan Actuator di service pertama, lalu menambahkan Prometheus + Grafana, kemudian tracing saat jumlah service bertambah. Jangan menunggu insiden untuk mulai; pada saat itu Anda sudah buta.
Ringkasan
Observability adalah kemampuan memahami kondisi sistem dari output-nya — dan bukan sekadar monitoring. Tiga pilar yang melengkapinya: logs (apa yang terjadi), metrics (seberapa banyak/seberapa cepat), dan traces (request pergi lewat mana, di mana lambat). Structured logging mengubah log teks menjadi data JSON yang bisa dicari, dengan level log yang dipilih hati-hati dan konteks MDC untuk menempelkan traceId ke setiap baris. Actuator memberi Spring Boot health check dan metrik siap pakai tanpa menulis kode, dan bisa diperluas dengan HealthIndicator custom. Prometheus mengumpulkan metrik (scrape + PromQL) dan Grafana menampilkan dashboard serta alert. Distributed tracing dengan OpenTelemetry melacak perjalanan satu request lintas service lewat span dan header traceparent, divisualisasikan di Jaeger atau Tempo. Dan yang membuat semuanya bermakna: correlation — satu traceId yang menghubungkan log, metrik, dan trace. Pasang semuanya sejak awal, jauhi jebakan (noise, PII, secret, cardinality), dan Anda akan menemukan bahwa "sistem yang terlihat" adalah sistem yang bisa dirawat.
Lanjut membaca
- Observability Primer — OpenTelemetry — pengantar tiga pilar observability
- Spring Boot Actuator — dokumentasi resmi endpoint Actuator
- Spring Boot Actuator Metrics — metrik Micrometer dan cara mengeksposnya
- Prometheus Overview — cara kerja scraping dan model data time series
- Grafana Docs — dashboard, visualisasi, dan alerting
- Grafana Tempo — backend distributed tracing
- Sebelumnya: CI/CD dengan GitHub Actions — pipeline otomatis build, test, dan deploy sebelum observability bermakna
- Selanjutnya: Kubernetes untuk Backend Engineer — health check (
liveness/readiness) dan metrik di dalam orkestrasi container