traceparent Başlığı: W3C Trace Context Kapsamlı Rehber
traceparent başlığı, dağıtık izleme başlıkları için W3C standardıdır: bir isteğin kimliğini dokunduğu her servise taşıyan tek satırlık bir ASCII dizisi. Güncel sürümde uzunluğu tam olarak 55 karakterdir ve tirelerle ayrılmış dört alandan oluşur:
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)
Bu alanların ikisi, istek ilerledikçe farklı davranır. trace-id her durakta aynı kalır: kenar proxy’sinden son veritabanı çağrısına kadar isteğin adıdır. parent-id ise her durakta değişir, çünkü isteği değil sizi çağıran span’i adlandırır. “İzlerim yanlış görünüyor” başlıklı kayıtların hatırı sayılır bir bölümü, tek başına bu ikisinin karıştırılmasından çıkar.
Anatomi bu kadar. Geriye alan tablosunun size söyleyemediği kısım kalıyor: bir başlığı geçersiz kılan nedir, kurallara uyan bir alıcı böyle bir başlık aldığında ne yapar ve her ikisi de izlemeyi desteklediğini söyleyen iki servis arasında başlık nerede sessizce kaybolur. Elinizde gerçek bir başlık varsa onu ücretsiz traceparent çözücüsüne yapıştırıp okumaya devam edin; araç alanları ayırır, bayrak baytını bit bit açar ve bozuk bir başlığın hangi kurala takıldığını adıyla söyler.
traceparent başlığına hızlı bakış
traceparent başlığı, dağıtık bir izi servisler arasında taşıyan tek bir HTTP başlığıdır. Tirelerle ayrılmış dört onaltılık alan tutar (version, trace-id, parent-id ve trace-flags) ve güncel sürümde uzunluğu tam olarak 55 karakterdir. trace-id isteğin tamamını adlandırır; parent-id ise sizi çağıran span’i gösterir.
| Alan | Hex basamağı | Bayt | Neyi tanımlar | Her durakta değişir mi? |
|---|---|---|---|---|
version | 2 | 1 | Geri kalanın hangi biçime uyduğunu. Bugün her zaman 00 | Hayır |
trace-id | 32 | 16 | İsteğin tamamını, baştan sona | Hayır |
parent-id | 16 | 8 | Çağıran span’i (sizi çağıranın span ID’si) | Evet |
trace-flags | 2 | 1 | 8 bitlik bir alan; 0. bit sampled | Nadiren |
Bu 52 hex basamağına üç tire eklerseniz 55 karakter elde edersiniz. Bu sayıyı ezberlemeye değer: 00 sürümlü ve başka uzunlukta olan her başlık geçersizdir, üstelik uzunluk gözle en hızlı denetlenebilecek şeydir.
Başlıktaki her şey küçük harfli onaltılıktır. “Onaltılık, büyük/küçük harften bağımsız” değil; küçük harf. W3C Trace Context tavsiye belgesindeki dilbilgisi yalnızca 0-9 ve a-f kabul eder, başka hiçbir şeyi; büyük harfle yazılmış, değeri kusursuz doğru bir trace ID’nin akış aşağısında yine de çöpe atılmasının nedeni budur.
Alan alan inceleme
Her alanın kendi genişliği, kendi geçersiz değerleri ve kendi bozulma biçimi var.
version — neden her zaman “sadece 00” değil
Bugün sürüm baytı 00 ve bir süre daha 00 kalacak. Ama ff açıkça yasaklanmıştır: belirtim onu geçersiz bir değer olarak ayırır, dolayısıyla ff ile başlayan bir başlık, devamında ne yazarsa yazsın daha kapıdan girerken ölüdür.
İlginç olan kural, hiç görmediğiniz sürümlerle ilgili olanı. if (version !== '00') reject() yapan bir ayrıştırıcı yanlıştır ve pahalıya patlayan bir yanlıştır. Belirtim, sürüm daha yüksekse ve başlık en az bilinen biçim kadar uzunsa alıcıların ayrıştırmayı denemesini ister: tanıdığınız alanları okuyun, sondaki fazla veriyi hoş görün ve yolunuza devam edin. Bunun yerine reddetmek, akış yukarısında biri sürüm yükselttiği anda servisinizin izin durduğu ve yenisinin başladığı sınır hâline gelmesi demektir.
// Wrong: makes your service the place traces go to die
if (version !== '00') throw new Error('bad traceparent');
// Right: parse the prefix you understand
if (version !== '00' && header.length >= 55) {
// read version, trace-id, parent-id, trace-flags; ignore the rest
}
trace-id — 16 bayt, tüm isteğin kimliği
Otuz iki küçük harfli hex basamağı; izin ömrü boyunca sabit kalır. Başlangıçta hangi servis ürettiyse üretsin, her durak onu değiştirmeden ileri kopyalar. Gözlemlenebilirlik arka ucunuzda bir iz aradığınızda yapıştırdığınız karakter dizisi budur.
Değerini iki kural yönetir. 32 hex basamağı uzunluğunda olmalıdır ve tamamı sıfır olmamalıdır. Belirtim 00000000000000000000000000000000 değerini adıyla geçersiz ilan eder ve alıcıların başlığın tamamını yok saymasını şart koşar; “henüz verisi olmayan bir iz” diye okunacak bir şey değildir. Pratikte tamamı sıfır bir trace ID, hiç başlatılmamış bir SDK’yı ya da iletecek gerçek bir bağlamı olmadığı için yer tutucu koyan bir ara katmanı gösterir.
Bir trace-id 128 bittir, yani bir UUID ile aynı genişliktedir, ama UUID değildir. Sürüm biti yok, varyant biti yok, tire yok, hiçbir türden yapı yok: on altı adet mat bayt. İçinden bir v4 ayrıştıramazsınız; tireleri sökülmüş bir UUID de kendiliğinden geçerli bir trace-id sayılmaz, çünkü sürüm ve varyant nibble’ları rastgeleliğini tekdüze olmaktan çıkarır. Bir UUID’nin o 128 bitin içinde neyi ayırdığını görmek isterseniz bir UUID’nin gerçekte neyi kodladığı yerleşimi adım adım anlatıyor; UUID üretici de sürüm ve varyant bitlerini yerinde gösteriyor.
parent-id — 8 bayt, sizi çağıran span
On altı hex basamağı; her durakta yeniden yazılır. Ad, alanın hak ettiğinden fazla kafa karışıklığı yaratır: W3C belirtimi ona parent-id der, OpenTelemetry aynı 8 bayta span ID der ve ikisi aynı şeyin iki yönden görünüşüdür. Sizin servisinizin bakış açısından o bir ebeveyndir; çağıranın bakış açısından ise dışarı giden istek için az önce oluşturduğu span’in kimliğidir.
Yani A servisi B servisini çağırdığında, A parent-id alanına kendi span ID’sini yazar. B ardından bir alt span oluşturur ve C’yi çağırdığında oraya bu kez B’nin span ID’sini koyar. trace-id baştan sona el değmeden kalır. Yayılım algoritmasının tamamı bundan ibaret.
Tamamı sıfır parent-id’ler de trace-id’lerle aynı gerekçeyle geçersizdir: 0000000000000000, çağıranın gerçek bir span vermediği anlamına gelir ve başlık yarım yamalak kabul edilmek yerine atılmalıdır.
trace-flags — boolean gibi görünür, aslında sekiz bittir
Göreceğiniz başlıkların neredeyse hepsi 01 ile biter, dolayısıyla alanı evet/hayır olarak okumak doğal geliyor. Oysa alan bir bayttır ve bitleri şöyle atanmıştır:
- 0. bit, maske
0x01—sampled - 1. bit, maske
0x02—random-trace-id, Trace Context Level 2 ile eklendi - 2–7. bitler — ayrılmıştır; alırken yok sayın, dışarı giden isteklerde sıfırlayın
Kombinasyonlar şöyle çözülür:
| Hex | İkilik | sampled | random-trace-id | flags === 0x01 tutar mı? |
|---|---|---|---|---|
00 | 00000000 | false | false | false |
01 | 00000001 | true | false | true |
02 | 00000010 | false | true | false |
03 | 00000011 | true | true | false ← hata burada |
Son satırı bir daha okuyun. Bayrakları 03 olan bir iz örneklenmiştir. Baytın tamamını 01 ile karşılaştıran her kod onu sessizce örneklenmemiş sayar ve bunu yalnızca Level 2 bayrağının işaretli olduğu trafik alt kümesi için yapar. Olabilecek en kötü arıza biçimi budur, çünkü bir ayrıştırma hatası gibi değil, örnekleme oranı sorunu gibi görünür.
const flags = parseInt(traceFlags, 16);
// Wrong: treats a bit field as an enumeration
const sampled = traceFlags === '01';
// Right
const sampled = (flags & 0x01) !== 0;
const randomTraceId = (flags & 0x02) !== 0;
random-trace-id aslında neyi taahhüt eder? trace-id’nin en sağdaki en az 7 baytının tekdüze biçimde rastgele üretildiğini. Tutarlı örneklemeyi düşünene kadar bu akademik bir ayrıntı gibi durur: akış aşağısındaki bir sistem izlerin %1’ini saklamak istiyorsa ve hangi %1 olduğu konusunda her servisin bağımsız olarak aynı sonuca varması gerekiyorsa, ID’yi önce özetlemek yerine doğrudan o baytların modunu alabilir. Bayrak, akış yukarısının bunun güvenli olduğuna dair verdiği sözdür.
Bir traceparent’i geçersiz kılan nedir
Sağlayıcı bilgi tabanlarının çoğu bu bölümü atlar, oysa insanların aradığı cevap burada duruyor. 00 sürümlü bir başlık için ret kümesinin tamamı şöyle:
| Belirti | Kural | Sonuç |
|---|---|---|
00-4BF92F35...-01 | Dilbilgisi yalnızca küçük harfli hex kabul eder | Geçersiz; değer doğru olsa da başlık reddedilir |
ff-... | ff sürümü belirtim tarafından yasaklanmıştır | Geçersiz |
trace-id 00000000000000000000000000000000 | Tamamı sıfır trace-id, adı konmuş geçersiz bir değerdir | Geçersiz |
parent-id 0000000000000000 | Tamamı sıfır parent-id, adı konmuş geçersiz bir değerdir | Geçersiz |
| trace-id 32 hex basamağı değil | Sabit genişlik | Geçersiz |
| parent-id 16 hex basamağı değil | Sabit genişlik | Geçersiz |
| trace-flags 2 hex basamağı değil | Sabit genişlik | Geçersiz |
Başlık tam olarak 55 karakter değil, sürüm 00 | Sondaki fazla veri yalnızca gelecekteki bir sürümde meşrudur | Geçersiz |
0-9a-f ve tireler dışında herhangi bir karakter | Onaltılık değil | Geçersiz |
Bu listenin pratikteki sonucu şu:
Kurallara uyan bir alıcı, geçersiz bir traceparent başlığını onarmaz ve ileri aktarmaz. Başlığı atar ve yeni üretilmiş bir trace-id ile sıfırdan bir iz başlatır.
Yani ekranınızda bozuk bir iz görmezsiniz. Birbirinden kopuk iki kısa iz görürsünüz: biri bozuk başlığı gönderen serviste birdenbire biter, diğeri onu alan serviste yoktan var olmuş gibi başlar. Hiçbir yerde hata olarak işaretlenen bir şey yoktur ve iki iz de tek başına bakıldığında sağlıklı görünür. Cevap çoğu zaman bir ara katmanın hex karakter dizisini büyük harfe çevirmesi ya da elle kurulan bir başlığın 54 karakter çıkmasıdır; insanlar bu arada aradaki eksik halkayı aramaya öğleden sonralarını harcar.
Uzunluk ve harf durumu, ekrana bakarak göremeyeceğiniz iki arıza biçimidir. Başlığı traceparent çözücüsüne yapıştırın; size basamak saydırmak yerine hangi kuralın çiğnendiğini doğrudan söyler.
tracestate: herkesin yanlış anladığı eşlikçi başlık
traceparent standart kimliği taşır. tracestate başlığı ise her sağlayıcının bunun yanına eklemek istediği her şeyi, virgülle ayrılmış key=value üyeleri olarak taşır:
tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE
Bir anahtarı tanımayan uygulama onu el değmeden iletmek zorundadır. Tasarım hedefinin tamamı bu: sağlayıcılar, her durağın anlamasına gerek kalmadan standart bir izin üzerine kendi özel durumlarını iliştirebilir.
Yine de dilbilgisinin dişleri var ve kurallarından üçü gerçek üretim belirtilerini açıklıyor.
32 liste üyesi katı bir tavandır. Bu bir öneri değil, dilbilgisinin kendisi: list = list-member 0*31( OWS "," OWS list-member ). 33 üyeli bir tracestate geçersiz bir başlıktır, girdisi bir fazla olan bir tracestate değil; alıcılar onu hiç ayrıştırmadan atabilir. Aksi hâlde sihir gibi görünen bir belirtinin cevabı budur: kenarda var olan, iki durak sonra hâlâ var olan, beşinci durakta ise tamamen kaybolan sağlayıcı verisi. Her durak kendi üyesini ekliyordu, liste 32’yi aştı ve o andan itibaren başlık kırpılmak yerine bütünüyle düşürüldü.
Değerler 1 ile 256 karakter arasındadır ve asla boş olamaz. Değer üretimi zorunlu bir boşluk-dışı karakterle biter, dolayısıyla vendor= yazmak sözdizimi hatasıdır; “değeri olmayan bir anahtar” diye okunmaz. Yalnızca yazdırılabilir ASCII kullanılır ve değerin içinde asla virgül veya eşittir işareti bulunmaz.
Anahtar dilbilgisi Level 1 ile Level 2 arasında değişti. Level 1 anahtarları tenant@vendor üretimi üzerinden tanımlıyordu ve orada @ yapısal bir ayraçtı. Level 2 bunun yerine düz bir karakter sınıfı getirdi: anahtar küçük harf veya rakamla başlar, ardından a-z, 0-9, _, -, *, / ve @ ile devam eder. Level 2’de @ sıradan bir karakterdir, anahtarlar rakamla başlayabilir ve a@b@c, Level 1 üretiminin reddedeceği hâlde tamamen meşru bir anahtardır. Level 1’e göre doğrulama yapan bir proxy’niz ve Level 2 anahtarları üreten bir servisiniz varsa, bir taraf diğerinin reddettiğini kabul eder ve başlık tam olarak tek bir durakta kaybolur.
Bilmeye değer iki kural daha var. Yinelenen anahtarlar doğrudan geçersizdir. İkincisi, traceparent’in parent-id’sini değiştirdiğinizde kendi tracestate girdinizi listenin başına taşımanız gerekir, çünkü liste en yeniden en eskiye doğru sıralanır. Başa taşıma adımını atlamak, bayatlamış sağlayıcı durumunu okuyanın güncel sanacağı bir yerde bırakır.
Bir de rahatlatıcı bir kural var: boş liste üyeleri meşrudur. Bir ara kutu bir girdiyi kaldırdığında çoğu zaman virgülü yerinde bırakır ve ortaya rojo=1,,congo=2 çıkar. Belirtim buna açıkça izin verir, dolayısıyla bir ayrıştırıcı başlığı bozuk ilan etmek yerine boş üyeyi atıp devam etmelidir. Çözücüdeki tracestate görünümü her üyeyi üye bazında doğrulamayla ve 32 üyelik sınıra karşı anlık sayaçla listeler; bu genellikle virgül saymaktan hızlıdır.
Dağıtık izleme başlıkları nasıl yol alır: tek istek, dört durak
Tek bir isteği bir kenar proxy’si, bir API servisi ve akış aşağısındaki iki servis boyunca izleyin:
Client
│ (no traceparent — the edge is the root)
▼
Edge proxy generates trace-id 4bf9…4736, span 00f0…02b7
│ traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
▼
API service reads it, creates span a1b2c3d4e5f60718
│ traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-a1b2c3d4e5f60718-01
▼
Orders service reads it, creates span 9f8e7d6c5b4a3928
│ traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-9f8e7d6c5b4a3928-01
▼
Inventory service
Her durak aynı üç işi yapar: gelen başlığı okur, dışarı giden her çağrı için parent-id’yi kendi span ID’siyle değiştirir ve trace-id ile bayrakları değiştirmeden iletir. Hiç gelen başlık yoksa (yukarıdaki istemcide olduğu gibi) alıcı servis kök olur: bir trace-id üretir ve akış aşağısındaki her şey için örnekleme kararını verir.
Bir zinciri baştan sona sınamak için başlığı elle enjekte edebilirsiniz:
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
Üretimden yakaladığınız bir başlığı staging ortamına karşı yeniden oynatın; aynı trace-id’nin arka ucunuzda belirdiğini görürsünüz. Kimlik doğrulama ya da gövde ekliyorsanız cURL Komut Oluşturucu bayrakları sizin için birleştirir; curl kopya kâğıdı da hata ayıklarken isteyeceğiniz başlık ve ayrıntılı çıktı seçeneklerini kapsıyor.
Gönderdiğinizi sandığınız şeyi değil, bir servisin gerçekte ne aldığını görmek için tek seferlik bir echo sunucusu çalıştırın ve bir durağı ona yönlendirin:
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
Ardından curl -H 'traceparent: …' http://127.0.0.1:8080/ çalıştırın ve diğer uçtan ne çıktığını okuyun. “Proxy başlığımı yiyor” araştırmalarının yarısı burada biter.
trace-flags sampled: akış yukarısından gelen bir karar, garanti değil
trace-flags içindeki sampled bitinin 1 olması, akış yukarısındaki servisin bu izi kaydetmeye karar verdiği anlamına gelir. Verinin arka ucunuza ulaştığına dair bir söz vermez.
Baştan örnekleme (head-based sampling) bu kararı kökte, henüz hiçbir şey olmadan verir ve aşağı doğru yayar: ucuzdur, servisler arasında tutarlıdır ve kördür, çünkü isteğin birazdan başarısız olacağını bilemez. Sondan örnekleme (tail-based sampling) span’leri iz tamamlanana kadar tamponda tutar ve kararı ondan sonra verir; böylece hata içeren her izi saklayabilir, karşılığında span’leri bellekte tutmak ve her servisin span’lerinin aynı toplayıcıya düşmesini sağlamak gerekir.
Sondan örneklemede bir iz her durakta 01 bayrağıyla gelip yine de en sonunda düşürülebilir. Hız sınırları ve dışa aktarım kotaları da onu düşürebilir. Yani kenarda 01 görüp arayüzde iz görmemek zorunlu olarak bir yayılım hatası değildir; başlıkları kurcalamaya girişmeden önce toplayıcının kendi düşürme metriklerine bakın.
Ters durum günlük hayatta daha çok işinize yarar. Gelen bayraklar 00 ise çağıran kendi örnekleyicisini çalıştırmış ve kaydetmemeyi seçmiştir. Servisinizde yanlış yapılandırılmış bir şey yoktur ve kendi örnekleyicinizi denetlemek boşa harcanan zamandır; bakılacak yer, akış yukarısındaki hangi servisin örneklememeye karar verdiğidir.
Yayılım biçimleri arasında dönüştürme
W3C Trace Context kazandı, ama pek çok sistem hâlâ daha eski bir dil konuşuyor ve gateway’ler aralarında çeviri yapıyor. Aynı traceparent örneği dört biçimde şöyle yazılır:
| Biçim | Başlık(lar) | Örneğimizdeki değer |
|---|---|---|
| W3C | traceparent | 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 |
| B3 tek başlık | b3 | 4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1 |
| B3 çok başlık | X-B3-TraceId, X-B3-SpanId, X-B3-Sampled | 4bf92f3577b34da6a3ce929d0e0e4736, 00f067aa0ba902b7, 1 |
| Datadog | x-datadog-trace-id, x-datadog-parent-id, _dd.p.tid etiketi | 11803532876627986230, 67667974448284343, 4bf92f3577b34da6 |
| AWS X-Ray | X-Amzn-Trace-Id | Root=1-4bf92f35-77b34da6a3ce929d0e0e4736;Parent=00f067aa0ba902b7;Sampled=1 |
Datadog: yüksek/düşük 64 bit ayrımı
Datadog’un tanımlayıcıları 128 bitlik trace ID’lerden daha eskidir ve dönüşümlerin çoğu bu uyumluluk katmanında bozulur. x-datadog-trace-id, alt 64 biti onluk bir karakter dizisi olarak taşır. Üst 64 bit ise ayrı olarak, onaltılık biçimde, _dd.p.tid etiketinde yol alır; bu etiketin kendisi de x-datadog-tags başlığında taşınır.
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
Klasik hata, 128 bitin tamamını tek bir onluk sayıya çevirmektir:
BigInt('0x' + traceId).toString(10);
// 100985939111033328018442752961257817910 — matches nothing in the UI
O değer aritmetik olarak yanlış değildir. Yanlış niceliğin doğru onluk gösterimidir; incelemeden geçmesinin ve sonra sessizce sıfır izle eşleşmesinin nedeni de bu.
İkinci tuzak sayısal hassasiyettir. 64 bitlik bir tanımlayıcı, değeri 9007199254740991 olan Number.MAX_SAFE_INTEGER sınırını aşar; dolayısıyla bir trace ID’nin JavaScript sayısına dönüşmesine izin veren her kod yolu, alt basamaklarını bozar. Trace ID’leri karakter dizisi olarak tutun ve BigInt’e yalnızca aritmetik yapmak zorunda olduğunuzda başvurun; JSON içinde tırnaksız gelen bir ID, siz görene kadar çoktan hasar görmüştür.
AWS X-Ray: orada olmayan zaman damgası
Bir X-Ray trace ID’si 1-{8 hex}-{24 hex} biçimindedir ve baştaki 8 hex basamağı, oluşturulma zamanını epoch saniyesi olarak taşır. W3C’den dönüştürmek mekanik bir iştir:
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
Şu tarihe bakın. Belirtimdeki örnek başlık Mayıs 2010’a çözülüyor; apaçık bir saçmalık, çünkü bir W3C trace-id’si zaman damgası içermez. On altı rastgele baytın ilk dördünü tek bir sayı gibi okuduğunuzda, gayet inandırıcı görünen bir epoch değeri çıkar; tanımlayıcı gerçekten X-Ray’den gelmediği sürece bu sayı hiçbir şey ifade etmez. Gelişigüzel bir trace-id’den zaman çözmek, rastgele bir sayıyı okuyup ona inanmaktır.
ID gerçekten X-Ray’den geldiyse dönüşüm işe yarar: o sekiz hex basamağını Unix Zaman Damgası Dönüştürücü aracına bırakıp okunabilir bir tarih alın; Unix timestamp rehberi de ardından gelen saniye-milisaniye ve saat dilimi tuzaklarını anlatıyor.
B3: Zipkin soyağacı
B3, Zipkin’den geldi ve eski servis ağlarında karşınıza çıkan biçimdir. Tek başlıklı biçimi traceId-spanId-sampled şeklindedir; buradaki sampled alanı bir hex bayt değil, 1 ya da 0 olur. Level 2’nin random-trace-id bitine böylece yer kalmaz ve o bit çeviride öylece kaybolur. Çok başlıklı biçim aynı değerleri X-B3-TraceId, X-B3-SpanId ve X-B3-Sampled arasında böler.
Tarihsel pürüz genişliktir. B3 trace ID’leri 64 bit olabilir, yani 32 yerine 16 hex basamağı. 64 bitlik bir B3 kimliğini W3C’ye çevirmek, 32 basamağa ulaşmak için soldan sıfırla doldurmak demektir; geri çevirmek ise kırpıp kırpmamaya karar vermek. Soldan doldurma güvenlidir, kırpma değil; çünkü yalnızca üst baytlarında farklılaşan iki iz tek bir ize çöker.
traceparent üretimde nerede kaybolur
Yukarıdaki her şey başlığın yerine ulaştığını varsayıyor. Çoğu zaman ulaşmaz. Kaybolduğu dört yer şunlar.
Tarayıcı, çapraz kaynaklı çağrılarda başlığı düşürür
Belirti: ön uç izleri var, arka uç izleri var, ama ikisini birbirine bağlayan hiçbir şey yok. Ya da çapraz kaynaklı istek doğrudan bir CORS hatasıyla başarısız oluyor.
Neden: traceparent özel bir başlıktır; eklemek isteği basit olmaktan çıkarır ve bir ön kontrol (OPTIONS) isteği tetikler. Sunucunun ön kontrol yanıtı başlığı Access-Control-Allow-Headers içinde listelemiyorsa tarayıcı asıl isteği engeller. Bundan bağımsız olarak, OpenTelemetry’nin tarayıcı enstrümantasyonu hangi kaynaklara izin verildiğini siz söylemedikçe çapraz kaynaklı isteklere iz başlığı eklemeyi reddeder.
Çözüm: sunucu tarafında ön kontrol için Access-Control-Allow-Headers: traceparent, tracestate döndürün. Tarayıcı SDK’sında propagateTraceHeaderCorsUrls değerini API kaynaklarınızla eşleşen bir desene ayarlayın. İkisi de gerekli; yalnızca biri sizi aynı belirtiyle baş başa bırakır. Beklenmedik bir durum koduyla dönen bir ön kontrolü, sorunun başlık olduğunu varsaymadan önce HTTP durum kodları rehberiyle karşılaştırmakta fayda var.
Proxy’ler, WAF’lar ve yük dengeleyiciler bilinmeyen başlıkları siler
Belirti: servise doğrudan curl attığınızda başlık yerinde, aynı istek gateway üzerinden geçtiğinde ortada yok.
Neden: allowlist’e dayalı iletim. Pek çok proxy yapılandırması, WAF kural kümesi ve yönetilen yük dengeleyici yalnızca tanıdığı başlıkları iletir ve traceparent varsayılan listede yoktur. Bazı servis ağları başlığı yeniden yazar: kendi trace-id’lerini üretip sizinkini atarlar.
Çözüm: az önceki echo sunucusuyla ikili arama yapın: onu sırayla her durağın arkasına koyun ve hangi katmanın başlığı düşürdüğünü görün. Ardından o katmanın iletim kurallarında traceparent ve tracestate başlıklarına açıkça izin verin. Proxy nginx ise, bir rotayı ele alan bloğun hangi başlıkların geçeceğine karar verdiğini ve o bloğun her zaman beklediğiniz blok olmadığını unutmayın; Nginx location önceliği kuralları, bir başlık yapılandırmasının neden tamamen yok sayılıyormuş gibi görünebileceğini açıklıyor.
Mesaj kuyruklarında HTTP başlığı yoktur
Belirti: bir istek arka plan işine dönüştüğü anda iz sona eriyor.
Neden: o sınırın ötesinde bir HTTP isteği yoktur, dolayısıyla başlığı taşıyacak bir şey de yoktur. Kafka’da kayıt başlıkları, SQS’te mesaj öznitelikleri var; ikisini de HTTP enstrümantasyonu sizin için doldurmaz.
Çözüm: bağlamı üretici tarafında mesajın içine enjekte edin, tüketici tarafında geri çıkarın. Her OpenTelemetry SDK’sı tam olarak bunun için inject ve extract sunar; hat üzerindeki biçim yine aynı W3C karakter dizisidir, yalnızca taşıyıcı bir HTTP başlık haritasından mesaj meta verisine dönüşür. OpenTelemetry yayıcı belgeleri taşıyıcı arayüzünü dil dil anlatıyor.
Harf durumu ve HTTP/2’nin gerçekte neyi küçük harfe çevirdiği
Belirti: kod incelemesinde Traceparent yazımının kabul edilebilir olup olmadığı konusunda kafa karışıklığı.
Neden: birbirinden ayrı iki kural tek bir kural sanılıyor. HTTP/1.1’de başlık adları büyük/küçük harften bağımsızdır ve HTTP/2 bunların hat üzerinde küçük harfle kodlanmasını şart koşar. Bu, adla ilgilidir. Bundan bağımsız olarak, başlık değerindeki onaltılık yazım küçük harf olmak zorundadır, çünkü W3C dilbilgisi böyle diyor; hiçbir protokol sürümü bunu sizin yerinize düzeltmez.
Çözüm: adı traceparent olarak gönderin ve değeri asla büyük harfe çevirmeyin. Başlık adlarını normalleştiren bir gateway sizin hex basamaklarınızı normalleştirmez; büyük harfli bir trace-id her taşıma katmanından rahatça geçer ve ancak onu en sonunda ayrıştıran uygulama tarafından reddedilir.
Gelen bir traceparent’e güvenmeli misiniz?
Halka açık internetten gelen bir traceparent, kullanıcı denetimindeki girdidir: anonim bir istemcinin seçtiği bir karakter dizisi. Çoğu servis onu ikinci kez düşünmeden kabul eder.
Bundan üç somut risk doğar. Birincisi, iz ekleme (trace splicing): başka bir yerde gözlemlediği bir trace-id’yi gönderen saldırgan, kendi isteğini mevcut bir ize diktirir; bu, grafiği kirletir ve o izi okuyabilen herkese dahili zamanlama bilgisini açabilir. İkincisi, kota yakma: 01 değerini sabit yazmak her istekte örneklemeyi zorlar ve mütevazı bir sel bile çok yüklü bir veri alım faturasına ya da daha kötüsü, gerçekten ihtiyaç duyduğunuz izlerin tahliye edilmesine dönüşür. Üçüncüsü, kiracılar arası ilişkilendirme: tek bir trace-id’yi farklı kiracıların isteklerinde yeniden kullanmak, araçlarınızın sonradan tek bir mantıksal işlem sayacağı kayıtları birbirine bağlar.
Pragmatik duruş, kenarda kabul etmek ama güvenmemektir. Dilbilgisini doğrulayın ve bozuk başlıkları içeri geçirmek yerine reddedin. Kimliği doğrulanmamış trafikte, gelen bayrağı olduğu gibi uygulamak yerine kendi örnekleme kararınızı yeniden çalıştırın; böylece hiçbir dış istemci örnekleyicinizi “her zaman kaydet” konumuna sabitleyemez. Kimliği doğrulanmış trafikte çağıranın kararına uymak genellikle sorun değildir, çünkü kim olduğunu biliyorsunuz.
Bir de trace-id’yi herkese açık kabul edin. Sır değildir ve hiç olmadı: kayıtlarda, hata sayfalarında, yanıt başlıklarında ve destek taleplerine yapıştırılan ekran görüntülerinde görünür. İçine asla bir kullanıcı kimliği, kiracı adı ya da anlamlı başka bir şey kodlamayın ve onu asla yetkilendirme anahtarı olarak kullanmayın. O bir korelasyon kimliğidir ve olması gereken tek şey de budur.
SSS
traceparent ile tracestate arasındaki fark nedir?
traceparent standartlaştırılmış kimliği taşır (trace-id, parent-id ve örnekleme bayrakları) ve her uygulama onu anlamak zorundadır. tracestate ise, tanımayan uygulamaların el değmeden ilettiği sağlayıcıya özgü durumu taşır. İkisi birbirine bağlıdır: traceparent geçersizse belirtim tracestate’in de yok sayılmasını şart koşar.
İzim çağrı zincirinin ortasında neden baştan başlıyor?
Bir iz zincirin ortasında baştan başlıyorsa, neredeyse her zaman bir durak dilbilgisine uymayan bir başlık almış, onu atmış ve yeni bir trace-id üretmiştir. Büyük harfli hex, tamamı sıfır bir trace-id ve tam olarak 55 karakter olmayan bir başlık; üçü de buna yol açar. Başlık kurallara uygunsa sıradaki şüpheliler, onu silen bir proxy ile başarısız olan çapraz kaynaklı bir ön kontroldür.
Tarayıcıdan traceparent göndermek için CORS yapılandırmam gerekir mi?
Evet, CORS yapılandırması gerekir. traceparent özel bir başlık olduğu için isteği basit olmaktan çıkarır ve bir ön kontrol tetikler; sunucunun traceparent başlığını Access-Control-Allow-Headers içinde listelemesi gerekir. Ayrıca OpenTelemetry’nin tarayıcı enstrümantasyonunda propagateTraceHeaderCorsUrls ayarını yapmanız gerekir, çünkü varsayılan olarak çapraz kaynaklı isteklere iz başlığı eklemez.
Kafka veya SQS üzerinden iz bağlamını nasıl yayarım?
Üretici tarafında traceparent değerini bir Kafka kayıt başlığına ya da bir SQS mesaj özniteliğine yazın, tüketici tarafında geri okuyup bağlamı yeniden kurun. OpenTelemetry SDK’ları bunun için her dilde inject ve extract sunar. Biçim değişmez; yalnızca taşıyıcı bir HTTP başlık haritasından farklıdır.
Bir trace ID’yi kayıtlarda veya yanıtlarda göstermek güvenli mi?
Evet, bir trace ID’yi göstermek güvenlidir. İçine gömülü bir kimlik ve yetkilendirme gücü olmayan rastgele bir tanımlayıcıdır. Sistemler arasında kayıtları ilişkilendirdiği doğru, bu yüzden içine asla bir kullanıcı kimliği ya da kiracı adı kodlamayın ve onu hiçbir şeyin kanıtı olarak kabul etmeyin. Herkese açık bir korelasyon anahtarı olarak ele alın; kayda yazmak, yanıtta döndürmek ve paylaşmak güvenlidir.
traceparent başlığını kim üretir?
traceparent başlığını, başlığı olmayan bir isteği ilk karşılayan servis üretir. Bu genellikle bir kenar proxy’si, bir API gateway’i ya da bir tarayıcı SDK’sıdır ve izin kökü olur: trace-id’yi üretir, ilk span’i oluşturur, örnekleme kararını verir. Ondan sonraki her durak yalnızca parent-id’yi yeniden yazar.
traceparent başlığı zorunlu mu?
Hayır. traceparent başlığı protokol düzeyinde isteğe bağlıdır ve başlığı taşımayan bir istek tamamen geçerlidir; onu alan servis yeni bir izin kökü olur. Zorunluluk yalnızca pratikte doğar: başlık olmadan bir servis sınırının iki yakasında yapılan iş tek bir izde birleşmez, her servis kendi kopuk izini üretir.
traceparent ölçülebilir bir ek yük getirir mi?
Kayda değer bir yük getirmez. Bir traceparent 55 bayttır ve bir tracestate genellikle birkaç yüz bayt daha ekler; bir TLS el sıkışmasının ya da herhangi bir gerçek payload’ın yanında ihmal edilebilir. İzlemenin asıl maliyeti, hat üzerinde dağıtık izleme başlıklarını taşımak değil, örneklenmiş span’leri dışa aktarmak ve saklamaktır.