Sentry 開發者貢獻指南- SDK 開發(會話)
對于基本的健康跟蹤,Sentry 接受包含會話更新事件的 envelopes。這些會話更新事件可用于通知 Sentry 有關 release 和 project 相關 project 健康狀況的信息。
注意: 在本地使用 session 時,請確保使用必要的環境變量更新配置文件 ~/.sentry/sentry.config.py:
SENTRY_EVENTSTREAM = 'sentry.eventstream.kafka.KafkaEventStream'
基本操作
- Session 完全是 client 驅動的。client 確定 session 何時開始、結束或轉變為不健康狀態。
- client 可以明確結束 session 以記錄時間或退出條件(崩潰等)。
- 如果需要,client 應在重新啟動時明確結束 session,但 session 不結束是可以接受的。
- Session 通過保存整個 session state 的會話更改事件進行更新。
- Session 從發送的事件中更新。最近的事件保存整個 session state。發送到服務器的初始 session event 被顯式標記。
- Session 更新在實現時不得更改屬性或數據損壞。請參閱下面關于屬性不變性的部分。
- Session 只能更新 5 天。如果一個會話在 5 天內沒有收到第二個事件,則永遠是好的。
- Session 不一定要啟動才崩潰。僅僅報告一次崩潰就足夠了。
服務器模型
目前,Sentry 的 session system 已針對易于擴展性和操作成本進行了優化。這意味著該協議非常適合實現這一目標。其中一些優化顯示在協議中,client 準確地遵循協議以避免在服務器上創建 bad data 非常重要。
服務器每小時都有預先物化(pre-materialized)的 session 數據。當會話更新事件到來時,服務器將立即將數據具體化(materialize)到正確的存儲桶中。這意味著該協議僅限于“附加”。這也意味著 client 需要在其一側存儲會話的整個狀態。
會話更新負載
一個 session update 是名為 session 的 envelope 中的一項。它包含一個大致如下所示的 JSON 負載:
- {
- "sid": "7c7b6585-f901-4351-bf8d-02711b721929",
- "did": "optional distinct user id",
- "init": true,
- "started": "2020-02-07T14:16:00Z",
- "duration": 60,
- "status": "exited",
- "attrs": {
- "release": "my-project-name@1.0.0",
- "environment": "environment name",
- "ip_address": "optional user ip address for filtering",
- "user_agent": "optional user agent for filtering"
- }
- }
請注意,這必須包含在 envelope 中。所以完整的事件看起來像這樣:
- {}
- {"type":"session"}
- {"sid":"..."}
存在以下字段:
sid
- String, optional. 會話 ID (唯一的并且由客戶端生成的)。
如果初始會話狀態為 exited,則允許客戶端跳過它。
did
- String, optional. Distinct ID. 應該是設備或用戶 ID。
系統會在存儲此 ID 之前自動對其進行哈希處理。
seq
- Number, optional. 一個邏輯時鐘。默認為攝取期間的當前 UNIX 時間戳(以毫秒為單位)。
值 0 是保留的,因為 init 設置為 true 的會話將自動將 seq 強制為 0。
timestamp
- String, optional. 會話更改事件發生時的時間戳。
必須是 ISO 日期時間字符串。如果未發送,服務器將采用當前的 UTC 時間戳。在數據模型中,這稱為 received。
started
- String, required. 會話開始時的時間戳。
必須是 ISO 日期時間字符串。
init
- Boolean, optional, 默認為 false。
如果將其設置為 true,則表示這是會話的第一個事件。這讓服務器優化會話計數,因為不需要重復數據刪除(客戶端無論如何都是權威的)。在內部設置此標志時,處理時 seq 更改為0。
duration
- Number, optional. 一個可選字段,可以在接收到事件時傳輸會話持續時間。這可以由客戶端控制,例如,可以減去非活動時間(以浮點數表示的秒數)。
status
- String, optional, 默認是ok。會話的當前狀態。
一個 session 只能有效地處于兩種狀態:ok,這意味著會話處于活動狀態或終止狀態之一。當會話從 ok 移開時,它不能再被更新。
- ok: 會話當前正在進行中,但運行良好。這可以是會話的終止狀態。
- exited: 會話正常終止。
- crashed: 會話因崩潰而終止。
- abnormal: 會話遇到非崩潰相關的異常退出。
errors
- _Number, optional, 默認為 0 _。此會話正在進行時遇到的錯誤的運行計數器。
重要的是,當會話進入 crashed 時,此計數器也會增加。(例如:crash 本身也始終是一個 error)。如果未設置或為 0,攝取應強制 errors 為 1。
attrs
- Object, required 除了 release 之外的所有 key 都是可選的。具有以下屬性的對象:
- release: Sentry Release ID (release),建議格式為 my-project-name@1.0.0。
- environment: Sentry 環境 (environment)。
- ip_address: 要考慮的主要 IP 地址。這通常是用戶的 IP。此數據不會持久化,而是用于過濾。如果未設置,則自動填寫 IP。
- user_agent: 要考慮的 user agent。這通常是導致會話的用戶的用戶代理。此數據不會持久化,而是用于過濾。
Session Aggregates Payload(會話聚合有效負載)
特別是對于 request-mode(請求模式) 會話(見下文),通常每秒有數千個請求和會話。
假設這些 session 將是短時間的,并且不希望跟蹤它們的持續時間, 那么在它們被發送到 Sentry 之前,可以在 SDK 端將這些 session 聚合在一起。
SDK 應聚合關閉的 session,并按 started 時間、distinct_id 和 attrs 對它們進行分組。這些組將作為 sessions envelope 項發送。它包含一個大致如下所示的 JSON 負載:
- {
- "aggregates": [
- {
- "started": "2020-02-07T14:16:00Z",
- "exited": 123
- },
- {
- "started": "2020-02-07T14:16:00Z",
- "did": "optional distinct user id",
- "exited": 12,
- "errored": 3
- }
- ],
- "attrs": {
- "release": "my-project-name@1.0.0",
- "environment": "development"
- }
- }
請注意,這必須包含在 envelope 中。所以完整的 envelope 看起來像這樣:
- {}
- {"type": "sessions"}
- {"aggregates": [...], "attrs": {...}}
aggregates
- Array, required. 按 started 時間戳和 distinct id (did) 分組的聚合數組。
- started: Required. 組的時間戳,四舍五入到分鐘。必須是 ISO 日期時間字符串。
- did: Optional. 組的 distinct user id。
- exited: Optional. 狀態為 "exited" 且沒有任何錯誤的 session 數。
- abnormal: Optional. 狀態為 "abnormal" 的 session 數。
- crashed: Optional. 狀態為 "crashed" 的 session 數。
- errored: Optional. 狀態為 "exited" 且 errors 計數不為零的 session 數。
attrs
- Object, required. 見上文。
崩潰與會話
會話(Session)和錯誤(error)事件是 Sentry 中兩個不同的系統。 Session 更新可以在不發送錯誤事件的情況下完成,同樣,可以在沒有 session 更新的情況下發送 error。
這使 client 可以完全控制應如何執行 session 更新。激勵因素是服務器可以在某些情況下自由拒絕 error 事件,在這種情況下記錄 session 信息仍然很有趣。例如,如果項目對 error 事件應用了 rate limit,則它們的 session 數據仍然可以繞過此 rate limit 路由到項目。
但是,強烈建議在與 crash 事件相同的 envelope 中發送 session 更新,以防 session 轉換到 crashed 狀態。如果網絡不可靠,這將確保事件同時到達系統。
重要客戶端行為
這些是 client 必須遵守的重要規則:
屬性不可變性
當前不允許 session 更改后續更新中的任何屬性,包括 did、started 或其他屬性。唯一允許更改的屬性是 session 狀態、持續時間或錯誤計數。如果一開始不知道 user,則應該延遲 session 開始,或者一旦知道 user 就應該重新啟動 session。
會話計數/初始化
發送到系統的初始 session 更新必須將 init 設置為 true。這是必要的,因為服務器當前不會將總 session 計數作為優化進行重復數據刪除。如果初始的 init: true flag 丟失,則 Sentry 可能無法正確攝取 session。
終止會話狀態
Session 可以存在兩種狀態:進行中(progress)或終止(terminated)。終止的 session 不得接收進一步的更新。退出(exited)、崩潰(crashed)和異常(abnormal)都是終止狀態。當 session 達到此狀態時,client 不得再報告任何 session 更新或啟動新 session。
鼓勵 SDK 區分結束 session 的不同方式:
- exited: 這意味著 session 干凈地結束了。從成功報告的角度來看,這與保持 ok 的 session 沒有任何不同。但是,只有以 exited 結尾的 session 才會被考慮用于 session 持續時間。即使發生 error,session 也可以進入 exited 狀態。
- crashed: 在以下情況下,session 應報告為 crashed:
- 發生未處理的錯誤(unhandled error)并且 session 自然結束(例如:HTTP 請求結束)
- 應用程序完全崩潰(崩潰到桌面,終止)
- 用戶反饋(feedback)對話框顯示給用戶。在此之后,SDK 必須啟動一個新 session,就像它完全崩潰一樣。
- abnormal: 如果可以的話,鼓勵 SDK 始終將 session 轉換為 exited 或 crashed。對于能夠始終結束 session 的 SDK,如果無法檢測到應用程序正確關閉, 則應以 abnormal 結束 session。異常 session 示例:
異常的 session 結束通常會在應用程序重新啟動時被記錄下來。
- 計算機被關閉/斷電
- 用戶通過 kill -9 或任務管理器強制關閉應用程序
崩潰、異常與錯誤
Session 應該在遇到未處理的錯誤(例如應用程序完全崩潰)時轉換為 crashed。對于無法完全崩潰的應用程序(例如網站),如果用戶遇到錯誤對話框,則轉換到 crashed 狀態是可以接受的。對于我們為每個傳入請求創建 session 的 server 環境,crashed 基本上就像狀態代碼 500 內部服務器錯誤。因此,如果在請求期間發生未處理的錯誤(unhandled error),會話應該 crashed。
Abnormal 是其命運未知的 session。例如,對于桌面應用程序,如果 session 被存儲但未觀察到應用程序退出但也沒有崩潰, 則將 session 轉換為 abnormal 是有意義的。在這些情況下,用戶通過任務管理器強制關閉應用程序、機器斷電或其他情況。可以通過將 session 持久保存到磁盤來存儲 session。這個保存的文件可以在應用程序重啟時檢測到,以關閉 abnormal 的會話。
錯誤的 session 由大于零的 errors 計數器確定。 client 需要對被視為 error 的事件進行計數,并將計數與 session 更新一起發送。正常且錯誤計數大于零的 session 被視為錯誤 session。所有崩潰和異常 session 也始終被視為錯誤,但會從最終錯誤 session 計數中減去。
退出
Session 可以轉換為 exited,這與 ok 狀態完全相同, 但有一個區別:轉換為 exited 的 session 的 session 持續時間是平均的。這讓 Sentry 向您顯示非崩潰 session 的持續時間。
警報
當 issue 影響到指定百分比的會話時觸發警報。創建新的 issue alert 并選擇 "When" 條件 An issue affects more than {X} percent of sessions(問題影響超過 {X}% 的會話)。在 Issue Alert Configuration 文檔中查看更多問題警報選項。
- https://docs.sentry.io/product/alerts/alert-types/#issue-alerts
- https://docs.sentry.io/product/alerts/create-alerts/issue-alert-config/
SDK 注意事項
一般來說,SDK 可以使用兩種獨立的健康報告模式。一個是非常短暫的 session,另一個是用戶參與的 session。
短時會話 (server-mode / request-mode)
這些 session 大致對應于服務器設置中的 HTTP 請求或 RPC 調用。
- 海量請求, 通常每個請求一個會話
- Session 數通常高于 Sentry 事件數
- Session 附加到單個 hub/concurrency unit
- 計時信息通常是無用的,因為 session 時間以毫秒為單位
用戶參與的會話 (user-mode / application-mode)
這些 session 更對應于實際的用戶 session 或應用程序運行。這就是您在 Web 瀏覽器、移動世界、命令行應用程序或類似應用程序中會看到的內容。
- 通常只是從應用程序開始到退出的單個 session
- 如果適用,一旦應用程序置于后臺超過 30 秒(移動 SDK),session 就可以結束
- session 次數通常少于 Sentry 事件
- session 跨越多個 hub/thread
- session 持續時間通常以分鐘為單位,計時信息很有用
從 API 的角度來看,這兩種情況看起來很相似,但對于 SDK 的建議不同。
選擇會話模式
雖然理論上可以在單個應用程序中使用兩種會話模式,但建議 SDK 默認使用最適合語言生態系統主要用例的單一模式。這類似于某些 SDK 支持的全局 Hub 模式,并且可以以相同的方式使用。
當 SDK 配置為使用 user-mode 會話或全局 Hub 模式時,應在應用程序啟動時啟動單個會話,并應在應用程序的運行時持續存在。根據 SDK 內部結構,此單個 session 可以在所有應用程序線程和線程本地 Hub 之間共享。
使用 server-mode 會話時,不會啟動應用程序范圍的 session, 并且由集成或用戶在收到請求時啟動 session 并在返回響應時結束 session。
統一 API 的含義
SDK 應該遵循的統一 API 定義了 Hub、Scope 和 Client 的概念。
從概念上講,session 是 Hub 的關注點,與 scope 不同,session 不應嵌套。當任何一種事件發生時,應該只有一個明確的 session 來跟蹤錯誤計數。
在考慮通過 SDK 的事件流時,從靜態 capture_event 函數,通過線程本地 Hub,進入 Client::capture_event(event, scope) 方法;根據 SDK 的內部實現細節,將 session 附加到 Scope 可能是有意義的, 這將使 Client 可以將 event 和 session 更新捆綁到單個 envelope 中以發送到 Sentry。
會話更新以及何時向上游發送更新
對于所有 SDK,只要在調用 apply_to_scope 的類似位置捕獲數據以增加 error 計數, 或根據 distinct ID / user ID 更新 session,將自動更新當前 session。
SDK 通常應旨在減少向上游發送的 envelope 數量。
跟蹤大量會話的 server-mode SDK 應考慮使用定期 session 刷新器(每 60 秒), 將會話預聚合到單個 session_aggregates envelope 項中。
User-Mode SDK 可能會選擇在同一 envelope 中發送 session 更新以及捕獲的事件。關閉 session 的最終 session 更新可以類似于 Server-Mode session 進行批處理。
在任何一種情況下,必須為 session 的第一次傳輸正確設置 init 標志, 并且 session 元數據(例如 distinct ID)在初始傳輸后必須是不可變的。
會話的預聚合
如果 SDK 配置為使用 server-mode session,則應在將 session 計數發送到 Sentry 之前對其進行分組和預聚合。每當 session 關閉(轉換到 terminal 狀態),并且之前沒有向上游發送(其 init 標志為 true)時,它就有資格進行聚合,其執行方式如下:
- Session 的 started 時間戳應四舍五入到分鐘。
- 然后必須將 Session 聚合到由該舍入時間戳標識的存儲 bucket 中,以及會話的 distinct id (did)。
- 在適當的 bucket 中,根據 session 狀態增加 session 計數。與單個 session 更新相反,"errored" 狀態用于標記具有 "exited" 狀態和非零 errors 計數的會話。
公開 API
公開的最基本的 API 位于 hub 級別,可讓您啟動和停止 session 記錄:
API:
Hub.start_session()
在當前 scope 上存儲一個 session 并開始跟蹤它。這通常會將一個全新的 session 附加到 scope,并隱式地結束任何已經存在的 session。
Hub.end_session()
結束 session,設置適當的 status 和 duration,并將其加入隊列以發送到 Sentry。
Hub.start_auto_session_tracking() / Hub.stop_auto_session_tracking()
停止并重新激活自動 session 跟蹤。
初始化選項:
auto_session_tracking
這通過集成 啟用/禁用 自動 session 跟蹤。
SDK 實現指南
在開始在 SDK 中實現此功能之前,請與團隊聯系。
- https://github.com/getsentry/develop/pull/323
我們通過從 SDK 發送 session 有效負載來跟蹤 Sentry 中每個項目 release 的健康狀況。 Session 有效負載提供諸如 session 持續時間以及是否存在錯誤/崩潰等數據。
SDK 以兩種模式之一跟蹤 session:
- 單個 Session
- Session 聚合
單個 session 是一般情況,非常適合通常只涉及單個用戶的(相對短暫的)應用程序。例子:
- 命令行實用程序,如 craft;craft 子命令的每次執行都會向 Sentry 報告一個 session
- 用戶與 mobile app 交互
- 用戶使用他們最喜歡的瀏覽器加載網站
當發送單個 session 不受歡迎或不切實際時,將使用 session 聚合。為了限制資源使用(即內存和網絡),SDK 會跟蹤有關最近發生的一批會話的摘要信息, 實際上不必處理代表構成聚合的各個 session 的 session 對象。此模式適用于運行任意長時間并為潛在的多個用戶處理更大吞吐量的應用程序,例如 Web Server、后臺 Job Worker 等。請注意,對于這些類型的應用程序,session 的更好定義與執行匹配單個 HTTP 請求或任務,而不是整個應用程序進程的單個執行。
在任何一種情況下,SDK 都應默認創建和報告 session,根據應用程序類型選擇單獨報告或作為聚合報告。
如果 SDK 可以檢測到 session 聚合更好地為應用程序提供服務,則它不得報告應用程序范圍的 session。應用程序范圍的 session 可能仍會在 SDK 初始化期間創建,但必須中止并且永遠不會發送到 Sentry。例如,在 Node.js SDK 中,如果應用程序使用提供的 requestHandler 集成,我們可以檢測到它可能是一個 web server。
單個會話功能
配置
- 全局/靜態 API 默認開啟;
如果用戶不想跟蹤 session,他們應該能夠禁用 session。
報告 session 和確定 Sentry 中項目的 Release Health 的先決條件,例如 release 應由 SDK 自動檢測,例如通過查找 env 變量。
(也許,需要討論)如果我的先決條件不能被檢測到(例如,沒有好的方法來確定 release 版本), 那么我們設置一些默認值,以便我們總是可以默認報告 session (取決于討論,這可能不會是 SDK 代碼的更改,但在 Relay 中,基本上刪除了 session 有效負載中的硬要求)。
會話的生命周期
默認情況下,會話應該只為由 Sentry.init 初始化的全局 hub/client 啟用,并默認為任何其他手動創建的 client 禁用。 Session 在 SDK 初始化時開始(理想情況下,當默認 client 綁定到全局 hub 時)并在以下情況之一發生時結束:顯式調用 Hub.endSession() 方法;或程序無錯誤終止;或程序以未處理的異常終止;或程序以未處理的 promise rejection 而終止。
必須注意永遠不要嘗試為已經結束的 session 向 Sentry 發送新的 session 負載。例如,如果用戶使用 Hub.endSession() 手動結束 session,則程序終止時不應有任何新的 session 更新。
會話屬性和可變性
向 Sentry 發送會話
Session 最初在一定的(最初是硬編碼的,配置越少越好)延遲(大約 1 到 30 秒 TBD)后發送, 然后在程序終止時更新持續時間、最終狀態和錯誤計數。請注意,作為一種優化,short lived 程序不會向 Relay 發送 2 個 session 請求, 而只會向 Relay 發送最后一個帶有狀態和持續時間的請求。
會話聚合功能
配置
默認情況下應啟用 session,session 在 web server 收到請求后立即啟動,并在響應完全發回后立即結束。
會話的生命周期
Session 從不被跟蹤或單獨發送,相反,它們被聚合,聚合每 30 秒發送一次,最后一次當 web server 終止。作為對上述點的實現提示,當 "Client" 關閉或刷新時,相關聯的 "Session Flusher" 也應被刷新并在傳輸被刷新/關閉之前提交當前聚合。確保這對于 Serverless 來說是合理的 — 我們不會使用 "request mode" 和 SessionFlusher,因為我們不能在 request-response 流之外進行任何工作。提供一種與現有 Node 框架(Express、Next.js、Koa)集成的簡單方法。