頂級開源專案 Sentry 20.x JS-SDK 設計藝術(Unified API篇)

為少發表於2021-03-13

SDK 開發

  1. 頂級開源專案 Sentry 20.x JS-SDK 設計藝術(理念與設計原則篇)
  2. 頂級開源專案 Sentry 20.x JS-SDK 設計藝術(開發基礎篇)
  3. 頂級開源專案 Sentry 20.x JS-SDK 設計藝術(概述篇)

系列

  1. Snuba:Sentry 新的搜尋基礎設施(基於 ClickHouse 之上)
  2. Sentry 10 K8S 雲原生架構探索,Vue App 1 分鐘快速接入
  3. Sentry(v20.x)玩轉前/後端監控與事件日誌大資料分析,使用 Helm 部署到 K8S 叢集
  4. Sentry(v20.x) JavaScript SDK 三種安裝載入方式
  5. Sentry(v20.x) JavaScript SDK 配置詳解
  6. Sentry(v20.x) JavaScript SDK 手動捕獲事件基本用法
  7. Sentry(v20.x) JavaScript SDK Source Maps詳解
  8. Sentry(v20.x) JavaScript SDK 故障排除
  9. Sentry(v20.x) JavaScript SDK 1分鐘上手效能監控
  10. Sentry(v20.x) JavaScript SDK 效能監控之管理 Transactions
  11. Sentry(v20.x) JavaScript SDK 效能監控之取樣 Transactions
  12. Sentry(v20.x) JavaScript SDK Enriching Events(豐富事件資訊)
  13. Sentry(v20.x) JavaScript SDK Data Management(問題分組篇)

統一的API

新的 Sentry SDK 應遵循 Unified API,使用一致的術語來指代概念。
本文件說明了 Unified API 是什麼以及為什麼它存在。

動機

Sentry 有各種各樣的 SDK,這些 SDK 是由不同的開發人員根據不同的想法在過去幾年裡開發出來的。這導致了不同 SDK 的特性設定不同,使用不同的概念和術語,這導致了通常不清楚如何在不同的平臺上實現相同的東西。

此外,這些 SDK 完全以通過 explicit clients 進行錯誤報告為中心,這意味著通常無法進行某些整合(例如麵包屑 breadcrumbs)。

一般準則

  • 我們希望所有 SDK API 的語言/措辭統一,以輔助支援和文件編制,並使使用者更輕鬆地在不同環境中使用 Sentry
  • 在設計 SDK 時,我們可以新增一些新的功能,而不是單純的事件報告(transactionsAPM等)。
  • 設計具有相同 client 例項的 SDK,我們既可以通過依賴項注入等在執行時環境中自然工作,也可以使用隱式上下文分派給已經存在的 clientsscopes,以掛接到大多數環境中。 這很重要,因為它允許事件將流程中其他整合的資料包括在內。
  • 常見任務必須簡單明瞭。
  • 為了幫助第三方庫,“non configured Sentry” 的情況需要快速處理(和延遲執行)。
  • 通用 API 需求在大多數語言中都是有意義的,並且一定不能依賴於超級特殊的構造。 為了使它更自然,我們應該考慮語言細節,並明確地支援它們作為替代方法(disposables, stack guards 等)。

簡化過的圖解

術語

  • minimal:一個單獨的 “facade” 包,它通過介面(interfaces)或代理(proxies)重新匯出 SDK 功能的子集。該包不直接依賴於 SDK,相反,如果沒有安裝 SDK,它應該使每個操作都成為 noop。這樣一個包的目的是允許 random 庫記錄麵包屑和設定上下文資料,同時不依賴 SDK
  • hub:管理狀態的物件。預設情況下可以使用隱含的 global thread local 或類似的 hubHubs 可以手動建立。
  • scopescope 包含了應該與 Sentry 事件一起隱式傳送的資料。它可以儲存上下文資料、額外引數、級別覆蓋、指紋等。
  • clientclient 是隻配置一次的物件,可以繫結到 hub。然後,使用者可以自動發現 client 並分派對它的呼叫。使用者通常不需要直接與 client 打交道。它們要麼通過 hub 實現,要麼通過 static convenience functions 實現。client 主要負責構建 Sentry 事件並將其傳送到 transport
  • client options:是特定於語言和執行時的引數,用於配置 client。 這可以是 releaseenvironment,也可以是要配置的 integrationsin-app works 等。
  • contextContextsSentry 提供額外的資料。有特殊的上下文( user 和類似的)和通用的上下文(runtime,os,device),等等。檢查有效鍵的 Contexts。注意:在舊的 SDK 中,您可能會遇到一個與上下文無關的概念,這個概念現在已被作用域棄用。
  • tagsTags 可以是任意 string → 可以搜尋事件的 string pairsContexts 被轉換為 tags
  • extraclient users 附加的真正任意資料。這是一個已棄用的特性,但在可預見的未來將繼續得到支援。鼓勵使用者使用上下文代替。
  • transporttransport 是對事件傳送進行抽象的客戶端的內部構造。通常,transport 在單獨的執行緒中執行,並獲取通過佇列傳送的事件。transport 負責傳送(sending)、重試(retrying)和處理速率限制(handling rate limits)。如果需要,transport 還可能在重啟過程中持久化未傳送的事件。
  • integration:向特定框架(frameworks)或環境(environments)提供中介軟體(middlewares)、繫結(bindings)或鉤子(hooks)的程式碼,以及插入這些繫結並啟用它們的程式碼。整合的使用不遵循公共介面。
  • event processors:針對每個事件執行的回撥(Callbacks)。他們可以修改並返回事件,或者可以為 null。返回 null 將丟棄該事件,並且不會進一步處理。有關更多資訊,請參見事件管道(Event Pipeline)。
  • disabled SDK:大多數 SDK 功能依賴於已配置的 active client。當有 transport 時,Sentry 認為 clientactive 的。否則,客戶端是 inactive 的,SDK 被認為是 “disabled”。在這種情況下,某些回撥函式,例如 configure_scope 或事件處理器(event processors),可能不會被呼叫。因此,麵包屑(breadcrumbs)不會被記錄下來。

"Static(靜態)API"

靜態 API 函式是最常見的面向使用者的 API。使用者只需匯入這些功能,即可開始向 Sentry 發出事件或配置作用域。這些快捷方式功能應在包的頂級名稱空間中匯出。 他們在後臺使用 hubsscopes(有關更多資訊,請參見併發性 Concurrency)(如果在該平臺上可用)。請注意,下面列出的所有函式大部分都是 Hub::get_current().function 的別名。

  • init(options):這是每個 SDK 的入口點。

通常,這會建立(creates)/重新初始化(reinitializes)傳播到所有新執行緒(new threads)/執行上下文(execution contexts)的global hub,或者為每個執行緒(per thread)/執行上下文(execution context)建立一個 hub

接受 optionsdsn 等),配置 client 並將其繫結到當前 hub 或對其進行初始化。應返回一個 stand-in,可用於 drain events(一次性)。

這可能會返回一個 handleguard 來處理。如何實現這一點完全取決於 SDK。這甚至可能是一個 client,如果這對 SDK 有意義的話。在 Rust 中,它是一個 ClientInitGuard,在 JavaScript 中,它可以是一個帶有可等待的 close 方法的 helper 物件。

您應該能夠多次呼叫此方法,而第二次呼叫它既可以拆除先前的 client,也可以減少先前 client 的引用計數,等等。

多次呼叫只能用於測試。如果您在應用程式啟動以外的任何時間呼叫 init,將會是 undefined

使用者必須呼叫一次 init,但允許使用禁用的 DSN 進行呼叫。
例如可能沒有引數傳遞等。

此外,它還設定了所有預設的整合。

  • capture_event(event):接受一個已經組合好的事件,並將其排程到當前活動的中心。 事件物件可以是普通字典或型別化的物件,無論在SDK中更有意義。 它應儘可能遵循本機協議,而忽略平臺特定的重新命名(案例樣式等)。
  • capture_exception(error):報告 errorexception 物件。根據平臺的不同,可能有不同的引數。最明顯的版本只接受一個 error 物件,但在不傳遞 error 且使用當前 exception 的情況下也可能發生變化。
  • capture_message(message, level):報告 message。級別可以是可選的語言預設引數,在這種情況下,它應該預設為 info
  • add_breadcrumb(crumb):向 scope 新增新的麵包屑。 如果麵包屑的總數超過 max_breadcrumbs 設定,則 SDK 應刪除最舊的麵包屑。這與 Hub API 的工作原理類似。如果禁用了 SDK,它應該忽略 breadcrumb
  • configure_scope(callback):可以重新配置 scope 物件呼叫的回撥。這用於為相同範圍內的未來事件附加上下文資料。
  • last_event_id():應該返回當前作用域發出的最後一個事件 ID。例如,這用於實現使用者反饋對話方塊(feedback)。

併發

所有 SDK 都應具有併發安全上下文儲存(concurrency safe context storage)的概念。 這意味著什麼取決於語言。 基本思想是,SDK 的使用者可以呼叫一種方法來為即將記錄的所有事件安全地提供其他上下文資訊。

在大多數語言中,這是作為 thread local stack 實現的,但在某些語言中(比如 JavaScript),它可能是全域性的,因為假設這在環境中是有意義的。

以下是一些常見的併發模式:

  • Thread bound hub:在這種模式下,每個 thread 都有自己的 “hub”,該 hub 在內部管理一系列作用域scopes。 如果遵循該模式,則一個 thread(呼叫 init() 的執行緒)將成為 “main” hub,該 hub 將用作新生成的執行緒的基礎,該執行緒將獲得基於主 hubhub(但又是獨立的)。
  • Internally scoped hub:在一些平臺上,如 .NET ambient data 是可用的,在這種情況下 Hub 可以內部管理作用域scopes
  • Dummy hub:在一些平臺上,併發性concurrency本身並不存在。在這種情況下,hub 可能完全不存在,或者只是一個沒有併發管理concurrency management的單例。

Hub

在正常情況下,hub 由一堆 clientsscopes 組成。

SDK 維護兩個變數:main hub(一個全域性變數)和 current hub(當前執行緒thead或執行上下文execution context的本地變數,有時也稱為非同步本地async local或上下文字地context local變數)

  • Hub::new(client, scope):使用給定的 clientscope 建立一個新的 hubclient 可以在 hubs 之間重用。scope 應歸 hub 所有(如有必要,請進行 clone
  • Hub::new_from_top(hub) / 或者原生建構函式過載native constructor overloads:通過克隆另一個 hub 的頂部堆疊top stack來建立新的 hub
  • get_current_hub() / Hub::current() / Hub::get_current():全域性函式或靜態函式以返回當前(執行緒的)hub
  • get_main_hub() / Hub::main() / Hub::get_main():在主執行緒main thread是特殊的語言中(“Thread bound hub”模型),這會返回 main thread 的中心而不是當前執行緒 current thread 的中心。這可能並不存在於所有的語言中。
  • Hub::capture_event / Hub::capture_message / Hub::capture_exception:捕獲 message / exceptioncapture eventcapture_event 將傳遞的 eventscope 資料合併,並分派給 client。作為附加引數,它還需要一個提示。有關 hint 引數,請參見 hints
  • Hub::push_scope():推送一個繼承前一個資料的新作用域層new scope layer。 這應返回有意義的語言的 disposablestack guard。當使用 “內部作用域中心”internally scoped hub 併發模型時,通常需要對此進行呼叫,否則可能會意外地錯誤共享作用域。
  • Hub::with_scope(callback) (optional):在 Python 中,這可能是上下文管理器;在 Ruby 中,這可能是塊函式。推動並彈出整合工作的 scope
  • Hub::pop_scope(callback) (optional):只存在於沒有更好的資源管理resource management的語言中。最好在 push_scope 的返回值上使用這個函式,或者使用 with_scope。這有時也被稱為 pop_scope_unsafe,以表明不應該直接使用該方法。
  • Hub::configure_scope(callback):使用對修改範圍的可變引用來呼叫回撥。 這也可以是具有它的語言(Python)中的 with 語句。如果沒有 active client 繫結到該 hub,則 SDK 不應呼叫回撥。
  • Hub::add_breadcrumb(crumb, hint):將麵包屑新增到當前作用域。
    • 支援的引數應為:
      • 建立麵包屑的函式
      • 已經建立的麵包屑物件
      • 麵包屑列表(可選)
    • 在沒有基本過載形式的語言中,只有原始的麵包屑物件raw breadcrumb object應該被接受。
    • 如果沒有 active client 繫結到該 hub,則 SDK 應忽略麵包屑。
    • 有關 hint 引數,請參見 hints
  • Hub::client() / Hub::get_client() (optional):返回當前 clientNoneAccessorgetter
  • Hub::bind_client(new_client):將不同的 client 繫結到 hub。如果 hub 也是 init 建立的 client 的所有者,那麼如果 hub 是負責處理它的物件,則需要保留對它的引用。
  • Hub::unbind_client() (optional):對於 bind_client 不接受空值的語言,可選的解繫結方法。
  • Hub::last_event_id():應該返回當前 scope 發出的最後一個 event ID。例如,這是用來實現使用者反饋對話方塊feedback dialogs
  • Hub::run(hub, callback) hub.run(callback), run_in_hub(hub, callback)(optional):執行將 hub 繫結為當前 hub 的回撥。

Scope

scope 包含了應該與 Sentry 事件一起隱式傳送的資料。它可以儲存上下文資料context data、額外引數extra parameters、級別覆蓋level overrides、指紋fingerprints等。

使用者可以通過全域性函式 configure_scope 修改當前作用域(設定額外的、標記、當前使用者)。configure_scope 接受一個回撥函式,並將當前的作用域傳遞給它。

使用這種基於回撥的 API 的原因是效率。如果禁用了 SDK,它就不應該呼叫回撥函式,從而避免不必要的工作。

Sentry.configureScope(scope =>
  scope.setExtra("character_name", "Mighty Fighter"));
  • scope.set_user(user):淺合併使用者 Shallow merges 配置(電子郵件email,使用者名稱username等)。刪除使用者資料是 SDK 定義的,可以使用 remove_user 函式,也可以不傳遞任何資料。
  • scope.set_extra(key, value):將附加鍵設定為任意值,覆蓋潛在的先前值。 刪除 keySDK 定義的,可以使用 remove_extra 函式或不傳遞任何資料作為資料。這是不推薦使用的功能,應鼓勵使用者改用上下文。
  • scope.set_extras(extras):設定一個具有 key/value 對,便捷功能的物件,而不是多個 set_extra 呼叫。與 set_extra 一樣,這被視為已棄用的功能。
  • scope.set_tag(key, value):將 tag 設定為字串值,覆蓋潛在的先前值。 刪除 keySDK 定義的,可以使用 remove_tag 函式或不傳遞任何資料作為資料。
  • scope.set_tags(tags):設定一個具有 key/value 對,便捷功能的物件,而不是多個 set_tag 呼叫。
  • scope.set_context(key, value):將上下文鍵設定為一個值,覆蓋一個潛在的先前值。刪除 keySDK 定義的,可以使用 remove_context 函式或不傳遞任何資料作為資料。 這些型別是 sdk 指定的。
  • scope.set_level(level):設定在此 scope 內傳送的所有事件的級別。
  • scope.set_transaction(transaction_name):設定當前 transaction 的名稱。
  • scope.set_fingerprint(fingerprint[]):將指紋設定為將特定事件分組在一起。
  • scope.add_event_processor(processor):註冊事件處理器函式 event processor。它接受一個事件並返回一個新事件,或者返回 None 來將其刪除。這是許多整合的基礎。
  • scope.add_error_processor(processor)(optional):註冊錯誤處理器函式。 它接受一個事件和異常物件,並返回一個新事件或“None”將其刪除。 這可用於從 SDK 無法提取自身的異常物件中提取其他資訊。
  • scope.clear():將 scope 重置為預設值,同時保留所有已註冊的事件處理器event processors。這不會影響子作用域或父作用域。
  • scope.add_breadcrumb(breadcrumb):將麵包屑新增到當前 scope
  • scope.clear_breadcrumbs():從 scope 中刪除當前的麵包屑 breadcrumbs
  • scope.apply_to_event(event[, max_breadcrumbs]):將 scope 資料應用於給定的事件物件。這也適用於內部儲存在 scope 中的事件處理器 event processors。 一些實現可能想要在此處設定最大面包屑計數。

Client

ClientSDK 中負責事件建立的部分。 例如,Client 應將異常轉換為 Sentry eventClient 應該是無狀態的,它會注入作用域並委託將事件傳送到 Transport 的工作。

  • Client::from_config(config):(或者是普通的建構函式)這通常採用帶有 options + dsn 的物件。
  • Client::capture_event(event, scope):通過將事件與其他資料(client 預設設定)合併來捕獲事件。另外,如果將 scope 傳遞到此係統,則來自該範圍的資料會將其傳遞到內部 transport
  • Client::close(timeout):重新整理佇列直到超時秒。如果客戶端能夠保證事件的交付僅持續到當前時間點,則首選此方法。這可能會因為超時秒而阻塞。在呼叫 close 後,客戶端應該被禁用或銷燬。
  • Client::flush(timeout):和 close 的區別一樣,客戶端在呼叫 flush 後不會被釋放。

Hints

(可選)支援事件捕獲和麵包屑新增的附加引數:hint

hint 是特定於 SDK 的,但提供了關於事件起源的高階資訊。例如,如果捕獲了一個異常,提示可能攜帶原始異常物件。並不是所有的 SDK 都需要提供這個功能。然而,這個引數是為此目的保留的。

Event Pipeline

capture_event 捕獲的事件將按以下順序處理。

注意:事件可以在任何階段丟棄,此時不會發生進一步的處理。

  1. 如果禁用 SDK, Sentry 會立即丟棄該事件。
  2. 客戶端根據配置的取樣速率對事件進行取樣。事件可以根據抽樣率隨機丟棄。
  3. 使用 apply_to_event 應用該作用域。按順序呼叫作用域的事件處理器。
  4. Sentry 呼叫 before-send 鉤子。
  5. Sentry 將事件傳遞到配置的 transport。如果傳輸沒有有效的 DSN,則可以丟棄該事件;它的內部佇列已滿;或由於伺服器要求的速率限制。

Options

許多選項都是跨 SDK 標準化的。有關這些選項的列表,請參閱 the main options documentation

我是為少
微信:uuhells123
公眾號:黑客下午茶
加我微信(互相學習交流),關注公眾號(獲取更多學習資料~)

相關文章