Header traceparent: Panduan Lengkap W3C Trace Context
Header traceparent adalah header distributed tracing yang dibakukan W3C: satu baris ASCII yang membawa identitas sebuah request melintasi setiap layanan yang disentuhnya. Pada versi yang berlaku sekarang panjangnya persis 55 karakter, dengan empat bidang (field) yang dipisahkan tanda hubung:
00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│ │ │ │
│ │ │ └─ trace-flags (2 hex, 1 byte)
│ │ └─ parent-id (16 hex, 8 bytes)
│ └─ trace-id (32 hex, 16 bytes)
└─ version (2 hex, 1 byte)
Dua di antara bidang itu berperilaku berbeda saat request bergerak. trace-id tetap sama di setiap hop: itulah nama request tersebut, dari edge proxy sampai panggilan database terakhir. parent-id berubah di setiap hop, karena yang ditunjuknya adalah span yang memanggil Anda, bukan request-nya. Mencampuradukkan keduanya adalah sumber klasik tiket bertajuk “trace saya kelihatan aneh”.
Itu anatominya. Yang lebih sulit adalah hal-hal yang tidak muat di tabel bidang: apa yang membuat sebuah header tidak valid, apa yang dilakukan penerima yang patuh spesifikasi ketika menerimanya, dan di mana header itu diam-diam lenyap di antara dua layanan yang sama-sama mengaku mendukung tracing. Kalau ada header sungguhan di depan Anda, tempelkan ke decoder traceparent gratis sambil membaca; alat itu memecah bidangnya, membentangkan byte flag bit demi bit, dan menyebutkan aturan mana yang dilanggar sebuah header rusak.
Header traceparent Sekilas
Header traceparent adalah satu header HTTP yang membawa satu trace melintasi banyak layanan sekaligus. Isinya empat bidang heksadesimal yang dipisahkan tanda hubung (version, trace-id, parent-id, dan trace-flags), dan untuk versi yang berlaku saat ini panjangnya tepat 55 karakter. trace-id menamai keseluruhan request, sedangkan parent-id menamai span yang memanggil Anda.
| Bidang | Digit hex | Byte | Menandai apa | Berubah tiap hop? |
|---|---|---|---|---|
version | 2 | 1 | Format yang mengatur sisa header. Sekarang selalu 00 | Tidak |
trace-id | 32 | 16 | Keseluruhan request, ujung ke ujung | Tidak |
parent-id | 16 | 8 | Span pemanggil (span ID milik pemanggil Anda) | Ya |
trace-flags | 2 | 1 | Bidang 8 bit; bit 0 adalah sampled | Jarang |
Tambahkan tiga tanda hubung ke 52 digit hex itu dan Anda dapat 55 karakter. Angka ini layak Anda hafal, sebab header versi 00 dengan panjang berapa pun selain itu tidak valid, dan panjang adalah hal tercepat yang bisa Anda periksa dengan mata telanjang.
Seluruh isi header adalah heksadesimal huruf kecil, bukan “heksadesimal, bebas huruf besar-kecil”. Tata bahasa dalam rekomendasi W3C Trace Context hanya menerima 0-9 dan a-f, tidak ada yang lain. Itulah sebabnya trace ID huruf besar dengan nilai yang sepenuhnya benar tetap dibuang di hilir.
Bidang demi Bidang
Setiap bidang punya lebar tetapnya sendiri, nilai yang membuatnya tidak valid, dan cara gagal yang khas.
version — kenapa ini bukan sekadar “selalu 00”
Hari ini byte versi bernilai 00, dan akan tetap 00 untuk waktu yang cukup lama. Namun ff terlarang secara eksplisit: spesifikasi mencadangkannya sebagai nilai tidak valid, sehingga header yang dibuka dengan ff sudah mati sejak tiba, apa pun isi berikutnya.
Aturan yang menarik justru soal versi yang belum pernah Anda lihat. Parser yang menulis if (version !== '00') reject() itu keliru, dan kekeliruannya mahal. Spesifikasi meminta penerima tetap mencoba mem-parse ketika versinya lebih tinggi dan headernya minimal sepanjang format yang sudah dikenal: baca bidang yang Anda kenali, toleransi data tambahan di ekor, lalu lanjutkan. Kalau Anda menolaknya, layanan Anda berubah menjadi batas tempat trace berhenti dan trace baru dimulai, persis begitu ada pihak di hulu yang naik versi.
// Salah: menjadikan layanan Anda tempat trace mati
if (version !== '00') throw new Error('bad traceparent');
// Benar: parse prefiks yang Anda pahami
if (version !== '00' && header.length >= 55) {
// baca version, trace-id, parent-id, trace-flags; abaikan sisanya
}
trace-id — 16 byte, identitas seluruh request
Tiga puluh dua digit hex huruf kecil, konstan sepanjang umur trace. Layanan mana pun yang membuatnya di awal, setiap hop menyalinnya ke depan tanpa perubahan. Ketika Anda mencari sebuah trace di backend observability, string inilah yang Anda tempelkan.
Dua aturan mengatur nilainya. Panjangnya harus 32 digit hex, dan isinya tidak boleh nol semua. 00000000000000000000000000000000 bukan berarti “trace yang datanya belum ada”: spesifikasi menyebutnya sebagai nilai tidak valid dan mewajibkan penerima mengabaikan seluruh header. Dalam praktik, trace ID nol semua menandakan SDK yang tidak pernah melakukan inisialisasi, atau middleware yang menyisipkan placeholder karena tidak punya konteks nyata untuk diteruskan.
Sebuah trace-id berukuran 128 bit, selebar UUID, tetapi bukan UUID. Tidak ada bit versi, bit varian, tanda hubung, atau struktur apa pun di dalamnya: enam belas byte buram. Anda tidak bisa mem-parse v4 darinya, dan UUID yang tanda hubungnya dilepas pun tidak otomatis menjadi trace-id yang sah, karena nibble versi dan varian membuat keacakannya tidak seragam. Kalau Anda ingin melihat apa saja yang sebenarnya dipesan UUID di dalam 128 bit itu, apa yang sebenarnya dikodekan UUID menelusuri tata letaknya, dan generator UUID menampilkan bit versi dan varian pada posisinya.
parent-id — 8 byte, span yang memanggil Anda
Enam belas digit hex, yang setiap hop tulis ulang. Namanya menimbulkan kebingungan lebih besar daripada yang pantas untuk bidang sesederhana ini: spesifikasi W3C menyebutnya parent-id, OpenTelemetry menyebut 8 byte yang sama sebagai span ID, dan keduanya hal yang sama dilihat dari dua arah. Dari sudut pandang layanan Anda, itu induknya; dari sudut pandang pemanggil, itu ID span yang baru saja ia buat untuk request keluar.
Jadi ketika layanan A memanggil layanan B, A menaruh span ID miliknya sendiri di slot parent-id. B lalu membuat span anak, dan ketika B memanggil C, B menaruh span ID milik B di sana. trace-id tidak tersentuh sepanjang perjalanan. Itulah seluruh algoritma propagasinya.
parent-id nol semua juga tidak valid, dengan alasan yang sama seperti trace-id: 0000000000000000 berarti pemanggil tidak menyediakan span sungguhan, dan penerima sebaiknya membuang headernya alih-alih menghormatinya setengah-setengah.
trace-flags — tampak seperti boolean, sebenarnya delapan bit
Hampir semua header yang akan Anda temui berakhir dengan 01, jadi wajar kalau Anda membacanya sebagai ya/tidak. Padahal ini satu byte, dan bit-bitnya punya penugasan masing-masing:
- bit 0, mask
0x01—sampled - bit 1, mask
0x02—random-trace-id, ditambahkan di Trace Context Level 2 - bit 2–7 — dicadangkan; abaikan saat menerima, nolkan saat mengirim request keluar
Beginilah hasil pembacaan tiap kombinasinya:
| Hex | Biner | sampled | random-trace-id | Apakah flags === 0x01 benar? |
|---|---|---|---|---|
00 | 00000000 | false | false | false |
01 | 00000001 | true | false | true |
02 | 00000010 | false | true | false |
03 | 00000011 | true | true | false ← inilah bug-nya |
Baca ulang baris terakhir. Trace dengan flag 03 itu ter-sample. Kode apa pun yang membandingkan seluruh byte dengan 01 akan melaporkannya sebagai tidak ter-sample, tanpa suara, dan hanya untuk sebagian trafik yang kebetulan menyalakan flag Level 2. Kegagalan seperti ini sulit dilacak, karena gejalanya mirip masalah laju sampling ketimbang bug parsing.
const flags = parseInt(traceFlags, 16);
// Salah: memperlakukan bit field sebagai enumerasi
const sampled = traceFlags === '01';
// Benar
const sampled = (flags & 0x01) !== 0;
const randomTraceId = (flags & 0x02) !== 0;
Apa yang dijamin random-trace-id? Bahwa setidaknya 7 byte paling kanan dari trace-id lahir dari keacakan yang seragam. Kedengarannya akademis sampai Anda memikirkan consistent sampling: kalau sistem di hilir ingin menyimpan 1% trace dan butuh setiap layanan sepakat sendiri-sendiri soal 1% yang mana, sistem itu bisa memodulo byte-byte tersebut alih-alih meng-hash ID-nya lebih dulu. Flag ini adalah janji dari hulu bahwa cara itu aman.
Apa yang Membuat traceparent Tidak Valid
Berikut daftar penolakan lengkap untuk header versi 00:
| Gejala | Aturan | Hasil |
|---|---|---|
00-4BF92F35...-01 | Tata bahasa hanya menerima hex huruf kecil | Tidak valid — nilainya benar, headernya ditolak |
ff-... | Spesifikasi melarang versi ff | Tidak valid |
trace-id bernilai 00000000000000000000000000000000 | trace-id nol semua tercatat sebagai nilai tidak valid | Tidak valid |
parent-id bernilai 0000000000000000 | parent-id nol semua tercatat sebagai nilai tidak valid | Tidak valid |
| trace-id bukan 32 digit hex | Lebar tetap | Tidak valid |
| parent-id bukan 16 digit hex | Lebar tetap | Tidak valid |
| trace-flags bukan 2 digit hex | Lebar tetap | Tidak valid |
Header tidak persis 55 karakter, versi 00 | Data tambahan hanya sah pada versi mendatang | Tidak valid |
Karakter apa pun di luar 0-9a-f dan tanda hubung | Bukan heksadesimal | Tidak valid |
Konsekuensinya:
Penerima yang patuh spesifikasi tidak memperbaiki header traceparent yang tidak valid dan tidak meneruskannya. Ia membuang headernya lalu memulai trace yang benar-benar baru dengan trace-id yang baru dibuat.
Artinya, gejala di layar Anda bukan trace yang rusak. Yang Anda lihat adalah dua trace pendek yang terputus: satu berhenti mendadak di layanan yang mengirim header rusak, satu lagi seolah muncul entah dari mana di layanan yang menerimanya. Tidak ada satu sistem pun yang menandainya sebagai error. Kedua trace itu tampak sehat kalau Anda lihat sendiri-sendiri. Orang menghabiskan sore hari mencari mata rantai yang hilang di antara keduanya, padahal jawabannya cuma: sebuah middleware mengubah string hex menjadi huruf besar, atau header buatan tangan ternyata panjangnya 54 karakter.
Panjang dan besar-kecil huruf adalah dua mode kegagalan yang tidak akan terlihat sekeras apa pun Anda menatapnya. Tempelkan headernya ke decoder dan alat itu menyebutkan aturan persis mana yang dilanggar, jadi Anda tidak perlu menghitung digit satu per satu.
tracestate: Header Pendamping yang Sering Salah Dipahami
traceparent membawa identitas standar. Header tracestate membawa apa pun yang ingin ditambahkan tiap vendor di sampingnya, dalam bentuk anggota key=value yang dipisahkan koma:
tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE
Implementasi yang tidak mengenali sebuah key wajib meneruskannya tanpa perubahan. Justru di situlah tujuan desainnya: vendor bisa menumpangkan state milik mereka pada trace standar tanpa mengharuskan setiap hop memahaminya.
Meski begitu, tata bahasanya bergigi, dan tiga aturannya menjelaskan gejala produksi yang nyata.
Batas 32 list-member itu keras, dan tertulis langsung di tata bahasanya: list = list-member 0*31( OWS "," OWS list-member ). tracestate dengan 33 anggota bukanlah tracestate dengan satu entri berlebih, melainkan header yang tidak valid, dan penerima berhak membuangnya bulat-bulat. Inilah jawaban atas gejala yang kalau tidak dijelaskan terlihat seperti sihir: data vendor ada di edge, masih ada dua hop kemudian, lalu hilang sama sekali di hop kelima. Setiap hop menambahkan anggotanya sendiri, daftar itu melewati 32, dan sejak titik itu seluruh header dibuang, bukan dipangkas.
Nilai berukuran 1 sampai 256 karakter dan tidak pernah boleh kosong. Produksi nilai harus berakhir dengan karakter non-spasi, jadi vendor= bukan “key tanpa nilai”, melainkan galat sintaks. Hanya ASCII yang bisa dicetak, dan jangan pernah ada koma atau tanda sama dengan di dalam nilainya.
Tata bahasa key berubah antara Level 1 dan Level 2. Level 1 mendefinisikan key lewat produksi tenant@vendor, dengan @ sebagai pemisah struktural. Level 2 menggantinya dengan kelas karakter datar: key diawali huruf kecil atau angka, lalu dilanjutkan a-z, 0-9, _, -, *, / dan @. Di bawah Level 2, @ cuma karakter biasa, key boleh diawali angka, dan a@b@c adalah key yang sepenuhnya sah padahal produksi Level 1 akan menolaknya. Kalau proxy Anda memvalidasi dengan Level 1 sementara sebuah layanan mengeluarkan key Level 2, satu pihak menerima apa yang ditolak pihak lain, dan header itu raib tepat di satu hop.
Dua aturan lain yang layak Anda ketahui. Key ganda langsung dianggap tidak valid. Dan ketika Anda mengubah parent-id di traceparent, Anda wajib memindahkan entri tracestate milik Anda ke depan daftar, karena urutannya paling baru di depan. Melewatkan langkah pindah-ke-depan ini meninggalkan state vendor yang basi di posisi yang akan dibaca pembaca sebagai yang terkini.
Terakhir, aturan yang ramah: list-member kosong itu sah. Ketika sebuah middlebox menghapus satu entri, ia sering meninggalkan komanya, sehingga muncul rojo=1,,congo=2. Spesifikasi mengizinkan ini secara eksplisit, jadi parser sebaiknya membuang anggota kosong itu lalu lanjut, bukan menyatakan headernya cacat. Tampilan tracestate di decoder mendaftar setiap anggota lengkap dengan validasi per anggota dan hitungan berjalan terhadap batas 32 anggota, dan itu biasanya lebih cepat daripada menghitung koma sendiri.
Perjalanan header distributed tracing: Satu Request, Empat Hop
Ikuti satu request melewati sebuah edge proxy, sebuah layanan API, dan dua layanan di hilir:
Client
│ (tanpa traceparent — edge adalah root)
▼
Proxy edge membuat trace-id 4bf9…4736, span 00f0…02b7
│ traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
▼
Layanan API membacanya, membuat span a1b2c3d4e5f60718
│ traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-a1b2c3d4e5f60718-01
▼
Layanan Orders membacanya, membuat span 9f8e7d6c5b4a3928
│ traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-9f8e7d6c5b4a3928-01
▼
Layanan Inventory
Setiap hop melakukan tiga hal yang sama: membaca header masuk, mengganti parent-id dengan span ID miliknya sendiri untuk tiap panggilan keluar, lalu meneruskan trace-id dan flag tanpa perubahan. Ketika sama sekali tidak ada header masuk (seperti client di atas), layanan penerimalah yang menjadi root: ia membuat trace-id dan mengambil keputusan sampling untuk semua yang ada di hilir.
Anda bisa menyuntikkan header secara manual untuk menguji satu rantai dari ujung ke ujung:
curl -sS -o /dev/null -w '%{http_code}\n' \
-H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' \
-H 'tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE' \
https://example.com/api
Putar ulang header yang Anda tangkap dari produksi ke staging, lalu amati trace-id yang sama muncul di backend Anda. Generator perintah cURL merangkai flag-nya untuk Anda kalau Anda perlu menambahkan auth atau body, dan contekan curl membahas opsi header dan verbose yang akan Anda butuhkan saat debugging.
Untuk melihat apa yang benar-benar diterima sebuah layanan, bukan apa yang Anda kira sudah Anda kirim, jalankan echo server sekali pakai dan arahkan satu hop ke sana:
python3 - <<'PY'
from http.server import BaseHTTPRequestHandler, HTTPServer
class Echo(BaseHTTPRequestHandler):
def do_GET(self):
for name, value in self.headers.items():
print(f"{name}: {value}")
self.send_response(200)
self.end_headers()
self.wfile.write(b"ok\n")
HTTPServer(("127.0.0.1", 8080), Echo).serve_forever()
PY
Lalu jalankan curl -H 'traceparent: …' http://127.0.0.1:8080/ dan baca apa yang keluar di seberang sana. Separuh investigasi bertema “proxy-nya memakan header saya” berakhir di sini.
trace-flags sampled: keputusan dari hulu, bukan tanda terima
Bit sampled bernilai 1 di trace-flags berarti layanan di hulu sudah memutuskan untuk merekam trace ini. Itu bukan janji bahwa datanya sampai ke backend Anda.
Head-based sampling mengambil keputusan itu di root, sebelum apa pun terjadi, lalu menyebarkannya ke bawah: murah, konsisten antar layanan, dan buta, karena ia tidak mungkin tahu bahwa request tersebut sebentar lagi gagal. Tail-based sampling menahan span di buffer sampai trace selesai baru memutuskan, sehingga ia bisa menyimpan setiap trace yang mengandung error, dengan ongkos menahan span di memori dan mensyaratkan span dari semua layanan mendarat di collector yang sama.
Di bawah tail-based sampling, sebuah trace bisa tiba dengan flag 01 di setiap hop dan tetap dibuang di ujung. Rate limit dan kuota ekspor juga bisa membuangnya. Jadi 01 di edge plus trace yang tidak muncul di UI belum tentu bug propagasi; periksa dulu metrik drop milik collector sebelum Anda menyalahkan header.
Kasus sebaliknya lebih sering muncul sehari-hari. Kalau flag yang masuk bernilai 00, pemanggil sudah menjalankan sampler-nya dan memilih untuk tidak merekam. Tidak ada yang salah konfigurasi di layanan Anda, dan mengaudit sampler sendiri cuma buang waktu. Pertanyaannya adalah layanan hulu mana yang memutuskan untuk tidak melakukan sampling.
Mengonversi Antar Format Propagasi
W3C Trace Context memenangi persaingan, tetapi banyak sistem masih berbicara dengan format lama, dan gateway menerjemahkan di antaranya. Contoh traceparent yang sama, ditulis dalam empat format:
| Format | Header | Nilai untuk contoh kita |
|---|---|---|
| W3C | traceparent | 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 |
| B3 tunggal | b3 | 4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1 |
| B3 multi | X-B3-TraceId, X-B3-SpanId, X-B3-Sampled | 4bf92f3577b34da6a3ce929d0e0e4736, 00f067aa0ba902b7, 1 |
| Datadog | x-datadog-trace-id, x-datadog-parent-id, tag _dd.p.tid | 11803532876627986230, 67667974448284343, 4bf92f3577b34da6 |
| AWS X-Ray | X-Amzn-Trace-Id | Root=1-4bf92f35-77b34da6a3ce929d0e0e4736;Parent=00f067aa0ba902b7;Sampled=1 |
Datadog: pemisahan 64 bit atas dan bawah
Pengenal milik Datadog lahir sebelum trace ID 128 bit ada, dan lapisan kompatibilitasnya adalah tempat sebagian besar konversi tergelincir. x-datadog-trace-id membawa 64 bit bawah sebagai string desimal. 64 bit atas berjalan terpisah, dalam bentuk heksadesimal, di tag _dd.p.tid, yang sendiri menumpang di header x-datadog-tags.
const traceparent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01';
const [, traceId, parentId] = traceparent.split('-');
const datadogTraceId = BigInt('0x' + traceId.slice(16)).toString(10);
const higher64Hex = traceId.slice(0, 16);
const datadogParentId = BigInt('0x' + parentId).toString(10);
console.log('x-datadog-trace-id:', datadogTraceId); // 11803532876627986230
console.log('x-datadog-tags:', higher64Hex); // _dd.p.tid=4bf92f3577b34da6
console.log('x-datadog-parent-id:', datadogParentId); // 67667974448284343
Kesalahan klasiknya adalah mengonversi seluruh 128 bit menjadi satu bilangan desimal:
BigInt('0x' + traceId).toString(10);
// 100985939111033328018442752961257817910 — tidak cocok dengan apa pun di UI
Nilai itu bukan hasil aritmetika yang keliru. Itu penulisan desimal yang benar dari kuantitas yang salah, dan itulah sebabnya ia lolos review lalu diam-diam tidak cocok dengan satu trace pun.
Jebakan kedua adalah presisi numerik. Pengenal 64 bit melampaui Number.MAX_SAFE_INTEGER, yaitu 9007199254740991, jadi jalur kode mana pun yang membiarkan trace ID berubah menjadi number JavaScript akan merusak digit-digit bawahnya. Simpan trace ID sebagai string dan pakai BigInt hanya kalau Anda memang perlu berhitung; ID yang tiba tanpa tanda kutip di JSON sudah rusak sebelum Anda sempat melihatnya.
AWS X-Ray: timestamp yang sebenarnya tidak ada
Trace ID X-Ray berbentuk 1-{8 hex}-{24 hex}, dan 8 digit hex di depan adalah waktu pembuatan dalam detik epoch. Konversi dari W3C bersifat mekanis:
const traceId = '4bf92f3577b34da6a3ce929d0e0e4736';
const epochHex = traceId.slice(0, 8); // 4bf92f35
const epochSeconds = parseInt(epochHex, 16); // 1274621749
const xrayId = `1-${epochHex}-${traceId.slice(8)}`;
// 1-4bf92f35-77b34da6a3ce929d0e0e4736
// X-Amzn-Trace-Id: Root=1-4bf92f35-77b34da6a3ce929d0e0e4736;Parent=00f067aa0ba902b7;Sampled=1
new Date(epochSeconds * 1000).toISOString(); // 2010-05-23T13:35:49.000Z
Perhatikan tanggalnya. Header contoh dari spesifikasi terbaca sebagai Mei 2010, yang jelas omong kosong, dan justru itulah intinya. trace-id W3C tidak memuat timestamp apa pun. Enam belas byte acak tetap menghasilkan epoch yang tampak masuk akal begitu Anda membaca empat byte pertamanya sebagai satu angka, padahal angka itu tidak berarti apa-apa kecuali pengenalnya memang benar-benar lahir di X-Ray. Membaca waktu dari trace-id sembarangan sama saja dengan membaca angka acak lalu memercayainya.
Kalau ID-nya memang berasal dari X-Ray, konversinya berguna: masukkan delapan digit hex itu ke konverter Unix timestamp untuk mendapat tanggal yang terbaca, dan panduan epoch membahas jebakan detik-versus-milidetik serta zona waktu yang menyusul setelahnya.
B3: garis keturunan Zipkin
B3 datang dari Zipkin dan masih Anda temui di service mesh generasi lama. Bentuk header tunggalnya adalah traceId-spanId-sampled, dengan bidang sampled berisi 1 atau 0 alih-alih satu byte hex, sehingga bit random-trace-id dari Level 2 tidak punya tempat dan hilang begitu saja dalam penerjemahan. Bentuk multi-header memecah nilai yang sama ke X-B3-TraceId, X-B3-SpanId, dan X-B3-Sampled.
Kerutan historisnya ada pada lebar. Trace ID B3 boleh 64 bit, artinya 16 digit hex alih-alih 32. Mengonversi ID B3 64 bit ke W3C berarti menambal nol di sebelah kiri sampai genap 32 digit, dan mengonversi baliknya berarti memutuskan apakah akan memotong. Menambal di kiri itu aman; memotong tidak, karena dua trace yang hanya berbeda di byte atasnya akan luruh menjadi satu.
Di Mana traceparent Hilang di Produksi
Semua di atas mengandaikan headernya tiba. Sering kali tidak. Ada empat tempat yang paling sering memakannya.
Browser membuangnya pada panggilan lintas origin
Gejala: trace frontend ada, trace backend ada, dan tidak ada yang menghubungkan keduanya. Atau request lintas origin gagal total dengan error CORS.
Penyebab: traceparent adalah header kustom, jadi menambahkannya membuat request tidak lagi tergolong sederhana dan memicu preflight OPTIONS. Kalau respons preflight dari server tidak mencantumkan header itu di Access-Control-Allow-Headers, browser memblokir request yang sesungguhnya. Terpisah dari itu, instrumentasi browser milik OpenTelemetry menolak menyuntikkan header trace ke request lintas origin kecuali Anda memberi tahu origin mana saja yang diizinkan.
Perbaikan: di sisi server, kembalikan Access-Control-Allow-Headers: traceparent, tracestate untuk preflight. Di SDK browser, setel propagateTraceHeaderCorsUrls ke pola yang cocok dengan origin API Anda. Keduanya perlu; salah satu saja meninggalkan gejala yang sama. Preflight yang balik dengan status tak terduga layak Anda cocokkan dulu dengan contekan kode status HTTP sebelum menuduh headernya bermasalah.
Proxy, WAF, dan load balancer memangkas header yang tidak dikenal
Gejala: headernya ada ketika Anda curl langsung ke layanan, dan hilang ketika request yang sama lewat gateway.
Penyebab: penerusan berbasis allowlist. Banyak konfigurasi proxy, rule set WAF, dan load balancer terkelola hanya meneruskan header yang mereka kenali, dan traceparent tidak ada di daftar bawaan. Sebagian mesh bahkan menulis ulang headernya, membuat trace-id sendiri dan membuang milik Anda.
Perbaikan: lakukan bisect dengan echo server tadi, taruh di belakang tiap hop bergantian dan lihat lapisan mana yang membuang headernya. Setelah itu izinkan traceparent dan tracestate secara eksplisit di aturan penerusan lapisan tersebut. Kalau proxy-nya nginx, ingat bahwa blok yang menangani sebuah rute menentukan header mana yang ia teruskan, dan blok mana yang menangani sebuah rute tidak selalu blok yang Anda kira; aturan prioritas location Nginx menjelaskan kenapa konfigurasi header bisa terlihat diabaikan sepenuhnya.
Antrean pesan tidak punya header HTTP
Gejala: trace berhenti tepat saat sebuah request berubah menjadi background job.
Penyebab: tidak ada request HTTP yang melintasi batas itu, jadi tidak ada tumpangan untuk menyebarkan headernya. Kafka punya record header, SQS punya message attribute, dan instrumentasi HTTP tidak mengisi keduanya untuk Anda.
Perbaikan: suntikkan konteksnya ke dalam pesan di sisi producer dan tarik kembali di sisi consumer. Setiap SDK OpenTelemetry menyediakan inject dan extract persis untuk keperluan ini, dan format kabelnya tetap string W3C yang sama; yang berubah hanya wadahnya, dari peta header HTTP menjadi metadata pesan. Dokumentasi propagator OpenTelemetry membahas antarmuka carrier untuk tiap bahasa.
Besar-kecil huruf, dan apa yang sebenarnya dikecilkan HTTP/2
Gejala: perdebatan di code review soal apakah Traceparent boleh dipakai.
Penyebab: dua aturan terpisah tercampur menjadi satu. Nama header di HTTP/1.1 tidak membedakan huruf besar-kecil, dan HTTP/2 mengharuskan nama itu dikodekan huruf kecil di kabel. Itu soal nama. Terpisah dari itu, heksadesimal di nilai header wajib huruf kecil, karena begitulah tata bahasa W3C menetapkannya, dan tidak ada versi protokol yang akan membereskannya untuk Anda.
Perbaikan: kirim namanya sebagai traceparent dan jangan pernah membesarkan huruf nilainya. Gateway yang menormalkan nama header tidak akan menormalkan digit hex Anda, dan trace-id huruf besar melenggang mulus melewati semua lapisan transport sebelum akhirnya ditolak aplikasi yang mem-parse-nya.
Haruskah Anda Memercayai traceparent yang Masuk?
traceparent yang datang dari internet publik adalah masukan yang dikendalikan pengguna: sebuah string yang dipilih klien anonim, dan kebanyakan layanan menerimanya tanpa berpikir dua kali.
Risiko yang paling gampang dipahami adalah penyambungan trace (trace splicing): penyerang yang mengirimkan trace-id hasil pengamatan di tempat lain akan membuat request-nya terjahit ke dalam trace yang sudah ada, sehingga grafiknya tercemar dan waktu internal Anda bisa terbaca siapa pun yang bisa membuka trace itu. Yang lebih mahal adalah pembakaran kuota: menuliskan 01 secara hard-code memaksa sampling pada setiap request, dan banjir kecil pun berubah menjadi tagihan ingest yang sangat besar atau, lebih buruk, menendang keluar trace yang justru Anda butuhkan. Lalu ada korelasi lintas tenant: memakai ulang satu trace-id untuk request dari tenant berbeda menautkan catatan-catatan yang kemudian diperlakukan perkakas Anda sebagai satu operasi logis.
Sikap pragmatisnya: terima di edge, tetapi jangan percaya. Validasi tata bahasanya dan tolak header yang cacat alih-alih meneruskannya ke dalam. Untuk trafik tanpa autentikasi, jalankan ulang keputusan sampling Anda sendiri daripada menuruti flag yang masuk, supaya tidak ada klien luar yang bisa memaku sampler Anda ke posisi “selalu rekam”. Untuk trafik yang terautentikasi, menuruti keputusan pemanggil biasanya aman, karena Anda tahu siapa mereka.
Dan perlakukan trace-id sebagai sesuatu yang publik. Ia bukan rahasia dan tidak pernah menjadi rahasia: ia muncul di log, di halaman error, di header respons, dan di tangkapan layar yang orang tempelkan ke tiket dukungan. Jangan pernah menyisipkan user ID, nama tenant, atau apa pun yang bermakna ke dalamnya, dan jangan pernah memakainya sebagai kunci otorisasi. Ia pengenal korelasi, tidak lebih dari itu.
FAQ
Apa bedanya traceparent dan tracestate?
traceparent membawa identitas yang sudah dibakukan (trace-id, parent-id, dan flag sampling), dan setiap implementasi wajib memahaminya. tracestate membawa state khas vendor yang diteruskan implementasi asing tanpa perubahan. Keduanya saling terkait: ketika traceparent tidak valid, spesifikasi mewajibkan tracestate ikut diabaikan.
Kenapa trace saya mulai dari awal di tengah rantai panggilan?
Trace mulai dari awal di tengah rantai hampir selalu karena satu hop menerima header yang gagal memenuhi tata bahasa, membuangnya, lalu membuat trace-id baru. Hex huruf besar, trace-id nol semua, dan header yang panjangnya bukan tepat 55 karakter sama-sama menyebabkan ini. Kalau headernya sudah rapi, tersangka berikutnya adalah proxy yang memangkasnya dan preflight lintas origin yang gagal.
Apakah saya perlu mengonfigurasi CORS untuk mengirim traceparent dari browser?
Ya, konfigurasi CORS memang diperlukan. traceparent adalah header kustom, jadi request-nya menjadi tidak sederhana dan memicu preflight; server wajib mencantumkan traceparent di Access-Control-Allow-Headers. Instrumentasi browser OpenTelemetry juga butuh propagateTraceHeaderCorsUrls, karena secara bawaan ia tidak menyuntikkan header trace lintas origin.
Bagaimana cara menyebarkan trace context lewat Kafka atau SQS?
Tulis nilai traceparent ke record header Kafka atau message attribute SQS di sisi producer, lalu baca kembali di sisi consumer untuk memulihkan konteksnya. SDK OpenTelemetry menyediakan inject dan extract untuk ini di semua bahasa. Formatnya tidak berubah; yang berbeda hanya wadahnya, bukan lagi peta header HTTP.
Apakah trace ID aman ditampilkan di log atau respons?
Ya, trace ID aman ditampilkan. Ia pengenal acak tanpa identitas tersemat dan tanpa daya otorisasi. Ia memang mengorelasikan catatan antar sistem, jadi jangan pernah menyisipkan user ID atau nama tenant ke dalamnya, dan jangan pernah menerimanya sebagai bukti apa pun. Perlakukan sebagai kunci korelasi publik dan ia aman untuk dicatat, dikembalikan, maupun dibagikan.
Siapa yang membuat header traceparent?
Header traceparent dibuat layanan pertama yang menangani request tanpa header tersebut, biasanya sebuah edge proxy, API gateway, atau SDK browser. Layanan itu menjadi root trace: ia membuat trace-id, membuka span pertama, dan mengambil keputusan sampling. Semua hop sesudahnya hanya menulis ulang parent-id.
Apakah header traceparent wajib?
Header traceparent tidak wajib. Di level protokol sifatnya opsional, dan request tanpa header ini tetap sah: layanan penerima cukup menjadi root bagi trace yang baru. Wajibnya hanya dalam arti praktis, karena tanpa header itu pekerjaan yang melintasi batas antarlayanan tidak bisa dirangkai menjadi satu trace.
Apakah traceparent menambah overhead yang terukur?
Tidak signifikan. Sebuah traceparent berukuran 55 byte, dan tracestate biasanya menambah beberapa ratus byte lagi, jumlah yang tak berarti dibanding TLS handshake atau payload sungguhan mana pun. Biaya tracing yang sebenarnya ada pada pengeksporan dan penyimpanan span yang ter-sample, bukan pada membawa header distributed tracing di kabel.