Observability: Logging, Monitoring & Tracing

17 min readAdvanced
ObservabilityLoggingMonitoringTracing

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.

AspekMonitoringObservability
FokusMengukur hal yang sudah diketahui (known unknowns)Memungkinkan penyelidikan hal yang belum diketahui (unknown unknowns)
PertanyaanApakah sistem sehat? Apakah ada yang error?Kenapa lambat? Kenapa gagal? Di mana tepatnya?
PendekatanPasang alert + dashboard untuk metrik tertentuEkspor data kaya (log, metrik, trace) yang bisa dieksplorasi bebas
HasilTahu "ada masalah"Tahu "akar masalah"
Prinsip dasarTidak mungkin memonitor sesuatu yang tidak Anda ketahui keberadaannyaJangan 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:

PilarApa ituMenjawab pertanyaanBentuk data
LogsCatatan peristiwa diskretKapan ini terjadi? Apa yang terjadi?Teks/JSON, satu entri per peristiwa
MetricsAngka terukur yang diagregasiSeberapa banyak? Seberapa cepat?Time series angka (counter, gauge, histogram)
TracesPerjalanan satu request lintas komponenRequest 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).

LevelKapan dipakaiContoh
TRACEDetail internal yang sangat verbose (hampir selalu untuk debugging)Nilai variabel dalam loop
DEBUGDetail untuk memahami alur saat investigasi"masuk ke method applyDiscount dengan nilai X"
INFOPeristiwa penting yang "normal" dan perlu dilacakService start, order berhasil dibuat, user login
WARNKondisi tidak ideal tapi masih berjalanRetry pertama gagal, cache miss, config kedaluwarsa
ERRORKegagalan nyata yang membutuhkan perhatianException 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 di DEBUG maupun TRACE. Log sering lebih lama hidup daripada sistem itu sendiri; sebuah token yang tertulis di log adalah kebocoran yang menunggu ditemukan. Tunjukkan hanya userId (bukan password), hanya orderId (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 queue di memori; jika ukurannya kecil dan aplikasi dibanjiri log, ada risiko log dibuang saat antrean penuh. Atur queueSize sesuai kebutuhan dan jangan menaruh log ERROR di jalur yang sepenuhnya best-effort.

Actuator: Health Check & Metrics Siap Pakai

Spring Boot menyediakan Actuatorsekumpulan 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:

EndpointFungsiContoh respons
/actuator/healthStatus kesehatan aplikasi (untuk liveness/readiness probe){"status":"UP"}
/actuator/metricsDaftar metrik yang tersediahttp.server.requests, jvm.memory.used
/actuator/metrics/{name}Detail satu metrikkuantil, total, count
/actuator/infoInformasi bebas (versi, commit, git){"app":{"version":"1.2.0"}}
/actuator/prometheusMetrik dalam format Prometheusbaris 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:

MetrikPertanyaan yang dijawabContoh 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 rateBerapa persen request gagal?rate(http_server_requests_seconds_count{status=~"5.."}[5m]) / rate(http_server_requests_seconds_count[5m])
SaturationSeberapa 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 collection yang 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:

KonsepPenjelasan
spanSatu unit kerja bernama, punya durasi dan status
traceKumpulan span yang saling terhubung untuk satu request
context propagationMekanisme meneruskan traceId + spanId dari satu service ke service berikutnya
traceparentHeader HTTP standar yang membawa konteks trace antar service
exporterPengirim 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:

  1. Dashboard Grafana menunjukkan lonjakan latensi p99 pada order-service.
  2. Anda klik titik waktu tersebut → buka log order-service pada jam itu, difilter level=ERROR.
  3. Log error membawa traceId: a1b2c3d4.
  4. Anda buka trace dengan traceId itu di Tempo/Jaeger → terlihat bahwa lambatnya berasal dari span "SELECT orders" ke database.
  5. Anda periksa metrik database pada waktu yang sama (misal connection pool penuh).

Kunci correlation: traceId adalah "benang merah" yang menjahit log, metrik, dan trace menjadi satu cerita. Pastikan tooling Anda — log aggregator, Prometheus, dan tracing backend — semua mengerti traceId sehingga 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:

JebakanDampakSolusi
Log semuanya (DEBUG di produksi)noise membanjiri sinyal, storage borosLog ringkas + level yang tepat; perbanyak di investigasi
Log berisi PIIMelanggar privasi/regulasi (GDPR, UU PDP)Jangan log data pribadi; gunakan userId pseudonim
Log berisi secretBocornya password/token lewat logFilterisasi otomatis + review, jangan pernah log credential
Label metrik tak terkendaliCardinality meledak (misal userId jadi label)Batasi label; jangan masukkan nilai unik ke label
Hanya lihat p5050% pengguna bisa merasakan latensi jauh lebih burukPantau p95/p99 dan max
Alert terlalu banyakAlert fatigue — insiden nyata terabaikanSedikit alert berkualitas, for yang cukup
Pasang telemetry setelah insidenTidak ada data saat dibutuhkanPasang 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

← Back to technical articles