Belajar OpenTelemetry - Propagasi & Distributed Tracing Across Services
Episode 15 of 23

Belajar OpenTelemetry - Propagasi & Distributed Tracing Across Services

Mempraktikkan korelasi trace lintas layanan melewati HTTP, gRPC, dan queue Kafka, memahami inject dan extract context di framework Express, Gin, dan Spring, serta teknik melacak latency antar-service untuk mengidentifikasi bottleneck dalam distributed tracing

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Empat belas episode membangun fondasi; episode 15 memakai semuanya di panggung yang paling nyata: lintas layanan. Di sinilah distributed tracing menunjukkan nilainya — ketika satu request melewati gateway, service A, queue, dan service B, dan kalian harus tahu di mana 3 detik itu terbuang.

Ini penting karena observability yang hanya melihat satu service itu seperti menonton satu bagian dari teater sambil menutup telinga. Propagasi yang benar membuat seluruh perjalanan request terlihat sebagai satu trace utuh — dan itulah yang membedakan debug tingkat sistem dari debug menebak-nebak.

Alur Propagasi Lintas HTTP

Ingat kembali episode 4: konteks mengalir lewat header traceparent (dan tracestate). Service yang diinstrumentasi otomatis melakukan extract (dari request masuk) dan inject (ke request keluar). Mari lihat dua jalurnya.

Node.js (Express)

Express dengan auto-instrumentation
const express = require("express")
const { NodeTracerProvider } = require("@opentelemetry/sdk-node")
const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node")
const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-grpc")
 
const provider = new NodeTracerProvider({
  resource: new Resource({ "service.name": "orders-api" }),
})
provider.addSpanProcessor(new BatchSpanProcessor(new OTLPTraceExporter()))
provider.register()
 
const app = express()
app.use(getNodeAutoInstrumentations())
app.get("/orders", async (req, res) => {
  const resp = await fetch("http://payment-service/charge")  // otomatis di-inject
  res.json(await resp.json())
})

fetch global Node.js 18+ terinstrumentasi otomatis — request keluar membawa traceparent dari span aktif tanpa satu baris kode manual.

Go (Gin)

Gin middleware OTel
import (
    "net/http"
 
    "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    "github.com/gin-gonic/gin"
)
 
func main() {
    r := gin.Default()
    r.Use(otelgin.Middleware("catalog-service"))
 
    client := http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}
 
    r.GET("/products/:id", func(c *gin.Context) {
        req, _ := http.NewRequestWithContext(c.Request.Context(), "GET", "http://inventory-service/stock", nil)
        resp, err := client.Do(req) // inject otomatis lewat otelhttp
        c.JSON(200, gin.H{"stock": resp.StatusCode})
    })
    r.Run(":8080")
}

Go tidak punya runtime agent — instrumentasi dilakukan lewat middleware per framework (otelgin) dan transport per client (otelhttp). Konteks gRPC di-pass secara eksplisit lewat c.Request.Context() — kebiasaan yang harus dijaga.

Java (Spring Boot)

Java memakai -javaagent yang meng-instrumentasi bytecode secara otomatis — Spring MVC, RestTemplate, dan WebClient ditangani tanpa kode:

Spring Boot dengan javaagent
java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=catalog-service \
  -Dotel.exporter.otlp.endpoint=http://collector:4317 \
  -jar app.jar

Lintas gRPC dan Queue (Kafka)

gRPC

Propagasi gRPC memakai metadata. Instrumentasi otomatis (Java agent, Python, Go contrib) menangani inject/extract via metadata traceparent secara transparan.

Kafka

Queue memutus "waktu" request — producer dan consumer hidup di momen berbeda. Konteks dibawa di header pesan Kafka:

Korelasi trace lewat Kafka
from opentelemetry.instrumentation.kafka import KafkaPropagation
from opentelemetry.instrumentation.kafka import Producer, Consumer

Dengan instrumentasi resmi, saat producer mengirim pesan di dalam span aktif, traceparent disisipkan ke header pesan. Consumer mengekstraknya sehingga span konsumsi menjadi anak logis dari span produksi — trace menyambung meski pesan menunggu di queue.

Note

Perhatikan terminologi "parent" pada messaging: span PRODUCER dan CONSUMER dihubungkan lewat message context, bukan parent-child temporal biasa. Di UI backend, keduanya tampil berdekatan dalam satu trace dengan messaging.operation sebagai penanda — bukan sebagai relasi induk-anak langsung. Ini disengaja: memisahkan waktu produksi dan konsumsi agar durasi queue terlihat jelas.

Troubleshooting Latency Antar-Service

Saat satu trace menunjukkan waktu terbuang, gunakan teknik berikut untuk menemukan bottleneck:

  1. Bandingkan durasi span per hop — service mana yang memakan waktu terbesar? Gunakan Gantt/timeline view di backend (Jaeger Trace Timeline) untuk melihat overlap.
  2. Waspadai pola serial palsu — beberapa service berjalan paralel; pastikan timeline menunjukkan paralelisme, bukan antrean tak disengaja.
  3. Cek span kindCLIENT span yang lambat menunjukkan masalah di sisi pemanggil/network; SERVER span yang lambat menunjukkan masalah di dalam service.
  4. Cari celah antar-span — gap besar antara span SERVER dan child-nya biasanya menunjukkan event loop blocking (JS) atau menunggu lock (Go/Java) — bukan network.
  5. Lihat attribute http.response.status_code dan error — kegagalan retry internal sering menyamar sebagai latency.
100%

Pada contoh di atas, bottleneck jelas di Inventory (2 detik). Tim dapat langsung mengejar service itu, bukan mengganti gateway atau network.

Tip

Latency yang muncul di dashboard tanpa span penyebab hampir selalu karena ada bagian request yang tidak diinstrumentasi — misalnya call ke library yang tidak ter-instrumentasi, thread pool tanpa span, atau context yang hilang di goroutine/async task. Temukan "celah tak ber-span" itu, bukan hanya menambah resource.

Common Pitfalls

  • Context tidak di-pass — memanggil client HTTP dengan konteks baru (context.Background()) memutus propagasi.
  • Framework tanpa middleware — handler yang tidak memakai otelgin/otelhttp memutus trace.
  • Pesan Kafka tanpa instrumentasi — producer/consumer terpisah jadi trace sendiri-sendiri.
  • Async task tanpa context — Promise/generator yang tidak meneruskan konteks aktif memutus korelasi.
  • Propagator tidak terpasang — memastikan W3CTraceContextPropagator aktif; beberapa SDK default-nya benar, beberapa perlu setup eksplisit.

Penutup

Pada episode 15 ini, kalian telah mempraktikkan distributed tracing lintas layanan.

Inti yang harus dibawa pulang:

  • HTTP: auto-instrumentation menangani inject/extract — Express (Node), otelgin + otelhttp (Go), -javaagent (Spring).
  • gRPC: propagasi via metadata, ditangani instrumentasi resmi.
  • Kafka: konteks di header pesan — span producer-consumer tersambung lewat message context.
  • Troubleshooting latency: bandingkan durasi per hop, waspadai span serial palsu, dan cari celah tak ber-span.

Di episode 16 selanjutnya, kita akan membahas security: TLS, auth & sensitive data — TLS/mTLS untuk OTLP, autentikasi via headers/extension (bearer, oauth2, basic), best practice hosting collector, serta redaksi PII dan configopaque untuk nilai sensitif. Sampai jumpa di episode 16!