Mekanisme Perambatan
Layanan A Layanan B span (trace=abc, span=111) │ ├─ suntikkan ke header ekstrak dari header │ traceparent: 00-abc-111-01 → trace=abc, induk=111 │ │ │ └─ span (trace=abc, span=222)
Instrumentasi otomatis menangani penyuntikan dan ekstraksi untuk protokol umum. Yang perlu ditangani sendiri adalah jalur non-standar: antrean pesan, pekerjaan terjadwal, dan panggilan yang menyeberangi batas proses dengan cara khusus.
Manual: Menyuntik dan Mengekstrak
import { context, propagation, trace } from "@opentelemetry/api";
// Sisi pengirim — sematkan konteks ke pesan
function kirimPesan(isi) {
const pembawa = {};
propagation.inject(context.active(), pembawa);
antrean.publish({ isi, _otel: pembawa });
}
// Sisi penerima — pulihkan konteks lalu buat span
async function terimaPesan(pesan) {
const ctxInduk = propagation.extract(context.active(), pesan._otel ?? {});
const span = tracer.startSpan("proses-pesan", {
kind: SpanKind.CONSUMER
}, ctxInduk);
await context.with(trace.setSpan(ctxInduk, span), async () => {
await proses(pesan.isi);
});
span.end();
}
Bila pesan tertahan di antrean selama sepuluh menit, span konsumen tetap menjadi anak dari span produsen — dan trace-nya terentang sepuluh menit dengan celah kosong besar di tengah. Untuk pekerjaan asinkron yang jaraknya jauh, pertimbangkan memakai span link alih-alih hubungan induk-anak, sehingga keduanya menjadi trace terpisah yang saling menunjuk.
const span = tracer.startSpan("proses-pesan", {
links: [{ context: trace.getSpanContext(ctxInduk) }]
});
Baggage: Membawa Nilai Lintas Layanan
import { propagation, context } from "@opentelemetry/api";
// Layanan A menetapkan
const bag = propagation.createBaggage({
"penyewa.id": { value: "akme" },
"paket.jenis": { value: "enterprise" }
});
const ctx = propagation.setBaggage(context.active(), bag);
await context.with(ctx, async () => {
await fetch("http://layanan-b/proses"); // baggage ikut terkirim
});
// Layanan D — beberapa lompatan kemudian
const penyewa = propagation.getBaggage(context.active())?.getEntry("penyewa.id");
span.setAttribute("penyewa.id", penyewa?.value);
Dua akibatnya sering terlewat. Pertama, ukuran: setiap entri menambah byte pada seluruh permintaan di bawahnya, dan baggage besar terasa pada latensi. Kedua, keamanan: baggage ikut mengalir ke layanan pihak ketiga bila permintaan keluar juga terinstrumentasi — sehingga data sensitif di baggage bocor ke luar. Simpan hanya pengenal singkat, jangan pernah data pribadi atau kredensial.
Nilai yang dibawa baggage tersedia di konteks, tetapi tidak muncul pada span kecuali disalin secara eksplisit. Banyak tim mengira nilai itu langsung terlihat di backend lalu bingung ketika tidak menemukannya. Salin secara manual, atau pakai prosesor baggage di Collector.
Konteks Hilang pada Kode Asinkron
// Konteks HILANG — callback berjalan di luar cakupan aktif
tracer.startActiveSpan("induk", (span) => {
setTimeout(() => {
const anak = tracer.startSpan("anak"); // tidak menemukan induk
anak.end();
}, 100);
span.end();
});
// Konteks TERJAGA — ikat secara eksplisit
tracer.startActiveSpan("induk", (span) => {
const ctx = context.active();
setTimeout(() => {
context.with(ctx, () => {
const anak = tracer.startSpan("anak"); // induk ditemukan
anak.end();
});
}, 100);
});
SDK memakai AsyncLocalStorage yang merambatkan konteks melalui rantai await secara otomatis. Yang bermasalah adalah pola berbasis callback yang memutus rantai itu: setTimeout, pendengar peristiwa, kumpulan koneksi yang memakai ulang callback, dan pustaka lama yang mengelola antrean sendiri.
Memilih Propagator
export OTEL_PROPAGATORS=tracecontext,baggage # bawaan, W3C export OTEL_PROPAGATORS=tracecontext,baggage,b3 # + kompatibel Zipkin export OTEL_PROPAGATORS=b3multi # sistem lama
Layanan lama berbasis Zipkin mengirim header b3, sedangkan layanan baru hanya membaca traceparent — sehingga konteks tidak ditemukan dan trace terpecah tepat di batas antara keduanya. Gejalanya terlihat sebagai trace yang selalu berhenti di layanan tertentu. Selama masa transisi, aktifkan kedua propagator di semua layanan.