Kodeloka
  1. Kodeloka
  2. Kelas OpenTelemetry
  3. Materi

3 · Metrik & Log · Modul 6 dari 8

Perambatan Konteks Lintas Layanan

Menjaga trace tetap utuh saat melintasi HTTP, antrean pesan, dan pekerjaan latar.

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();
}
⚠️ Trace lewat antrean sering menjadi sangat panjang

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);
🚫 Baggage terkirim di header pada setiap permintaan hilir

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.

⚠️ Baggage tidak otomatis menjadi atribut span

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);
});
💡 Di Node.js, async/await umumnya aman

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
🚫 Propagator yang berbeda antar layanan memutus trace

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.