Mencatat jejak observability end-to-end di tokokita: distributed tracing dengan span dan propagasi traceparent, metrik RED per endpoint, log JSON terstruktur dengan request_id dan service, serta backend Prometheus, Grafana, Tempo/Jaeger, dan Loki

Setelah sekian episode membangun arsitektur yang rumit, jujur saja: ini episode yang paling terasa manfaatnya saat produksi. Observability adalah jawaban atas pertanyaan paling mahal di microservices: "di mana request saya tersesat, kenapa lambat, dan di mana errornya?" Di monolith cukup stack trace; di microservices, satu request melewati gateway, product, order, payment, Kafka, sampai notifikasi — tak ada satu pun layanan yang tahu keseluruhan ceritanya.
Mengapa episode ini penting? Karena tanpa observability, semua arsitektur yang kita bangun hanyalah utang yang menumpuk — kamu tidak akan pernah tahu layanan mana yang rusak sampai user mengeluh. OpenTelemetry menyatukan trace, metrics, logs di bawah satu standar terbuka.
OpenTelemetry menamai setiap unit kerja sebagai span: "HTTP POST /orders", "reserve stock", "consume order-events", "SMTP send". Span punya nama, start/end time, attribute, dan relasi parent-child. Rangkaian span yang saling terkait untuk satu request adalah trace, diidentifikasi trace_id (satu trace = satu logika request lintas layanan).
import { trace } from '@opentelemetry/api'
const tracer = trace.getTracer('order-service')
export const createOrderHandler = async (req) => {
const span = tracer.startSpan('POST /orders', { attributes: { http.method: 'POST' } })
const ctx = trace.setSpan(trace.active(), span)
try {
const order = await createOrderWithContext(req, ctx)
span.setStatus({ code: 1 }) // OK
return order
} finally {
span.end() // catat durasi akurat walau error
}
}traceparent: Jembatan antar LayananAgar trace menyambung, setiap hop harus meneruskan header traceparent (00-{trace_id}-{span_id}-{flags}):
web-app ──x-request-id + traceparent──→ gateway
gateway ──traceparent──────────────→ order-service
order-service ──traceparent (message header)──→ kafka producer
kafka consumer (notification) ──traceparent──→ SMTP
satu trace_id sepanjang perjalananSaat memproduksi event, salin trace context ke header Kafka agar konsumen meneruskan trace yang sama — inilah propogasi lintas transport. Dengan selesainya propagasi, kita bisa melihat satu request melintasi auth → order → payment → kafka → notif dalam satu waterfall di Tempo/Jaeger.
Metrics adalah angka agregat yang bisa di-alert. Untuk service API, pola yang paling banyak dipakai adalah RED:
import { metrics } from '@opentelemetry/api'
const meter = metrics.getMeter('api-gateway')
const latency = meter.createHistogram('http.server.duration', {
unit: 'ms',
description: 'Latency per request',
})
// di onAfterHandle: latency.record(durationMs, {
// http.route, http.method, http.status_code
// })Histogram menyimpan distribusi — bisa dihitung p99 kapan pun oleh Prometheus. Label (route, status) dipakai untuk agregasi: "endpoint mana yang lambat?" dijawab langsung.
Log JSON terstruktur lebih berharga daripada teks bebas. Standar field tokokita: timestamp, level, service, request_id, trace_id, span_id, event payload.
export function logger(base: { service: string }) {
return {
info: (msg: string, fields: Record<string, unknown> = {}) =>
console.log(JSON.stringify({ ts: new Date().toISOString(), level: 'info', service: base.service, msg, ...fields })),
error: (msg: string, fields: Record<string, unknown> = {}) =>
console.error(JSON.stringify({ ts: new Date().toISOString(), level: 'error', service: base.service, msg, ...fields })),
}
}
// dipakai: logger({ service: 'order-service' }).error('reserve failed',
// { requestId, traceId: span.spanContext().traceId, orderId })Saat semua layanan memakai skema yang sama, Loki bisa meng-agregasi: "tampilkan semua log ber-request_id X" — bahkan lintas layanan. Inilah yang membuat debugging distributed terasa seperti debugging monolith.
Stack open-source yang dipakai tokokita:
| Data | Backend | Tugas |
|---|---|---|
| Metrics | Prometheus | scrape dari /metrics tiap service; simpan time-series |
| Dashboard/Alert | Grafana | visualisasi + alert rules |
| Traces | Tempo (atau Jaeger) | simpan & query trace waterfall |
| Logs | Loki | log terpusat dengan label |
Arsitektur pengumpulannya: tiap service mengekspos /metrics (Prometheus) dan mengirim trace/log via OTLP. Di skala besar, OpenTelemetry Collector menjadi proxy penerima semua sinyal — dan berbagai managed backend (Grafana Cloud) tinggal menyambung.
# akses grafana: http://localhost:3000 (admin/admin)Buat satu order, lalu di Grafana/Tempo:
POST /api/orders → 4-6 span (gateway, order, reserve, kafka publish, notif consume) — satu trace_id.http.server.duration menunjukkan p99 POST /orders.{request_id: "..."} di Loki menampilkan semua log lintas service untuk request yang sama.Tip
Ketiga pilar observability itu saling melengkapi, bukan tumpang tindih: metrics menjawab "ada yang lambat?" (alert, dashboard), traces menjawab "di hop mana?" (per-request), logs menjawab "detailnya apa?" (konteks program). Di incident, urutan yang benar biasanya: metric → trace → log. Latih alur ini — inilah basis runbook di episode 24.
Episode 20 melengkapi tokokita dengan mata & telinga:
traceparent lintas HTTP dan Kafka; satu trace_id end-to-end.service, request_id, trace_id, span_id yang seragam.Di episode 21 selanjutnya, kita membahas scaling: KEDA, HPA, dan kapasitas — autoscale berdasarkan CPU/request, scaling worker berbasis consumer lag, pola scaling per tipe, dan capacity planning request vs limit per service. Sampai jumpa di episode 21!