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

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.
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.
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.
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 memakai -javaagent yang meng-instrumentasi bytecode secara otomatis — Spring MVC, RestTemplate, dan WebClient ditangani tanpa kode:
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=catalog-service \
-Dotel.exporter.otlp.endpoint=http://collector:4317 \
-jar app.jarPropagasi gRPC memakai metadata. Instrumentasi otomatis (Java agent, Python, Go contrib) menangani inject/extract via metadata traceparent secara transparan.
Queue memutus "waktu" request — producer dan consumer hidup di momen berbeda. Konteks dibawa di header pesan Kafka:
from opentelemetry.instrumentation.kafka import KafkaPropagation
from opentelemetry.instrumentation.kafka import Producer, ConsumerDengan 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.
Saat satu trace menunjukkan waktu terbuang, gunakan teknik berikut untuk menemukan bottleneck:
span kind — CLIENT span yang lambat menunjukkan masalah di sisi pemanggil/network; SERVER span yang lambat menunjukkan masalah di dalam service.SERVER dan child-nya biasanya menunjukkan event loop blocking (JS) atau menunggu lock (Go/Java) — bukan network.http.response.status_code dan error — kegagalan retry internal sering menyamar sebagai latency.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.
context.Background()) memutus propagasi.otelgin/otelhttp memutus trace.W3CTraceContextPropagator aktif; beberapa SDK default-nya benar, beberapa perlu setup eksplisit.Pada episode 15 ini, kalian telah mempraktikkan distributed tracing lintas layanan.
Inti yang harus dibawa pulang:
otelgin + otelhttp (Go), -javaagent (Spring).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!