性能監(jiān)控指南
本文檔介紹了 SDK 應(yīng)如何通過(guò)分布式跟蹤添加對(duì)性能監(jiān)控的支持。
- https://docs.sentry.io/product/performance/distributed-tracing/
這應(yīng)該提供 SDK 需要實(shí)現(xiàn)的 API 的概述,而不強(qiáng)制要求內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。
參考實(shí)現(xiàn):
- @sentry/tracing (JavaScript)
- https://github.com/getsentry/sentry-javascript/tree/master/packages/tracing
Python SDK
- https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/tracing.py
SDK 配置
通過(guò)設(shè)置兩個(gè)新的 SDK 配置選項(xiàng)之一來(lái)啟用跟蹤,tracesSampleRate 和 tracesSampler。如果未設(shè)置,則兩者都默認(rèn)為 undefined,從而選擇如何加入跟蹤。
tracesSampleRate
這應(yīng)該是介于 0.0 和 1.0(含)之間的 float/double,表示任何給定 transaction 將被發(fā)送到 Sentry 的百分比機(jī)會(huì)。因此,0.0 是 0% 的機(jī)會(huì)(不會(huì)發(fā)送),而 1.0 是 100% 的機(jī)會(huì)(都將發(fā)送)。此 rate 同樣適用于所有 transaction;換句話說(shuō),每個(gè) transaction 都應(yīng)該有相同的隨機(jī)機(jī)會(huì)以 sampled = true 結(jié)束,等于 tracesSampleRate。
tracesSampler
這應(yīng)該是一個(gè) callback,在 transaction 開(kāi)始時(shí)調(diào)用,它將被賦予一個(gè) samplingContext 對(duì)象,并且應(yīng)該返回一個(gè)介于 0.0 和 1.0 之間的采樣率_對(duì)于所討論的 transaction_。此采樣率的行為方式應(yīng)與上面的 tracesSampleRate 相同,不同之處在于它僅適用于新創(chuàng)建的 transaction,因此可以以不同的 rate 對(duì)不同的 transaction 進(jìn)行采樣。返回 0.0 應(yīng)該強(qiáng)制刪除 transaction(設(shè)置為 sampled = false),返回 1.0 應(yīng)該強(qiáng)制發(fā)送 transaction(設(shè)置 sampled = true)。
可選地,tracesSampler callback 也可以返回一個(gè)布爾值來(lái)強(qiáng)制進(jìn)行采樣決策(false 等同于 0.0,true 等同于 1.0)。如果返回兩種不同的數(shù)據(jù)類(lèi)型在實(shí)現(xiàn)語(yǔ)言中不是一個(gè)選項(xiàng),則可以安全地省略這種可能性。
maxSpans
由于 transaction payload 在攝取端強(qiáng)制執(zhí)行最大大小,因此 SDK 應(yīng)限制附加到事務(wù)的 span 數(shù)。這類(lèi)似于如何限制面包屑和其他任意大小的列表以防止意外誤用。如果在達(dá)到最大值后添加新的 span,SDK 應(yīng)刪除 span 并理想地使用內(nèi)部日志記錄來(lái)幫助調(diào)試。
maxSpans 應(yīng)該作為一個(gè)內(nèi)部的、不可配置的、默認(rèn)為 1000 的常量來(lái)實(shí)現(xiàn)。如果在給定的平臺(tái)中有理由,它可能會(huì)變得可配置。
maxSpans 限制還可以幫助避免永遠(yuǎn)不會(huì)完成的 transaction(在 span 打開(kāi)時(shí)保持 transaction 打開(kāi)的平臺(tái)中),防止 OOM 錯(cuò)誤,并通常避免降低應(yīng)用程序性能。
Event 變更
在撰寫(xiě)本文時(shí),transaction 是作為 Event 模型的擴(kuò)展實(shí)現(xiàn)的。
Transaction 的顯著特征是 type: "transaction"。
除此之外,Event 獲得了新的字段:spans、contexts.TraceContext。
新的 Span 和 Transaction 類(lèi)
在內(nèi)存中,span 構(gòu)建了一個(gè)定時(shí)操作的概念樹(shù)(conceptual tree)。我們稱整個(gè) span tree 為 transaction。有時(shí)我們使用術(shù)語(yǔ) "transaction" 來(lái)指代作為整棵樹(shù)的 span tree,有時(shí)特指樹(shù)的 root span。
通過(guò)網(wǎng)絡(luò),transaction 被序列化為 JSON 作為增強(qiáng)的 Event,并作為 envelope 發(fā)送。不同的 envelope 類(lèi)型用于優(yōu)化攝取(因此我們可以以不同于其他事件的方式路由 “transaction events”,主要是 “error events”)。
在 Sentry UI 中,您可以使用 Discover 查看所有類(lèi)型的事件,并使用 Issues 和 Performance 部分分別深入研究 errors 和 transactions。 面向用戶的跟蹤文檔解釋了更多產(chǎn)品級(jí)別的概念。
- https://docs.sentry.io/product/performance/distributed-tracing/#traces-transactions-and-spans
Span 類(lèi)將每個(gè)單獨(dú)的 span 存儲(chǔ)在 trace 中。
Transaction 類(lèi)就像一個(gè) span,有幾個(gè)主要區(qū)別:
- Transaction 有 name,span 沒(méi)有。
- 在 span 上調(diào)用 finish 方法會(huì)記錄 span 的結(jié)束時(shí)間戳。對(duì)于 transaction,finish 方法另外向 Sentry 發(fā)送一個(gè)事件。
Transaction 類(lèi)可能繼承自 Span,但這是一個(gè)實(shí)現(xiàn)細(xì)節(jié)。從語(yǔ)義上講,transaction 既表示 span tree 的 top-level span,也表示向 Sentry 報(bào)告的單位。
Span 接口
- 創(chuàng)建 Span 時(shí),將 startTimestamp 設(shè)置為當(dāng)前時(shí)間
- SpanContext 是 Span 的屬性集合(可以是一個(gè)實(shí)現(xiàn)細(xì)節(jié))。如果可能,SpanContext 應(yīng)該是不可變的。
- Span 應(yīng)該有一個(gè)方法 startChild,它使用當(dāng)前 span 的 id 作為新 span 的 parentSpanId 創(chuàng)建一個(gè)新的 span,并將當(dāng)前 span 的 sampled 值復(fù)制到新 span 的 sampled 屬性
- startChild 方法應(yīng)遵守 maxSpans 限制,一旦達(dá)到限制,SDK 不應(yīng)為給定的 transaction 創(chuàng)建新的子 span。
- Span 應(yīng)該有一個(gè)名為 toSentryTrace 的方法,它返回一個(gè)字符串,該字符串可以作為名為 sentry-trace 的 header 發(fā)送。
- Span 應(yīng)該有一個(gè)名為 iterHeaders(適應(yīng)平臺(tái)的命名約定)的方法,它返回一個(gè)可迭代的或 header 名稱和值的映射。這是一個(gè)包含 return {"sentry-trace": toSentryTrace()} 的薄 wrapper。請(qǐng)參閱 continueFromHeaders 以了解為什么存在這種情況,并且在編寫(xiě)集成(integration)時(shí)應(yīng)該首選。
Transaction 接口
- 一個(gè) Transaction 內(nèi)部包含一個(gè)子 Span 的平面列表(不是樹(shù)結(jié)構(gòu))
- Transaction 還有一個(gè) setName 方法來(lái)設(shè)置 transaction 的名稱
- Transaction 在創(chuàng)建時(shí)收到一個(gè) TransactionContext(新屬性與 SpanContext 是 name)
- 由于 Transaction 繼承了 Span,因此它具有所有 Span 可用的函數(shù)并且可以像 Span 一樣進(jìn)行交互
- 一個(gè) transaction 要么被采樣(sampled = true),要么被取消采樣(sampled = false),一個(gè)在 transaction 的生命周期中被繼承或設(shè)置一次的決定,并且在任何一種情況下都會(huì)傳播給所有的 children。不應(yīng)將未抽樣的 transaction 發(fā)送給 Sentry。
- TransactionContext 應(yīng)該有一個(gè)叫做 fromSentryTrace 的 static/ctor 方法,它用從 sentry-trace header值接收的數(shù)據(jù)預(yù)填充一個(gè) TransactionContext
- TransactionContext 應(yīng)該有一個(gè)名為 continueFromHeaders(headerMap) 的 static/ctor 方法,它現(xiàn)在實(shí)際上只是一個(gè)圍繞 fromSentryTrace(headerMap.get("sentry-trace")) 的薄 wrapper。integration/framework-sdk 的作者應(yīng)該更喜歡 fromSentryTrace,因?yàn)樗[藏了核心 sdk 中更深層次使用的確切 header 名稱,并為將來(lái)使用其他 header(來(lái)自 W3C)留下了機(jī)會(huì),而無(wú)需更改所有集成。
Span.finish()
- 只需將 endTimestamp 設(shè)置為當(dāng)前時(shí)間(在 payload timestamp 中)
Transaction.finish()
- super.finish() (在 Span 上調(diào)用 finish)
- 僅當(dāng) sampled == true 時(shí)才將其發(fā)送給 Sentry
- 一個(gè) Transaction 需要被包裹在一個(gè) Envelope 中并發(fā)送到 Envelope Endpoint
- Transport 應(yīng)該為 Transactions/Events 使用相同的內(nèi)部隊(duì)列
- Transport 應(yīng)該實(shí)現(xiàn)基于類(lèi)別的速率限制 →
- Transport 應(yīng)該處理在內(nèi)部將 Transaction 包裝在 Envelope 中
采樣
每個(gè) transaction 都有一個(gè) “抽樣決策”,即一個(gè)布爾值,指示是否應(yīng)該將其發(fā)送給 Sentry。這應(yīng)該在 transaction 的生命周期內(nèi)只設(shè)置一次,并且應(yīng)該存儲(chǔ)在內(nèi)部的 sampled 布爾值中。
transaction 可以通過(guò)多種方式結(jié)束抽樣決策(sampling decision):
- 根據(jù) tracesSampleRate 中設(shè)置的靜態(tài)采樣率隨機(jī)采樣
- 根據(jù) tracesSampler 返回的動(dòng)態(tài)采樣率進(jìn)行隨機(jī)采樣
- tracesSampler 返回的絕對(duì)決策(100% 概率或 0% 概率)
- 如果 transaction 有父級(jí),繼承其父級(jí)的抽樣決策
- 傳遞給 startTransaction 的絕對(duì)決策
當(dāng)其中一個(gè)以上發(fā)揮作用的可能性時(shí),應(yīng)適用以下優(yōu)先規(guī)則:
- 如果將抽樣決策傳遞給 startTransaction (startTransaction({name: "my transaction", sampled: true})),則將使用該決策,而不管其他任何事情。
- 如果定義了 tracesSampler,則將使用其決策。它可以選擇保留或忽略任何父采樣決策,或使用采樣上下文數(shù)據(jù)做出自己的決策或?yàn)?transaction 選擇采樣率。
- 如果未定義 tracesSampler,但存在父采樣決策,則將使用父采樣決策。
- 如果沒(méi)有定義 tracesSampler 并且沒(méi)有父采樣決策,則將使用 tracesSampleRate。
Transaction 應(yīng)僅通過(guò) tracesSampleRate 或 tracesSampler 進(jìn)行采樣。sampleRate 配置用于 error 事件,不應(yīng)應(yīng)用于 transaction。
采樣上下文
如果定義,tracesSampler 回調(diào)應(yīng)該傳遞一個(gè) samplingContext 對(duì)象,該對(duì)象至少應(yīng)該包括:
- 創(chuàng)建 transaction 的 transactionContext
- 一個(gè)布爾值 parentSampled,包含從父級(jí)傳遞過(guò)來(lái)的采樣決策,如果有的話
- 來(lái)自可選的 customSamplingContext 對(duì)象的數(shù)據(jù)在手動(dòng)調(diào)用時(shí)傳遞給 startTransaction
根據(jù)平臺(tái),可能包含其他默認(rèn)數(shù)據(jù)。(例如,對(duì)于服務(wù)器框架,包含與 transaction 正在測(cè)量的請(qǐng)求相對(duì)應(yīng)的 request 對(duì)象是有意義的。)
傳播
transaction 的抽樣決策應(yīng)傳遞給其所有子項(xiàng),包括跨服務(wù)邊界。這可以在相同服務(wù)子項(xiàng)的 startChild 方法中完成,并為不同服務(wù)中的子項(xiàng)使用 senry-trace header。
Header sentry-trace
Header 用于跟蹤傳播。SDK 使用 header 繼續(xù)跟蹤來(lái)自上游服務(wù)(傳入 HTTP 請(qǐng)求),并將跟蹤信息傳播到下游服務(wù)(傳出 HTTP 請(qǐng)求)。
sentry-trace = traceid-spanid-sampled
sampled 是可選的。所以至少,它是預(yù)期的:
sentry-trace = traceid-spanid
為了與 W3C traceparent header(沒(méi)有版本前綴) 和 Zipkin's b3 headers(考慮 64 位和 128 位的 traceId 有效)提供最小的兼容性, sentry-trace header 應(yīng)具有以 32 個(gè)十六進(jìn)制字符編碼的 128 位的 traceId 以及以 16 個(gè)十六進(jìn)制字符編碼的 64 位 spanId。為了避免與 W3C traceparent header(我們的 header 相似但不相同)混淆, 我們將其簡(jiǎn)稱為 sentry-trace。header 中沒(méi)有定義版本。
- https://www.w3.org/TR/trace-context/#traceparent-header
- https://zipkin.io/pages/instrumenting#communicating-trace-information
sampled 值
為簡(jiǎn)化處理,該值由單個(gè)(可選)字符組成。可能的值為:
- - No value means defer
- 0 - Don't sample
- 1 - Sampled
與 b3 header 不同,sentry-trace header 不應(yīng)該只包含一個(gè)采樣決策,沒(méi)有 traceid 或 spanid 值。有很好的理由 無(wú)論采樣決策如何,始終包含 traceid 和 spanid,這樣做也簡(jiǎn)化了實(shí)現(xiàn)。
- https://github.com/apache/incubator-zipkin-b3-propagation/blob/bc937b6854ea30e46b3e85fbf147d8f4de685dd5/README.md#why-send-trace-ids-with-a-reject-sampling-decision
除了在 Sentry 的情況下使用 *defer 的通常原因外, 還有一個(gè)原因是下游系統(tǒng)使用 Sentry 捕獲 error 事件。可以在那時(shí)做出決定,對(duì)跟蹤進(jìn)行采樣,以便為報(bào)告的崩潰提供跟蹤數(shù)據(jù)。
- https://github.com/apache/incubator-zipkin-b3-propagation/blob/bc937b6854ea30e46b3e85fbf147d8f4de685dd5/README.md#why-defer-a-sampling-decision
sentry-trace = sampled
這實(shí)際上對(duì)于代理將其設(shè)置為 0 并選擇退出跟蹤很有用。
Static API 變更
Sentry.startTransaction 函數(shù)應(yīng)該接受兩個(gè)參數(shù) - 傳遞給 Transaction 構(gòu)造函數(shù)的 transactionContext 和一個(gè)包含要傳遞給 tracesSampler(如果已定義)的數(shù)據(jù)的可選的 customSamplingContext 對(duì)象。
它創(chuàng)建一個(gè)綁定到當(dāng)前 hub 的 Transaction 并返回實(shí)例。用戶與實(shí)例交互以創(chuàng)建子 span,因此,必須自己跟蹤它。
Hub 變更
引入一個(gè)名為 traceHeaders 的方法
- 此函數(shù)返回 header(string)sentry-trace
- 該值應(yīng)該是當(dāng)前在 Scope 上的 Span 的 trace header 字符串
引入一個(gè)名為 startTransaction 的方法
- 采用與 Sentry.startTransaction 相同的兩個(gè)參數(shù)
- 創(chuàng)建一個(gè)新的 Transaction 實(shí)例
- 應(yīng)按照本文檔 'Sampling' 部分中更詳細(xì)的描述實(shí)施抽樣
修改名為 captureEvent 或 captureTransaction 的方法
- 不要為 transaction 設(shè)置 lastEventId
Scope 變更
Scope 持有對(duì)當(dāng)前 Span 或 Transaction 的引用。
Scope 引入 setSpan
- 這可以在內(nèi)部使用,來(lái)傳遞 Span / Transaction,以便集成可以將子項(xiàng)附加到它
- 在 Scope(舊版)上設(shè)置 transaction 屬性應(yīng)該覆蓋存儲(chǔ)在 Scope 中的 Transaction 的名稱,如果有的話。這樣,即使用戶無(wú)法直接訪問(wèn) Transaction 的實(shí)例,我們也可以讓用戶選擇更改 transaction 名稱。
與 beforeSend 和事件處理器的交互
beforeSend 回調(diào)是我們認(rèn)為最重要的特殊 Event Processor。適當(dāng)?shù)氖录幚砥魍ǔ1徽J(rèn)為是內(nèi)部的。
Transaction 應(yīng)該不通過(guò) beforeSend。但是,它們?nèi)匀挥墒录幚砥魈幚怼_@是在將 transaction 作為 event 的當(dāng)前實(shí)現(xiàn)處理的一些靈活性與為 transaction 和 span 的不同生命周期 hook 留出空間之間的折衷。
動(dòng)機(jī):
- 面向未來(lái):如果用戶依賴 beforeSend 進(jìn)行 transaction, 這將使最終在不破壞用戶代碼的情況下實(shí)現(xiàn)單個(gè) span 攝取變得復(fù)雜。在撰寫(xiě)本文時(shí),transaction 作為 event 發(fā)送,但這被視為實(shí)現(xiàn)細(xì)節(jié)。
- API 兼容性:用戶擁有他們現(xiàn)有的 beforeSend 實(shí)現(xiàn),只需要處理錯(cuò)誤事件。我們將 transaction 作為一種新型 event 引入。當(dāng)用戶升級(jí)到新的 SDK 版本并開(kāi)始使用跟蹤時(shí),他們的 beforeSend 將開(kāi)始看到他們的代碼不打算處理的新類(lèi)型。在 transaction 之前,他們根本不必關(guān)心不同的事件類(lèi)型。有幾種可能的后果:破壞用戶應(yīng)用程序;默默地和無(wú)意地放棄 transaction; transaction 事件以令人驚訝的方式修改。
- 就可用性而言,beforeSend 不適合刪除 transaction,就像刪除 error 一樣。 Error 是一個(gè)時(shí)間點(diǎn)事件。當(dāng) error 發(fā)生時(shí),用戶在 beforeSend 中有完整的上下文, 并且可以在它進(jìn)入 Sentry 之前修改/丟棄事件。對(duì)于交易,transaction 是不同的。創(chuàng)建 transaction,然后將它們打開(kāi)一段時(shí)間,同時(shí)創(chuàng)建 child span 并將其附加到它。同時(shí)傳出的 HTTP 請(qǐng)求包括當(dāng)前 transaction 與其他服務(wù)的采樣決策。在 span 和 transaction 完成后,將 transaction 放入類(lèi)似 beforeSend 的鉤子中會(huì)在跟蹤中留下來(lái)自其他服務(wù)的孤立 transaction。同樣,在此后期將采樣決策修改為 "yes" 也會(huì)產(chǎn)生不一致的痕跡。
跟蹤上下文(實(shí)驗(yàn)性)
為了對(duì)跟蹤進(jìn)行采樣,我們需要沿著調(diào)用鏈傳遞 trace id 以及做出采樣決策所需的信息,即所謂的 跟蹤上下文(trace context)。
協(xié)議
Trace 信息作為編碼的 tracestate header 在 SDK 之間傳遞,SDK 預(yù)計(jì)會(huì)攔截和傳播這些 header。
對(duì)于向 sentry 提交的事件,trace context 作為嵌入在 Envelope header 中的 JSON 對(duì)象發(fā)送,key 為 trace。
跟蹤上下文
無(wú)論采用何種傳輸機(jī)制,trace context 都是具有以下字段的 JSON 對(duì)象:
- trace_id (string, required) - UUID V4 編碼為不帶破折號(hào)的十六進(jìn)制序列(例如771a43a4192642f0b136d5159a501700),它是一個(gè)由 32 個(gè)十六進(jìn)制數(shù)字組成的序列。這必須與提交的 transaction item 的 trace id 匹配。
- public_key (string, required) - 來(lái)自 SDK 使用的 DSN 的 Public key。它允許 Sentry 通過(guò)基于起始項(xiàng)目解析相同的規(guī)則集來(lái)對(duì)跨多個(gè)項(xiàng)目的跟蹤進(jìn)行采樣。
- release (string, optional) - 客戶端選項(xiàng)中指定的版本名稱,通常是:package@x.y.z+build。 這應(yīng)該與 transaction event payload 的 release 屬性匹配*
- environment - 客戶端選項(xiàng)中指定的 environment 名稱,例如 staging。 這應(yīng)該與 transaction event payload 的 environment 屬性匹配*
- user (object, optional) - 包含以下字段的 scope 的 user context 的子集:
- id (string, optional) - 用戶上下文的 id 屬性。
- segment (string, optional) - 用戶數(shù)據(jù)包中的 segment 屬性值(如果存在)。將來(lái),該字段可能會(huì)被提升為用戶上下文的適當(dāng)屬性。
- transaction (string, optional) - 在 scope 上設(shè)置的 transaction 名稱。這應(yīng)該與 transaction event payload 的 transaction 屬性匹配*
例子:
- {
- "trace_id": "771a43a4192642f0b136d5159a501700",
- "public_key": "49d0f7386ad645858ae85020e393bef3",
- "release": "myapp@1.1.2",
- "environment": "production",
- "user": {
- "id": "7efa4978da177713df088f846f8c484d",
- "segment": "vip"
- },
- "transaction": "/api/0/project_details"
- }
Envelope Headers(信封頭)
當(dāng)通過(guò) Envelope 向 Sentry 發(fā)送 transaction 事件時(shí),必須在 trace 字段下的 envelope header 中設(shè)置 trace 信息。
這是一個(gè)包含 trace context 的最小 envelope header 的示例(盡管 header 不包含換行符,但在下面的示例中添加了換行符以提高可讀性):
- {
- "event_id": "12c2d058d58442709aa2eca08bf20986",
- "trace": {
- "trace_id": "771a43a4192642f0b136d5159a501700",
- "public_key": "49d0f7386ad645858ae85020e393bef3"
- // other trace attributes
- }
- }
Tracestate Headers(跟蹤狀態(tài)頭)
將跟蹤上下文傳播到其他 SDK 時(shí),Sentry 使用 W3C tracestate header。有關(guān)如何將這些 header 傳播到其他 SDK 的更多信息,請(qǐng)參閱 "Trace Propagation"。
- https://www.w3.org/TR/trace-context/#trace-context-http-headers-format
Tracestate header 包含幾個(gè)特定于供應(yīng)商的不透明數(shù)據(jù)。根據(jù) HTTP 規(guī)范,這些多個(gè) header 值可以通過(guò)兩種方式給出,通常由 HTTP 庫(kù)和開(kāi)箱即用的框架支持:
用逗號(hào)連接:
- tracestate: sentry=,other=
重復(fù):
- tracestate: sentry=
- tracestate: other=
要?jiǎng)?chuàng)建 tracestate header 的內(nèi)容:
- 將完整的 trace context 序列化為 JSON,包括 trace_id。
- 如果字符串在平臺(tái)上的表示方式不同,則將生成的 JSON 字符串編碼為 UTF-8。
- 使用 base64 對(duì) UTF-8 字符串進(jìn)行編碼。
- 去除尾隨填充字符 (=),因?yàn)檫@是一個(gè)保留字符。
-
在前面加上 "sentry=",導(dǎo)致 "sentry=
"。 - 如上所述加入 header。
通過(guò)去除尾隨填充,默認(rèn)的 base64 解析器可能會(huì)檢測(cè)到不完整的 payload。選擇允許丟失 = 或允許截?cái)?payload 的解析模式。
例如:
- {
- "trace_id": "771a43a4192642f0b136d5159a501700",
- "public_key": "49d0f7386ad645858ae85020e393bef3",
- "release": "1.1.22",
- "environment": "dev",
- "user": {
- "segment": "vip",
- "id": "7efa4978da177713df088f846f8c484d"
- }
- }
將編碼為
- ewogICJ0cmFjZV9pZCI6ICI3NzFhNDNhNDE5MjY0MmYwYjEzNmQ1MTU5YTUwMTcwMCIsCiAgInB1YmxpY19rZXkiOiAiNDlkMGY3Mzg2YWQ2NDU4NThhZTg1MDIwZTM5M2JlZjMiLAogICJyZWxlYXNlIjogIjEuMS4yMiIsCiAgImVudmlyb25tZW50IjogImRldiIsCiAgInVzZXIiOiB7CiAgICAic2VnbWVudCI6ICJ2aXAiLAogICAgImlkIjogIjdlZmE0OTc4ZGExNzc3MTNkZjA4OGY4NDZmOGM0ODRkIgogIH0KfQ
并導(dǎo)致 header
- tracestate: sentry=ewogIC...IH0KfQ,other=[omitted]
(注意 header 末尾的第三方條目;新的或修改的條目總是添加到左側(cè),因此我們將 sentry= 值放在那里。另請(qǐng)注意,盡管此處為了清晰起見(jiàn)省略了編碼值, 在真正的 header 中,將使用完整的值。)
實(shí)施指南
支持此 header 的 SDK 必須:
- 創(chuàng)建新的 trace context 時(shí)使用 scope 信息
- 為包含 transaction 的 envelope 添加帶有 trace context 的 envelope header
- 將 tracestate HTTP header 添加到傳出的 HTTP 請(qǐng)求以進(jìn)行傳播
- 在適用的情況下攔截對(duì) tracestate HTTP header 的傳入 HTTP 請(qǐng)求,并將它們應(yīng)用到 local trace context
背景
這是性能指南涵蓋的 trace ID 傳播的擴(kuò)展。根據(jù)統(tǒng)一 API 跟蹤規(guī)范,Sentry SDK 通過(guò)集成向傳出請(qǐng)求添加 HTTP header sentry-trace。最重要的是,此 header 包含 trace ID,它必須與 transaction event 的 trace id 以及下面的 trace context 的 trace id 匹配。
trace context 應(yīng)在 W3C traceparent header 中定義的附加 tracestate header 中傳播。請(qǐng)注意,我們必須保持與 W3C 規(guī)范的兼容性,而不是專有的 sentry-trace header。除了 Sentry SDK 放置的內(nèi)容之外,tracestate header 還包含供應(yīng)商特定的不透明數(shù)據(jù)。
- https://www.w3.org/TR/trace-context/#trace-context-http-headers-format
Client 選項(xiàng)
雖然 trace context 正在開(kāi)發(fā)中,但它們應(yīng)該在內(nèi)部 trace_sampling 布爾值 client 選項(xiàng)后面進(jìn)行門(mén)控。該選項(xiàng)默認(rèn)為 false,不應(yīng)在 Sentry 文檔中記錄。
根據(jù)平臺(tái)命名指南,該選項(xiàng)應(yīng)該適當(dāng)?shù)貐^(qū)分大小寫(xiě):
- trace_sampling (snake case)
- traceSampling (camel case)
- TraceSampling (pascal case)
- setTraceSampling (Java-style setters)
添加 Envelope Header
在以下任何一種情況下,SDK 應(yīng)將 envelope header 添加到傳出 envelope 中:
- envelope 包含 transaction event。
- scope 有一個(gè) transaction 綁定。
具體來(lái)說(shuō),這意味著即使沒(méi)有 transaction 的 envelope 也可以包含 trace envelope header, 從而允許 Sentry 最終對(duì)屬于 transaction 的 attachment 進(jìn)行采樣。當(dāng) envelope 包含 transaction 且 scope 有綁定 transaction 時(shí), SDK 應(yīng)使用 envelope 的 transaction 來(lái)創(chuàng)建 trace envelope header。
凍結(jié)上下文
為了確保 trace 中所有 transaction 的 trace context 完全一致,一旦通過(guò)網(wǎng)絡(luò)發(fā)送 trace context,就不能更改 trace context,即使 scope 或 options 之后發(fā)生更改。也就是說(shuō),一旦計(jì)算出 trace context 就不再更新。即使應(yīng)用程序調(diào)用 setRelease,舊版本仍保留在 context 中。
為了彌補(bǔ)對(duì) setTransaction 和 setUser 等函數(shù)的延遲調(diào)用, 可以認(rèn)為 trace context 處于兩種狀態(tài):NEW 和 SENT。最初,context 處于 NEW 狀態(tài)并且可以修改。第一次發(fā)送后,它將變?yōu)?SENT 并且不能再更改。
我們建議 trace context 應(yīng)該在第一次需要時(shí)即時(shí)計(jì)算:
- 創(chuàng)建 Envelope
- 傳播到傳出的 HTTP 請(qǐng)求
Trace context 必須保留,直到用戶開(kāi)始新的 trace,此時(shí) SDK 必須計(jì)算新的 trace context。
建議 SDK 記錄在 trace context 凍結(jié)時(shí)會(huì)導(dǎo)致 trace context 更改的屬性修改,例如 user.id,以簡(jiǎn)化常見(jiàn)動(dòng)態(tài)采樣陷阱的調(diào)試。
傳入上下文
與攔截來(lái)自入站 HTTP 請(qǐng)求的 trace ID 相同,SDK 應(yīng)讀取 tracestate header 并假設(shè) Sentry 跟蹤上下文(如果指定)。這樣的上下文立即凍結(jié)在 SENT 狀態(tài),不應(yīng)再允許修改。
平臺(tái)細(xì)節(jié)
在 JavaScript 中編碼
如前所述,我們需要使用 UTF-8 字符串對(duì) JSON trace context 進(jìn)行編碼。JavaScript 內(nèi)部使用 UTF16,因此我們需要做一些工作來(lái)進(jìn)行轉(zhuǎn)換。
Base64 encoding and decoding in JavaScript(以及 Using Javascript's atob to decode base64 doesn't properly decode utf-8 strings)介紹了基本思想。
- https://attacomsian.com/blog/javascript-base64-encode-decode
- https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
簡(jiǎn)而言之,這是將 context 轉(zhuǎn)換為可以保存在 tracestate 中的 base64 字符串的函數(shù)。最后我們采用了一個(gè)更簡(jiǎn)單的實(shí)現(xiàn),但想法是一樣的:
- // Compact form
- function objToB64(obj) {
- const utf16Json = JSON.stringify(obj);
- const b64 = btoa(
- encodeURIComponent(utf16Json).replace(
- /%([0-9A-F]{2})/g,
- function toSolidBytes(match, p1) {
- return String.fromCharCode("0x" + p1);
- }
- )
- );
- const len = b64.length;
- if (b64[len - 2] === "=") {
- return b64.substr(0, len - 2);
- } else if (b64[len - 1] === "=") {
- return b64.substr(0, len - 1);
- }
- return b64;
- }
- // Commented
- function objToB64(obj) {
- // object to JSON string
- const utf16Json = JSON.stringify(obj);
- // still utf16 string but with non ASCI escaped as UTF-8 numbers)
- const encodedUtf8 = encodeURIComponent(utf16Json);
- // replace the escaped code points with utf16
- // in the first 256 code points (the most wierd part)
- const b64 = btoa(
- endcodedUtf8.replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) {
- return String.fromCharCode("0x" + p1);
- })
- );
- // drop the '=' or '==' padding from base64
- const len = b64.length;
- if (b64[len - 2] === "=") {
- return b64.substr(0, len - 2);
- } else if (b64[len - 1] === "=") {
- return b64.substr(0, len - 1);
- }
- return b64;
- }
- // const test = {"x":"a-