<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sentry on Tarragon</title><link>https://tarrragon.github.io/blog/tags/sentry/</link><description>Recent content in Sentry on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 22 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/sentry/index.xml" rel="self" type="application/rss+xml"/><item><title>Sentry 深入</title><link>https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/sentry-deep-dive/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/sentry-deep-dive/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>跟 Backend 04 的分工&lt;/strong>：本文從 client-side 使用角度說明 Sentry 的 error tracking、performance monitoring 與 session replay — SDK 怎麼埋、error 怎麼分群、release 怎麼追蹤。Server-side 平台治理（告警路由整合、SLI 指標設計、self-hosted vs SaaS 成本治理、跟 OTel 的整合）見 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Backend 04 Sentry vendor page&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;p>Sentry 的核心是 error tracking — 自動捕獲未處理的例外、提供 stack trace、自動分群（grouping）相同 root cause 的 error。在 error tracking 的基礎上，Sentry 擴展了 performance monitoring（transaction / span）和 session replay（重播使用者操作）。&lt;/p>
&lt;h2 id="error-tracking">Error tracking&lt;/h2>
&lt;p>Sentry 的 error tracking 架構有三個層次：SDK 端的自動捕獲、server 端的 issue grouping 和 UI 端的 issue management。&lt;/p>
&lt;h3 id="自動捕獲">自動捕獲&lt;/h3>
&lt;p>Sentry SDK 在各平台註冊全域錯誤處理器（和&lt;a href="https://tarrragon.github.io/blog/monitoring/03-sdk-design/auto-intercept/" data-link-title="自動攔截機制" data-link-desc="JS window.onerror / Flutter FlutterError.onError / Python sys.excepthook — 各平台攔截未捕獲例外的機制和限制">模組三 自動攔截&lt;/a>的機制相同）。捕獲到例外後，SDK 收集 stack trace、breadcrumbs（最近的使用者操作）、device context（OS / browser / device model）和自訂 tags，打包成 event 送到 Sentry server。&lt;/p>
&lt;h3 id="issue-grouping">Issue grouping&lt;/h3>
&lt;p>Sentry server 收到 error event 後，用 fingerprinting 演算法判斷這個 error 是否和已有的 issue 相同。預設的 fingerprinting 基於 stack trace 的 frame — 如果兩個 error 的 stack trace 指向同一個位置，歸入同一個 issue。&lt;/p>
&lt;p>自訂 fingerprint 讓開發者控制 grouping 邏輯。例如：不同使用者觸發的同一個 API error 可能有不同的 stack trace（因為 call site 不同），但 root cause 相同 — 自訂 fingerprint 把它們歸入同一個 issue。&lt;/p>
&lt;h3 id="issue-management">Issue management&lt;/h3>
&lt;p>每個 issue 有狀態（unresolved / resolved / ignored）、指派（誰負責修復）、趨勢（這個 issue 的發生頻率是上升還是下降）。Sentry 的 UI 提供 issue 列表、趨勢圖、影響範圍（影響多少使用者）。&lt;/p>
&lt;h2 id="performance-monitoring">Performance monitoring&lt;/h2>
&lt;p>Sentry 的 performance monitoring 用 transaction 和 span 模型（和 OpenTelemetry 的 trace / span 概念相同）。&lt;/p>
&lt;p>Transaction 代表一個完整的操作（頁面載入、API 請求處理）。Span 是 transaction 內的子操作（database query、外部 API 呼叫）。Transaction 和 span 的 duration 構成操作的時間分佈。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p><strong>跟 Backend 04 的分工</strong>：本文從 client-side 使用角度說明 Sentry 的 error tracking、performance monitoring 與 session replay — SDK 怎麼埋、error 怎麼分群、release 怎麼追蹤。Server-side 平台治理（告警路由整合、SLI 指標設計、self-hosted vs SaaS 成本治理、跟 OTel 的整合）見 <a href="/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Backend 04 Sentry vendor page</a>。</p></blockquote>
<p>Sentry 的核心是 error tracking — 自動捕獲未處理的例外、提供 stack trace、自動分群（grouping）相同 root cause 的 error。在 error tracking 的基礎上，Sentry 擴展了 performance monitoring（transaction / span）和 session replay（重播使用者操作）。</p>
<h2 id="error-tracking">Error tracking</h2>
<p>Sentry 的 error tracking 架構有三個層次：SDK 端的自動捕獲、server 端的 issue grouping 和 UI 端的 issue management。</p>
<h3 id="自動捕獲">自動捕獲</h3>
<p>Sentry SDK 在各平台註冊全域錯誤處理器（和<a href="/blog/monitoring/03-sdk-design/auto-intercept/" data-link-title="自動攔截機制" data-link-desc="JS window.onerror / Flutter FlutterError.onError / Python sys.excepthook — 各平台攔截未捕獲例外的機制和限制">模組三 自動攔截</a>的機制相同）。捕獲到例外後，SDK 收集 stack trace、breadcrumbs（最近的使用者操作）、device context（OS / browser / device model）和自訂 tags，打包成 event 送到 Sentry server。</p>
<h3 id="issue-grouping">Issue grouping</h3>
<p>Sentry server 收到 error event 後，用 fingerprinting 演算法判斷這個 error 是否和已有的 issue 相同。預設的 fingerprinting 基於 stack trace 的 frame — 如果兩個 error 的 stack trace 指向同一個位置，歸入同一個 issue。</p>
<p>自訂 fingerprint 讓開發者控制 grouping 邏輯。例如：不同使用者觸發的同一個 API error 可能有不同的 stack trace（因為 call site 不同），但 root cause 相同 — 自訂 fingerprint 把它們歸入同一個 issue。</p>
<h3 id="issue-management">Issue management</h3>
<p>每個 issue 有狀態（unresolved / resolved / ignored）、指派（誰負責修復）、趨勢（這個 issue 的發生頻率是上升還是下降）。Sentry 的 UI 提供 issue 列表、趨勢圖、影響範圍（影響多少使用者）。</p>
<h2 id="performance-monitoring">Performance monitoring</h2>
<p>Sentry 的 performance monitoring 用 transaction 和 span 模型（和 OpenTelemetry 的 trace / span 概念相同）。</p>
<p>Transaction 代表一個完整的操作（頁面載入、API 請求處理）。Span 是 transaction 內的子操作（database query、外部 API 呼叫）。Transaction 和 span 的 duration 構成操作的時間分佈。</p>
<p>Performance monitoring 的價值是發現「慢」的問題 — P95 回應時間超過閾值、特定 span 佔了 transaction 80% 的時間。和 error tracking 互補：error 告訴你「什麼壞了」，performance 告訴你「什麼慢了」。</p>
<h2 id="session-replay">Session replay</h2>
<p>Session replay 錄製使用者的操作過程 — DOM 變化、滑鼠移動、點擊事件 — 在 Sentry UI 中重播。開發者可以看到「使用者在觸發 error 之前做了什麼操作」。</p>
<p>Session replay 的實作是 DOM snapshot + mutation recording。記錄的是 DOM 結構的變化（非螢幕錄影），在重播時重建 DOM。資料量比錄影小很多，但仍然是所有 Sentry 功能中資料量最大的。</p>
<p>隱私考量：session replay 會看到使用者輸入的內容（除非做 masking）。Sentry 提供 privacy configuration 控制哪些元素被 mask（輸入框、敏感資料區域）。</p>
<h2 id="自架方案和-sentry-的差距">自架方案和 Sentry 的差距</h2>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>自架方案</th>
          <th>Sentry</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Error 捕獲</td>
          <td>SDK 自動攔截</td>
          <td>SDK 自動攔截（相同）</td>
      </tr>
      <tr>
          <td>Issue grouping</td>
          <td>手動 grep 分群</td>
          <td>自動 fingerprinting + 自訂規則</td>
      </tr>
      <tr>
          <td>趨勢分析</td>
          <td>手動計數</td>
          <td>自動趨勢圖 + 告警</td>
      </tr>
      <tr>
          <td>Performance</td>
          <td>metric 事件 + 手動分析</td>
          <td>Transaction / span + 自動 P95</td>
      </tr>
      <tr>
          <td>Session replay</td>
          <td>無</td>
          <td>DOM recording + 重播 UI</td>
      </tr>
  </tbody>
</table>
<p>Sentry 的核心價值在 issue grouping 和趨勢分析 — 把大量 error event 歸類成可管理的 issue 列表，自動追蹤每個 issue 的趨勢。自架方案用 grep 做不到自動 grouping。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Firebase 的整合方案 → <a href="/blog/monitoring/06-commercial-comparison/firebase-suite/" data-link-title="Firebase 套件" data-link-desc="Crashlytics &#43; Analytics &#43; Remote Config 的整合 — Firebase 把 error tracking 和行為分析拆成獨立產品的設計取捨">Firebase 套件</a></li>
<li>Datadog 的全棧 APM → <a href="/blog/monitoring/06-commercial-comparison/datadog-rum/" data-link-title="Datadog RUM" data-link-desc="全棧 APM 的 client-side 觀點 — client action 到 server trace 的完整鏈路追蹤">Datadog RUM</a></li>
<li>自架 vs 商業的判斷 → <a href="/blog/monitoring/06-commercial-comparison/self-hosted-vs-commercial/" data-link-title="自架 vs 商業的判斷決策表" data-link-desc="使用者數、網路範圍、功能需求、合規要求四個維度判斷該自架還是用商業方案">自架 vs 商業的判斷決策表</a></li>
<li>自架方案的 error fingerprint 實作 → <a href="/blog/monitoring/04-collector/error-fingerprint/" data-link-title="Error Fingerprint 與去重分群" data-link-desc="把大量 error 事件歸組成可管理的 issue 列表 — fingerprint 演算法、message normalization、error_groups 表設計、自架方案的務實邊界">Error Fingerprint 與去重分群</a></li>
</ul>
]]></content:encoded></item><item><title>商業方案的事件類型對應</title><link>https://tarrragon.github.io/blog/monitoring/01-mental-model/commercial-event-mapping/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/01-mental-model/commercial-event-mapping/</guid><description>&lt;p>商業監控方案各自有不同的事件分類體系。理解它們的分類邏輯和四類事件（event / error / metric / lifecycle）的對應關係，才能在接入時正確映射自架方案的事件，避免資料遺漏或分類錯誤。&lt;/p>
&lt;h2 id="sentry">Sentry&lt;/h2>
&lt;p>Sentry 的核心概念是 error tracking，但已擴展到 performance monitoring 和 session replay。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>四類事件&lt;/th>
 &lt;th>Sentry 對應&lt;/th>
 &lt;th>說明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Event&lt;/td>
 &lt;td>Breadcrumb&lt;/td>
 &lt;td>使用者操作記錄在 breadcrumb trail，附加在 error 上&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error&lt;/td>
 &lt;td>Event（Exception type）&lt;/td>
 &lt;td>Sentry 的核心。自動捕獲 + 手動 captureException&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Metric&lt;/td>
 &lt;td>Transaction + Span&lt;/td>
 &lt;td>Performance monitoring 的度量單位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Lifecycle&lt;/td>
 &lt;td>Breadcrumb（navigation）&lt;/td>
 &lt;td>app 生命週期記錄為 navigation/system breadcrumb&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Sentry 的設計假設是「error 是主角，其他事件是 error 的 context」。Event 和 lifecycle 都以 breadcrumb 形式附加在 error 報告上，獨立查看的能力有限。Breadcrumb 預設保留最近 100 條且不可獨立查詢 — 它是 error 報告的附件，不是獨立的事件資料庫。Metric 對應的 Transaction + Span 則有獨立的 Performance 頁面可以查看，和 error 是不同的 UI 入口。如果主要需求是行為分析而非 error tracking，Sentry 的 breadcrumb 模型可能不夠用。&lt;/p>
&lt;h2 id="firebase-crashlytics--analytics">Firebase Crashlytics + Analytics&lt;/h2>
&lt;p>Firebase 把 error tracking 和行為分析拆成兩個獨立產品。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>四類事件&lt;/th>
 &lt;th>Firebase 對應&lt;/th>
 &lt;th>說明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Event&lt;/td>
 &lt;td>Analytics custom event&lt;/td>
 &lt;td>GA4 的 event，有 parameters 附加屬性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error&lt;/td>
 &lt;td>Crashlytics exception&lt;/td>
 &lt;td>fatal + non-fatal exception 分開處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Metric&lt;/td>
 &lt;td>Analytics event + parameters&lt;/td>
 &lt;td>用 event 的 parameters 記錄數值（無原生 metric）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Lifecycle&lt;/td>
 &lt;td>Analytics auto events&lt;/td>
 &lt;td>screen_view、app_open 等自動收集&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Firebase 的特點是 Crashlytics 和 Analytics 各自獨立運作 — error 資料在 Crashlytics console，行為資料在 Analytics console。Metric 沒有原生支援，只能用 Analytics event 的 parameters 欄位記錄數值（例如 &lt;code>event: 'page_load', parameters: {duration_ms: 320}&lt;/code>），查詢時需要在 BigQuery export 中自行聚合。兩個 console 之間的關聯需要手動（在 Crashlytics 的 custom key 中設定 user ID，再到 Analytics 用同一個 ID 查行為）。&lt;/p>
&lt;h2 id="datadog-rum">Datadog RUM&lt;/h2>
&lt;p>Datadog Real User Monitoring 從全棧 APM 的角度設計 client-side 監控。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>四類事件&lt;/th>
 &lt;th>Datadog RUM 對應&lt;/th>
 &lt;th>說明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Event&lt;/td>
 &lt;td>Action&lt;/td>
 &lt;td>使用者操作（click、tap、scroll）自動或手動捕獲&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error&lt;/td>
 &lt;td>Error&lt;/td>
 &lt;td>JS exception、network error、custom error&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Metric&lt;/td>
 &lt;td>Long Task + 自訂&lt;/td>
 &lt;td>長任務自動捕獲，自訂 metric 用 global context&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Lifecycle&lt;/td>
 &lt;td>View&lt;/td>
 &lt;td>頁面/畫面的進入和離開，自動偵測 SPA route 變換&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Datadog RUM 的特點是和 backend APM 的深度整合。Client-side 的 action 可以關聯到 server-side 的 trace，形成從按鈕點擊到 database query 的完整鏈路。自架方案通常做不到這個深度的跨層關聯。&lt;/p></description><content:encoded><![CDATA[<p>商業監控方案各自有不同的事件分類體系。理解它們的分類邏輯和四類事件（event / error / metric / lifecycle）的對應關係，才能在接入時正確映射自架方案的事件，避免資料遺漏或分類錯誤。</p>
<h2 id="sentry">Sentry</h2>
<p>Sentry 的核心概念是 error tracking，但已擴展到 performance monitoring 和 session replay。</p>
<table>
  <thead>
      <tr>
          <th>四類事件</th>
          <th>Sentry 對應</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Event</td>
          <td>Breadcrumb</td>
          <td>使用者操作記錄在 breadcrumb trail，附加在 error 上</td>
      </tr>
      <tr>
          <td>Error</td>
          <td>Event（Exception type）</td>
          <td>Sentry 的核心。自動捕獲 + 手動 captureException</td>
      </tr>
      <tr>
          <td>Metric</td>
          <td>Transaction + Span</td>
          <td>Performance monitoring 的度量單位</td>
      </tr>
      <tr>
          <td>Lifecycle</td>
          <td>Breadcrumb（navigation）</td>
          <td>app 生命週期記錄為 navigation/system breadcrumb</td>
      </tr>
  </tbody>
</table>
<p>Sentry 的設計假設是「error 是主角，其他事件是 error 的 context」。Event 和 lifecycle 都以 breadcrumb 形式附加在 error 報告上，獨立查看的能力有限。Breadcrumb 預設保留最近 100 條且不可獨立查詢 — 它是 error 報告的附件，不是獨立的事件資料庫。Metric 對應的 Transaction + Span 則有獨立的 Performance 頁面可以查看，和 error 是不同的 UI 入口。如果主要需求是行為分析而非 error tracking，Sentry 的 breadcrumb 模型可能不夠用。</p>
<h2 id="firebase-crashlytics--analytics">Firebase Crashlytics + Analytics</h2>
<p>Firebase 把 error tracking 和行為分析拆成兩個獨立產品。</p>
<table>
  <thead>
      <tr>
          <th>四類事件</th>
          <th>Firebase 對應</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Event</td>
          <td>Analytics custom event</td>
          <td>GA4 的 event，有 parameters 附加屬性</td>
      </tr>
      <tr>
          <td>Error</td>
          <td>Crashlytics exception</td>
          <td>fatal + non-fatal exception 分開處理</td>
      </tr>
      <tr>
          <td>Metric</td>
          <td>Analytics event + parameters</td>
          <td>用 event 的 parameters 記錄數值（無原生 metric）</td>
      </tr>
      <tr>
          <td>Lifecycle</td>
          <td>Analytics auto events</td>
          <td>screen_view、app_open 等自動收集</td>
      </tr>
  </tbody>
</table>
<p>Firebase 的特點是 Crashlytics 和 Analytics 各自獨立運作 — error 資料在 Crashlytics console，行為資料在 Analytics console。Metric 沒有原生支援，只能用 Analytics event 的 parameters 欄位記錄數值（例如 <code>event: 'page_load', parameters: {duration_ms: 320}</code>），查詢時需要在 BigQuery export 中自行聚合。兩個 console 之間的關聯需要手動（在 Crashlytics 的 custom key 中設定 user ID，再到 Analytics 用同一個 ID 查行為）。</p>
<h2 id="datadog-rum">Datadog RUM</h2>
<p>Datadog Real User Monitoring 從全棧 APM 的角度設計 client-side 監控。</p>
<table>
  <thead>
      <tr>
          <th>四類事件</th>
          <th>Datadog RUM 對應</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Event</td>
          <td>Action</td>
          <td>使用者操作（click、tap、scroll）自動或手動捕獲</td>
      </tr>
      <tr>
          <td>Error</td>
          <td>Error</td>
          <td>JS exception、network error、custom error</td>
      </tr>
      <tr>
          <td>Metric</td>
          <td>Long Task + 自訂</td>
          <td>長任務自動捕獲，自訂 metric 用 global context</td>
      </tr>
      <tr>
          <td>Lifecycle</td>
          <td>View</td>
          <td>頁面/畫面的進入和離開，自動偵測 SPA route 變換</td>
      </tr>
  </tbody>
</table>
<p>Datadog RUM 的特點是和 backend APM 的深度整合。Client-side 的 action 可以關聯到 server-side 的 trace，形成從按鈕點擊到 database query 的完整鏈路。自架方案通常做不到這個深度的跨層關聯。</p>
<h2 id="接入策略">接入策略</h2>
<p>接入商業方案時的映射原則：</p>
<p><strong>自架事件名稱是 source of truth</strong>。商業方案的事件名稱是自架名稱的映射，不是取代。映射邏輯集中在一個 adapter 層，商業方案更換時只改 adapter。</p>
<p><strong>不要為了配合商業方案改變自架的分類</strong>。Sentry 把 event 記錄為 breadcrumb 不代表自架方案也要把 event 降級成 error 的附屬品。自架的四類分類是語意正確的，商業方案的分類是它自己的產品設計。</p>
<p><strong>同時接入多個方案時做去重</strong>。Error 同時發到 Sentry 和 Crashlytics 會產生重複。在 adapter 層控制「哪類事件發到哪個方案」，避免同一個事件在多個 dashboard 出現。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>四類事件的定義 → <a href="/blog/monitoring/01-mental-model/four-event-types/" data-link-title="四類事件的完整定義" data-link-desc="Event / Error / Metric / Lifecycle 四類事件各自的語意、觸發時機和典型用途 — 分類是監控體系的統一語言">四類事件的完整定義</a></li>
<li>商業方案的深入比較 → <a href="/blog/monitoring/06-commercial-comparison/" data-link-title="模組六：商業方案對照" data-link-desc="Sentry / Crashlytics / Datadog RUM / Mixpanel — 自架 vs 商業的功能和成本取捨">模組六 商業方案比較</a></li>
<li>事件命名規範 → <a href="/blog/monitoring/01-mental-model/event-naming-convention/" data-link-title="事件命名規範" data-link-desc="namespace.action 格式的事件命名、命名一致性的工程價值、和商業方案命名慣例的對應">事件命名規範</a></li>
</ul>
]]></content:encoded></item><item><title>模組六：商業方案對照</title><link>https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/</guid><description>&lt;p>回答「什麼時候該從自架切換到商業方案」。&lt;/p>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 自架 vs 商業的判斷決策表（使用者數 / 網路範圍 / 功能需求 / 合規要求）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Sentry 深入（error + performance + session replay 的架構）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Firebase 套件（Crashlytics + Analytics + Remote Config 的整合）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Datadog RUM（全棧 APM 的 client-side 觀點）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Mixpanel / Amplitude（行為分析專用 vs 通用監控的差異）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 部署光譜（BaaS + Serverless / PaaS / 完全自架 / 商業 SaaS 四條路徑）&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/monitoring/08-business-analytics/" data-link-title="模組八：行為資料的商業利用" data-link-desc="Funnel / Cohort / Attribution / A/B test / 推薦系統 / RFM — 從 debug 工具到商業資產的翻轉">monitoring 模組八 商業利用&lt;/a>：商業方案的核心賣點是行為分析功能&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">backend 04 可觀測性&lt;/a>：server-side 商業方案（Datadog / New Relic）的對照&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>回答「什麼時候該從自架切換到商業方案」。</p>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 自架 vs 商業的判斷決策表（使用者數 / 網路範圍 / 功能需求 / 合規要求）</li>
<li><input checked="" disabled="" type="checkbox"> Sentry 深入（error + performance + session replay 的架構）</li>
<li><input checked="" disabled="" type="checkbox"> Firebase 套件（Crashlytics + Analytics + Remote Config 的整合）</li>
<li><input checked="" disabled="" type="checkbox"> Datadog RUM（全棧 APM 的 client-side 觀點）</li>
<li><input checked="" disabled="" type="checkbox"> Mixpanel / Amplitude（行為分析專用 vs 通用監控的差異）</li>
<li><input checked="" disabled="" type="checkbox"> 部署光譜（BaaS + Serverless / PaaS / 完全自架 / 商業 SaaS 四條路徑）</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/monitoring/08-business-analytics/" data-link-title="模組八：行為資料的商業利用" data-link-desc="Funnel / Cohort / Attribution / A/B test / 推薦系統 / RFM — 從 debug 工具到商業資產的翻轉">monitoring 模組八 商業利用</a>：商業方案的核心賣點是行為分析功能</li>
<li>→ <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">backend 04 可觀測性</a>：server-side 商業方案（Datadog / New Relic）的對照</li>
</ul>
]]></content:encoded></item><item><title>Sentry Error Grouping 與 Fingerprinting 策略</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/error-grouping-fingerprinting/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/error-grouping-fingerprinting/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry&lt;/a> 的 vendor deep article，深化 overview「Issue grouping / fingerprint」段。初次接觸 Sentry 的讀者建議先讀 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry 服務頁&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;h2 id="問題情境">問題情境&lt;/h2>
&lt;p>Error grouping 決定 Sentry 的使用體驗。Grouping 太粗（不同 bug 被合併成同一個 issue），團隊會漏掉新問題；grouping 太細（同一個 bug 被拆成數百個 issue），issue list 變成 noise。理解 Sentry 的 grouping 演算法跟自訂 fingerprint 機制，才能讓 issue list 反映真實的 bug 數量而非 error event 數量。&lt;/p>
&lt;h2 id="預設-grouping-演算法">預設 Grouping 演算法&lt;/h2>
&lt;h3 id="stack-trace-為主">Stack trace 為主&lt;/h3>
&lt;p>Sentry 的預設 grouping 策略以 exception type + stack trace 為核心。兩個 error event 會被歸到同一個 issue，如果它們的 exception type 相同、且 stack trace 的「相關 frame」相同。&lt;/p>
&lt;p>「相關 frame」是 Sentry 的判定結果 — 它會過濾掉標準函式庫、框架內部 frame 跟已知 noise frame，只留下 application code frame。這個過濾邏輯叫 stack trace rules，由 Sentry 的 grouping 引擎自動決定。&lt;/p>
&lt;h3 id="grouping-版本">Grouping 版本&lt;/h3>
&lt;p>Sentry 的 grouping 演算法有多個版本（稱為 grouping config）。新建的 project 自動用最新版（截至 2024 年是 &lt;code>newstyle:2023-01-11&lt;/code>），舊 project 可能還在用舊版。升級 grouping config 會改變 issue 的歸屬 — 之前合併的 event 可能被拆開，之前分開的可能合併。&lt;/p>
&lt;p>確認目前的 grouping config：Project Settings → General Settings → Event Grouping。升級前先用 Sentry 的 grouping preview 功能測試影響範圍。&lt;/p>
&lt;h3 id="非-exception-事件">非 exception 事件&lt;/h3>
&lt;p>沒有 stack trace 的事件（&lt;code>capture_message&lt;/code>、breadcrumb-only event、CSP violation）用 message 內容做 grouping。相同 message template 的事件歸到同一個 issue。&lt;/p>
&lt;p>message 中如果包含動態值（user ID、request ID、timestamp），Sentry 會嘗試辨識並忽略動態部分。但辨識不完美 — 如果 message 格式不一致，同一種錯誤可能被拆成多個 issue。&lt;/p>
&lt;h2 id="自訂-fingerprint">自訂 Fingerprint&lt;/h2>
&lt;h3 id="何時需要自訂">何時需要自訂&lt;/h3>
&lt;p>預設 grouping 不夠用的常見場景：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>問題&lt;/th>
 &lt;th>Fingerprint 解法&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>外部 API timeout&lt;/td>
 &lt;td>不同 caller 的 stack trace 不同，但根因相同&lt;/td>
 &lt;td>用 &lt;code>{{ default }}&lt;/code> + error type 做 fingerprint&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Database connection error&lt;/td>
 &lt;td>每個 query 的 stack trace 不同&lt;/td>
 &lt;td>用 error message pattern 做 fingerprint&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>前端 minified code&lt;/td>
 &lt;td>source map 缺失導致 frame 不穩定&lt;/td>
 &lt;td>先修 source map 上傳，而非硬 fingerprint&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rate limit / 429 error&lt;/td>
 &lt;td>大量 429 拆成數百個 issue&lt;/td>
 &lt;td>用 HTTP status code 做 fingerprint&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="server-side-fingerprint-rules">Server-side fingerprint rules&lt;/h3>
&lt;p>在 Project Settings → Issue Grouping → Fingerprint Rules 設定。語法：&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry</a> 的 vendor deep article，深化 overview「Issue grouping / fingerprint」段。初次接觸 Sentry 的讀者建議先讀 <a href="/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry 服務頁</a>。</p></blockquote>
<h2 id="問題情境">問題情境</h2>
<p>Error grouping 決定 Sentry 的使用體驗。Grouping 太粗（不同 bug 被合併成同一個 issue），團隊會漏掉新問題；grouping 太細（同一個 bug 被拆成數百個 issue），issue list 變成 noise。理解 Sentry 的 grouping 演算法跟自訂 fingerprint 機制，才能讓 issue list 反映真實的 bug 數量而非 error event 數量。</p>
<h2 id="預設-grouping-演算法">預設 Grouping 演算法</h2>
<h3 id="stack-trace-為主">Stack trace 為主</h3>
<p>Sentry 的預設 grouping 策略以 exception type + stack trace 為核心。兩個 error event 會被歸到同一個 issue，如果它們的 exception type 相同、且 stack trace 的「相關 frame」相同。</p>
<p>「相關 frame」是 Sentry 的判定結果 — 它會過濾掉標準函式庫、框架內部 frame 跟已知 noise frame，只留下 application code frame。這個過濾邏輯叫 stack trace rules，由 Sentry 的 grouping 引擎自動決定。</p>
<h3 id="grouping-版本">Grouping 版本</h3>
<p>Sentry 的 grouping 演算法有多個版本（稱為 grouping config）。新建的 project 自動用最新版（截至 2024 年是 <code>newstyle:2023-01-11</code>），舊 project 可能還在用舊版。升級 grouping config 會改變 issue 的歸屬 — 之前合併的 event 可能被拆開，之前分開的可能合併。</p>
<p>確認目前的 grouping config：Project Settings → General Settings → Event Grouping。升級前先用 Sentry 的 grouping preview 功能測試影響範圍。</p>
<h3 id="非-exception-事件">非 exception 事件</h3>
<p>沒有 stack trace 的事件（<code>capture_message</code>、breadcrumb-only event、CSP violation）用 message 內容做 grouping。相同 message template 的事件歸到同一個 issue。</p>
<p>message 中如果包含動態值（user ID、request ID、timestamp），Sentry 會嘗試辨識並忽略動態部分。但辨識不完美 — 如果 message 格式不一致，同一種錯誤可能被拆成多個 issue。</p>
<h2 id="自訂-fingerprint">自訂 Fingerprint</h2>
<h3 id="何時需要自訂">何時需要自訂</h3>
<p>預設 grouping 不夠用的常見場景：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>問題</th>
          <th>Fingerprint 解法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>外部 API timeout</td>
          <td>不同 caller 的 stack trace 不同，但根因相同</td>
          <td>用 <code>{{ default }}</code> + error type 做 fingerprint</td>
      </tr>
      <tr>
          <td>Database connection error</td>
          <td>每個 query 的 stack trace 不同</td>
          <td>用 error message pattern 做 fingerprint</td>
      </tr>
      <tr>
          <td>前端 minified code</td>
          <td>source map 缺失導致 frame 不穩定</td>
          <td>先修 source map 上傳，而非硬 fingerprint</td>
      </tr>
      <tr>
          <td>Rate limit / 429 error</td>
          <td>大量 429 拆成數百個 issue</td>
          <td>用 HTTP status code 做 fingerprint</td>
      </tr>
  </tbody>
</table>
<h3 id="server-side-fingerprint-rules">Server-side fingerprint rules</h3>
<p>在 Project Settings → Issue Grouping → Fingerprint Rules 設定。語法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl"># 所有 ConnectionError 歸成一個 issue
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">error.type:ConnectionError -&gt; connection-error
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"># 特定 message pattern 歸成一個 issue
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">message:&#34;Rate limit exceeded*&#34; -&gt; rate-limit
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"># 特定 module 的所有 error 歸成一組
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">module:payment.gateway.* -&gt; payment-gateway-error
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"># 組合條件
</span></span><span class="line"><span class="ln">11</span><span class="cl">error.type:TimeoutError module:external.api.* -&gt; external-api-timeout</span></span></code></pre></div><p>Server-side rules 的優先順序：越後面的 rule 優先順序越高。如果一個 event 匹配多條 rule，用最後一條。</p>
<h3 id="sdk-side-fingerprint">SDK-side fingerprint</h3>
<p>在 SDK 的 <code>before_send</code> callback 中設定 <code>event.fingerprint</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">before_send</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">hint</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;ConnectionError&#34;</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">hint</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;exc_info&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="n">event</span><span class="p">[</span><span class="s2">&#34;fingerprint&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;connection-error&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">return</span> <span class="n">event</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">sentry_sdk</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">dsn</span><span class="o">=</span><span class="s2">&#34;...&#34;</span><span class="p">,</span> <span class="n">before_send</span><span class="o">=</span><span class="n">before_send</span><span class="p">)</span></span></span></code></pre></div><p>SDK-side 跟 server-side 的差異：</p>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Server-side rules</th>
          <th>SDK-side fingerprint</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>設定位置</td>
          <td>Sentry Web UI</td>
          <td>程式碼</td>
      </tr>
      <tr>
          <td>部署速度</td>
          <td>即時生效</td>
          <td>需要 deploy</td>
      </tr>
      <tr>
          <td>可見性</td>
          <td>團隊都能看到跟修改</td>
          <td>散在程式碼裡</td>
      </tr>
      <tr>
          <td>複雜邏輯</td>
          <td>只支援 pattern matching</td>
          <td>可用任意程式邏輯</td>
      </tr>
  </tbody>
</table>
<p>優先用 server-side rules — 集中管理、即時生效。SDK-side 用在 server-side rules 表達不了的複雜邏輯。</p>
<h3 id="-default--組合"><code>{{ default }}</code> 組合</h3>
<p>Fingerprint 中的 <code>{{ default }}</code> 代表 Sentry 預設的 grouping 結果。跟自訂值組合使用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># 用預設 grouping + environment 維度拆分
</span></span><span class="line"><span class="ln">2</span><span class="cl">fingerprint: [&#34;{{ default }}&#34;, &#34;{{ environment }}&#34;]</span></span></code></pre></div><p>這樣同一個 bug 在 staging 跟 production 會分成兩個 issue，方便分別追蹤。</p>
<h2 id="merge-與-unmerge">Merge 與 Unmerge</h2>
<h3 id="事後修正">事後修正</h3>
<p>當 grouping 不準時，Sentry 提供事後修正：</p>
<p><strong>Merge</strong>：選擇多個 issue，合併成一個。合併後的 issue 保留所有 event，但只保留一個 issue ID。適合預設 grouping 太細（同一 bug 被拆成多個 issue）的情況。</p>
<p><strong>Unmerge</strong>（拆分）：從一個 issue 中選擇部分 event，拆出成新 issue。適合預設 grouping 太粗（不同 bug 被合在同一個 issue）的情況。</p>
<h3 id="mergeunmerge-的限制">Merge/Unmerge 的限制</h3>
<p>Merge 跟 Unmerge 都是「貼 OK 繃」— 只影響現有 event，新進的 event 仍然用原來的 grouping 邏輯。如果根因是 grouping 太粗或太細，應該修 fingerprint rule，而非持續 merge/unmerge。</p>
<p>判讀順序：</p>
<ol>
<li>發現 grouping 不準</li>
<li>先用 merge/unmerge 處理現有 issue（止血）</li>
<li>分析 root cause — 是 stack trace 不穩定、message 有動態值、還是缺 fingerprint rule</li>
<li>加 fingerprint rule 永久修正</li>
<li>驗證新進 event 的 grouping 是否正確</li>
</ol>
<h2 id="grouping-不準的判讀">Grouping 不準的判讀</h2>
<h3 id="太細的訊號">太細的訊號</h3>
<ul>
<li>Issue list 中出現大量「相似標題但不同 ID」的 issue</li>
<li>單一事件只有 1-2 個 occurrence 的 issue 大量出現</li>
<li>同一個使用者操作觸發的 error 被分散到多個 issue</li>
</ul>
<p>常見原因：message 中包含動態值（user ID、timestamp、request path）、source map 缺失（前端）、stack trace 包含 generated code frame。</p>
<h3 id="太粗的訊號">太粗的訊號</h3>
<ul>
<li>一個 issue 的 event 數量持續增長，但 event detail 看起來是不同問題</li>
<li>Issue 的 status 被 resolve 後馬上 regress，但新 event 跟原因不同</li>
<li>團隊 ignore 了一個「雜 issue」但裡面混著真正需要處理的 bug</li>
</ul>
<p>常見原因：exception type 太通用（<code>RuntimeError</code>、<code>Exception</code>）、fingerprint rule 太粗（把整個 module 的 error 合成一個 issue）。</p>
<h2 id="大量-unique-errors-的治理">大量 Unique Errors 的治理</h2>
<h3 id="問題issue-爆量">問題：Issue 爆量</h3>
<p>project 的 issue 數量超過數千時，issue list 失去可操作性。on-call 打開 Sentry 看到 2000 個 unresolved issue，等於沒有 triage。</p>
<h3 id="治理策略">治理策略</h3>
<p><strong>Inbound filter</strong>：在 Project Settings → Inbound Filters 設定，丟棄已知的 noise event（browser extension error、crawler error、legacy browser error）。丟棄在 ingestion 層，不消耗 quota。</p>
<p><strong>Rate limit</strong>：project 或 key 級別的 rate limit。超過限額的 event 被丟棄。適合防止單一 bug 的暴增 event 耗盡 quota，但不解決 issue 數量問題。</p>
<p><strong>Alert rule 搭配 ownership</strong>：用 Sentry alert rule 把特定 tag（service、team、module）的新 issue 通知對應 team。不是所有 issue 都要同一個人看。</p>
<p><strong>定期 triage cadence</strong>：每週或每兩週的 triage session，把 issue 分成 fix / ignore / merge 三類。Sentry 的 <code>For Review</code> tab 自動列出需要初次 triage 的 issue。</p>
<p><strong>Auto-resolve</strong>：設定 auto-resolve policy — 超過 N 天沒有新 event 的 issue 自動 resolve。避免舊 issue 永遠佔據 unresolved list。</p>
<h3 id="治理後的穩態">治理後的穩態</h3>
<p>合理的穩態是：unresolved issue 數量穩定在數十到數百，每週新增 issue 跟 resolve issue 數量大致平衡。如果 unresolved 持續增長，先檢查是否有 noise event 沒被 filter，或 fingerprint 太細。</p>
<h2 id="整合與下一步">整合與下一步</h2>
<ul>
<li>Error tracking 跟 observability 的邊界：Sentry 處理 error lifecycle、metrics/logs/traces 處理系統行為，見 <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality</a></li>
<li>OTel context 整合：Sentry SDK 接受 OTel trace_id / span_id，讓 error 跟 trace 關聯，見 <a href="/blog/backend/04-observability/vendors/opentelemetry/collector-deployment-patterns/" data-link-title="OTel Collector 部署模式：agent / gateway / sidecar 與 pipeline 設計" data-link-desc="說明 OpenTelemetry Collector 三種部署位置的責任分工、receivers/processors/exporters pipeline 設計，以及 collector 失效、記憶體壓力與 backpressure 的故障演練與容量邊界">OpenTelemetry Collector 部署模式</a></li>
<li>Release tracking 跟 session replay：見 <a href="../release-tracking-session-replay/">Release Tracking 與 Session Replay</a></li>
<li>事故響應整合：嚴重 issue → alert → on-call，見 <a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 Incident Response 模組</a></li>
</ul>
]]></content:encoded></item><item><title>Sentry Release Tracking 與 Session Replay</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/release-tracking-session-replay/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/release-tracking-session-replay/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry&lt;/a> 的 vendor deep article，深化 overview「Release / source map」跟「Session Replay」段。初次接觸 Sentry 的讀者建議先讀 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry 服務頁&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;h2 id="問題情境">問題情境&lt;/h2>
&lt;p>Release tracking 讓 Sentry 從「error 收集器」升級成「部署品質追蹤器」。每次部署標記一個 release，Sentry 自動計算 crash-free sessions、regressed errors 跟 release health。Session Replay 進一步把 error 的觸發脈絡從 stack trace 擴展到使用者操作錄影。兩者搭配使用時，團隊能看到「這個版本部署後、哪些使用者遇到什麼操作導致什麼錯誤」的完整鏈路。&lt;/p>
&lt;h2 id="release-health">Release Health&lt;/h2>
&lt;h3 id="核心概念">核心概念&lt;/h3>
&lt;p>Release health 追蹤每個版本的使用者體驗品質。核心指標：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>定義&lt;/th>
 &lt;th>健康閾值&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Crash-free sessions&lt;/td>
 &lt;td>沒有 unhandled error 的 session 百分比&lt;/td>
 &lt;td>99.5% 以上&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Crash-free users&lt;/td>
 &lt;td>沒有遇到 unhandled error 的使用者百分比&lt;/td>
 &lt;td>99.5% 以上&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Adoption rate&lt;/td>
 &lt;td>使用此版本的 session 佔比&lt;/td>
 &lt;td>依 rollout 策略&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error count&lt;/td>
 &lt;td>此版本的 error event 數量&lt;/td>
 &lt;td>不應比前一版高&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Crash-free sessions 跟 crash-free users 的差異：sessions 是頻率加權（一個使用者一天開 10 次 app，10 次都算），users 是去重的。Mobile app 通常看 crash-free users（使用者感知），web 通常看 crash-free sessions（頻率反映服務品質）。&lt;/p>
&lt;h3 id="release-標記">Release 標記&lt;/h3>
&lt;p>在 SDK 初始化時傳入 release 標記：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">sentry_sdk&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">init&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="n">dsn&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="n">release&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;checkout-api@1.2.3&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="n">environment&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;production&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Release 命名慣例：&lt;code>&amp;lt;service&amp;gt;@&amp;lt;version&amp;gt;&lt;/code> 或 git SHA。用語意版本方便比較，用 git SHA 方便對應 commit。CI/CD pipeline 在 deploy step 自動設定。&lt;/p>
&lt;h3 id="deploy-標記">Deploy 標記&lt;/h3>
&lt;p>Release 建立後，用 Sentry CLI 或 API 標記 deploy：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">sentry-cli releases deploys checkout-api@1.2.3 new &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> --env production &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> --started &lt;span class="k">$(&lt;/span>date -u +%s&lt;span class="k">)&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> --finished &lt;span class="k">$(&lt;/span>date -u +%s&lt;span class="k">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Deploy 標記讓 Sentry 知道某個 release 何時部署到哪個環境。issue list 的 &amp;ldquo;First seen in release&amp;rdquo; 跟 &amp;ldquo;Regressed in release&amp;rdquo; 依賴這個資訊。&lt;/p>
&lt;h3 id="regressed-error-偵測">Regressed Error 偵測&lt;/h3>
&lt;p>Sentry 會追蹤已 resolve 的 issue。如果新 release 重新觸發了已 resolve 的 issue，Sentry 標記為 regression。這比人工追蹤有效 — 團隊不需要記住哪些 bug 修過，Sentry 自動偵測回歸。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry</a> 的 vendor deep article，深化 overview「Release / source map」跟「Session Replay」段。初次接觸 Sentry 的讀者建議先讀 <a href="/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry 服務頁</a>。</p></blockquote>
<h2 id="問題情境">問題情境</h2>
<p>Release tracking 讓 Sentry 從「error 收集器」升級成「部署品質追蹤器」。每次部署標記一個 release，Sentry 自動計算 crash-free sessions、regressed errors 跟 release health。Session Replay 進一步把 error 的觸發脈絡從 stack trace 擴展到使用者操作錄影。兩者搭配使用時，團隊能看到「這個版本部署後、哪些使用者遇到什麼操作導致什麼錯誤」的完整鏈路。</p>
<h2 id="release-health">Release Health</h2>
<h3 id="核心概念">核心概念</h3>
<p>Release health 追蹤每個版本的使用者體驗品質。核心指標：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>定義</th>
          <th>健康閾值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Crash-free sessions</td>
          <td>沒有 unhandled error 的 session 百分比</td>
          <td>99.5% 以上</td>
      </tr>
      <tr>
          <td>Crash-free users</td>
          <td>沒有遇到 unhandled error 的使用者百分比</td>
          <td>99.5% 以上</td>
      </tr>
      <tr>
          <td>Adoption rate</td>
          <td>使用此版本的 session 佔比</td>
          <td>依 rollout 策略</td>
      </tr>
      <tr>
          <td>Error count</td>
          <td>此版本的 error event 數量</td>
          <td>不應比前一版高</td>
      </tr>
  </tbody>
</table>
<p>Crash-free sessions 跟 crash-free users 的差異：sessions 是頻率加權（一個使用者一天開 10 次 app，10 次都算），users 是去重的。Mobile app 通常看 crash-free users（使用者感知），web 通常看 crash-free sessions（頻率反映服務品質）。</p>
<h3 id="release-標記">Release 標記</h3>
<p>在 SDK 初始化時傳入 release 標記：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">sentry_sdk</span><span class="o">.</span><span class="n">init</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">dsn</span><span class="o">=</span><span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">release</span><span class="o">=</span><span class="s2">&#34;checkout-api@1.2.3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">environment</span><span class="o">=</span><span class="s2">&#34;production&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><p>Release 命名慣例：<code>&lt;service&gt;@&lt;version&gt;</code> 或 git SHA。用語意版本方便比較，用 git SHA 方便對應 commit。CI/CD pipeline 在 deploy step 自動設定。</p>
<h3 id="deploy-標記">Deploy 標記</h3>
<p>Release 建立後，用 Sentry CLI 或 API 標記 deploy：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">sentry-cli releases deploys checkout-api@1.2.3 new <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --env production <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --started <span class="k">$(</span>date -u +%s<span class="k">)</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --finished <span class="k">$(</span>date -u +%s<span class="k">)</span></span></span></code></pre></div><p>Deploy 標記讓 Sentry 知道某個 release 何時部署到哪個環境。issue list 的 &ldquo;First seen in release&rdquo; 跟 &ldquo;Regressed in release&rdquo; 依賴這個資訊。</p>
<h3 id="regressed-error-偵測">Regressed Error 偵測</h3>
<p>Sentry 會追蹤已 resolve 的 issue。如果新 release 重新觸發了已 resolve 的 issue，Sentry 標記為 regression。這比人工追蹤有效 — 團隊不需要記住哪些 bug 修過，Sentry 自動偵測回歸。</p>
<p>Regression 通知的準確度取決於 grouping 品質。如果 grouping 不準（見 <a href="../error-grouping-fingerprinting/">Error Grouping 與 Fingerprinting</a>），regression 偵測也會不準 — 不同 bug 被合成同一 issue 時，resolve 一個 bug 後另一個觸發會被誤判為 regression。</p>
<h3 id="source-map-上傳">Source map 上傳</h3>
<p>前端 minified code 的 stack trace 不可讀。上傳 source map 讓 Sentry 還原原始 source code 位置：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">sentry-cli releases files checkout-api@1.2.3 upload-sourcemaps <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --url-prefix <span class="s1">&#39;~/static/js&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  ./build/static/js</span></span></code></pre></div><p>Source map 上傳必須在 deploy 前完成，且 release 版本跟前端 build 版本一致。版本不一致時，Sentry 找不到對應的 source map，stack trace 仍然是 minified。</p>
<p>CI/CD 整合：在 build step 之後、deploy step 之前上傳 source map。多數框架（Next.js、Vite、Webpack）有 Sentry plugin 自動處理。</p>
<h2 id="session-replay">Session Replay</h2>
<h3 id="核心能力">核心能力</h3>
<p>Session Replay 錄製使用者在網頁上的操作。Sentry 記錄的是 DOM mutation 跟使用者事件的結構化資料，播放時 replay DOM 變化，效果類似影片但資料量遠小於螢幕錄影。</p>
<p>replay 跟 error 關聯：Sentry 在 error event 中附帶 replay ID，讓工程師從 issue detail 直接跳到 error 發生前後的使用者操作。</p>
<h3 id="隱私設定">隱私設定</h3>
<p>Session Replay 預設會遮罩敏感資訊：</p>
<table>
  <thead>
      <tr>
          <th>遮罩類型</th>
          <th>預設行為</th>
          <th>自訂方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>文字內容</td>
          <td>所有文字替換成 <code>*</code></td>
          <td><code>maskAllText: false</code> 關閉、或用 CSS class <code>sentry-mask</code> 指定</td>
      </tr>
      <tr>
          <td>輸入框</td>
          <td>所有 input value 遮罩</td>
          <td><code>maskAllInputs: false</code> 關閉（注意 PII 風險）</td>
      </tr>
      <tr>
          <td>圖片</td>
          <td>不遮罩（但 <code>&lt;img&gt;</code> 從原始 URL 載入）</td>
          <td><code>blockAllMedia: true</code> 遮蔽所有媒體</td>
      </tr>
      <tr>
          <td>特定元素</td>
          <td>不遮罩</td>
          <td>加 <code>data-sentry-block</code> attribute 完全隱藏</td>
      </tr>
  </tbody>
</table>
<p>PII 合規考量：</p>
<ul>
<li>預設 <code>maskAllText: true</code> + <code>maskAllInputs: true</code> 是安全起點</li>
<li>GDPR / CCPA 場景需要額外確認：replay 資料存在 Sentry SaaS（美國資料中心），跨境傳輸需要評估</li>
<li>Self-hosted Sentry 可以把 replay 資料留在自己的基礎設施</li>
</ul>
<h3 id="sampling-策略">Sampling 策略</h3>
<p>Session Replay 會增加前端 SDK 的 payload 大小跟 Sentry 的 event quota。用 sampling rate 控制：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">Sentry</span><span class="p">.</span><span class="nx">init</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nx">dsn</span><span class="o">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">replaysSessionSampleRate</span><span class="o">:</span> <span class="mf">0.1</span><span class="p">,</span>  <span class="c1">// 10% 的 session 錄影
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="nx">replaysOnErrorSampleRate</span><span class="o">:</span> <span class="mf">1.0</span><span class="p">,</span>  <span class="c1">// error 發生時 100% 錄影
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><p>推薦策略：<code>replaysSessionSampleRate</code> 用低值（1-10%），<code>replaysOnErrorSampleRate</code> 用 100%。目的是確保每個 error 都有 replay 可看，但不錄所有正常 session。</p>
<p>高流量網站（每日百萬 session 以上）可能需要把 <code>replaysSessionSampleRate</code> 設到 0，只在 error 時才錄。session replay 的 quota 消耗速度可以在 Sentry Usage Stats 頁面監控。</p>
<h2 id="performance-monitoring">Performance Monitoring</h2>
<h3 id="transaction-based-tracing">Transaction-based tracing</h3>
<p>Sentry 的 performance monitoring 用 transaction / span 結構（跟 OpenTelemetry 的 trace / span 概念對齊）。每個 HTTP request、page load 或自訂操作是一個 transaction，transaction 內的子操作是 span。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">with</span> <span class="n">sentry_sdk</span><span class="o">.</span><span class="n">start_transaction</span><span class="p">(</span><span class="n">op</span><span class="o">=</span><span class="s2">&#34;checkout&#34;</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s2">&#34;POST /api/checkout&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">with</span> <span class="n">sentry_sdk</span><span class="o">.</span><span class="n">start_span</span><span class="p">(</span><span class="n">op</span><span class="o">=</span><span class="s2">&#34;db&#34;</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;insert order&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="c1"># DB operation</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">with</span> <span class="n">sentry_sdk</span><span class="o">.</span><span class="n">start_span</span><span class="p">(</span><span class="n">op</span><span class="o">=</span><span class="s2">&#34;http&#34;</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;payment gateway&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="c1"># External API call</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><p>自動 instrumentation 會自動建立 transaction 跟 span（HTTP framework、DB driver、HTTP client）。手動 span 用在自訂業務邏輯或自動 instrumentation 沒覆蓋的路徑。</p>
<h3 id="otel-context-整合">OTel context 整合</h3>
<p>Sentry SDK 支援 OTel context propagation — 如果 upstream service 用 OTel SDK 產生 trace，Sentry SDK 會接受 <code>traceparent</code> header 中的 trace_id 跟 parent_span_id，把自己的 transaction 接到同一條 trace。</p>
<p>整合方式：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>設定</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Sentry SDK 接收 OTel context</td>
          <td>預設支援 W3C Trace Context、不需額外設定</td>
      </tr>
      <tr>
          <td>Sentry 資料送到 OTel backend</td>
          <td>用 Sentry 的 OTel exporter（experimental）</td>
      </tr>
      <tr>
          <td>OTel SDK 送資料到 Sentry</td>
          <td>OTel SDK → OTLP exporter → Sentry（Sentry 支援 OTLP ingestion）</td>
      </tr>
  </tbody>
</table>
<p>常見架構：backend service 用 OTel SDK + Collector，frontend 用 Sentry SDK（前端 error tracking 跟 session replay 是 Sentry 的強項）。兩者透過 trace_id 關聯，在 Sentry 看 frontend error + replay，在 OTel backend 看 backend trace。</p>
<h3 id="web-vitals">Web Vitals</h3>
<p>前端 SDK 自動收集 Core Web Vitals（LCP、FID / INP、CLS）跟 TTFB。這些指標跟 error 在同一個 dashboard，讓團隊在 release 後同時看 error regression 跟效能 regression。</p>
<p>Web Vitals 的觀測不需要額外設定 — 前端 SDK 自動收集。但 sampling rate 會影響資料量 — <code>tracesSampleRate</code> 設太低時，Web Vitals 的 sample 數量可能不夠做統計比較。</p>
<h2 id="self-hosted-vs-saas">Self-hosted vs SaaS</h2>
<h3 id="決策維度">決策維度</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>SaaS（sentry.io）</th>
          <th>Self-hosted</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>維運</td>
          <td>Sentry 負責</td>
          <td>自己維運（docker-compose、20+ 容器）</td>
      </tr>
      <tr>
          <td>資料位置</td>
          <td>Sentry 資料中心（美國為主）</td>
          <td>自己的基礎設施</td>
      </tr>
      <tr>
          <td>功能完整度</td>
          <td>全功能</td>
          <td>社群版功能略少（部分企業功能不含）</td>
      </tr>
      <tr>
          <td>升級</td>
          <td>自動</td>
          <td>手動（每月有新版、升級需要停機）</td>
      </tr>
      <tr>
          <td>成本模型</td>
          <td>Event-based pricing</td>
          <td>基礎設施 + 人力成本</td>
      </tr>
      <tr>
          <td>Replay / Profiling</td>
          <td>含</td>
          <td>含（但 storage 自負）</td>
      </tr>
  </tbody>
</table>
<h3 id="何時選-self-hosted">何時選 self-hosted</h3>
<p>資料必須留在特定地理區域（GDPR / 特定產業法規）、或企業 security policy 不允許 error data 送到第三方 — 這是 self-hosted 的核心理由。</p>
<p>Self-hosted Sentry 的維運成本常被低估：20+ 個容器（Kafka、ClickHouse、PostgreSQL、Redis、Snuba、Relay 等）、升級可能需要資料庫 migration、troubleshooting 時沒有 vendor 支援。中小團隊通常 SaaS 的 event pricing 比 self-hosted 的人力成本低。</p>
<h3 id="混合模式">混合模式</h3>
<p>部分團隊用混合模式：production error 送 Sentry SaaS（低維運），但 audit-sensitive 的資料（PII-heavy environment）走 self-hosted。兩套 Sentry instance 各自獨立，不共享 issue。</p>
<h2 id="整合與下一步">整合與下一步</h2>
<ul>
<li>Error grouping 策略：在 issue 數量失控前建立 fingerprint rule，見 <a href="../error-grouping-fingerprinting/">Error Grouping 與 Fingerprinting</a></li>
<li>觀測證據整合：把 Sentry issue link 放進 evidence package，見 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a></li>
<li>Client-side monitoring：Sentry 的前端 SDK 跟 RUM 的定位互補，見 <a href="/blog/backend/04-observability/client-side-monitoring/" data-link-title="4.10 Client-side / Synthetic / RUM" data-link-desc="補 server-side 看不到的 user perceived 訊號">4.10 Client-side Monitoring</a></li>
<li>事故響應整合：Sentry alert → PagerDuty / incident.io，見 <a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 Incident Response 模組</a></li>
</ul>
]]></content:encoded></item><item><title>Sentry → Honeycomb：trace 不是 error、是不同 observability paradigm</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/honeycomb/migrate-from-sentry/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/vendors/honeycomb/migrate-from-sentry/</guid><description>&lt;blockquote>
&lt;p>本文是跨 vendor migration playbook、cross-link &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/honeycomb/" data-link-title="Honeycomb" data-link-desc="High-cardinality observability 平台、events-based 模型">Honeycomb&lt;/a>。跑 &lt;a href="https://tarrragon.github.io/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">migration-playbook-methodology 6 維 audit&lt;/a> 後對映 &lt;em>Paradigm = High（error tracking ↔ wide-event observability）→ Type E paradigm shift&lt;/em>。&lt;/p>&lt;/blockquote>
&lt;h2 id="trace-不是-error是不同-paradigm">Trace 不是 error、是不同 paradigm&lt;/h2>
&lt;p>把 Sentry → Honeycomb 當「trace tool 替換」是最常見的誤判 — Sentry trace 是 &lt;em>error 上下文&lt;/em>、Honeycomb trace 是 &lt;em>observability 第一性&lt;/em>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>概念&lt;/th>
 &lt;th>Sentry&lt;/th>
 &lt;th>Honeycomb&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>核心 paradigm&lt;/td>
 &lt;td>Error tracking + transaction trace&lt;/td>
 &lt;td>High-cardinality wide-event observability&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>第一性 unit&lt;/td>
 &lt;td>Error event&lt;/td>
 &lt;td>Wide event (span with N fields)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Trace 角色&lt;/td>
 &lt;td>Error 的「附帶 context」&lt;/td>
 &lt;td>Observability 主軸、每 event 是 trace span&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Sampling&lt;/td>
 &lt;td>Error 全收 + transaction sample&lt;/td>
 &lt;td>Adaptive sampling、保留 &lt;em>anomaly&lt;/em>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Query model&lt;/td>
 &lt;td>Filter + group by + aggregation&lt;/td>
 &lt;td>High-cardinality 多維 query (BubbleUp / heatmap)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>User base&lt;/td>
 &lt;td>Developer (debug error)&lt;/td>
 &lt;td>SRE + Platform (debug system behavior)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cost model&lt;/td>
 &lt;td>Per-error event + transaction&lt;/td>
 &lt;td>Per-event (wide event volume)&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>核心差異不在「Honeycomb 是 better Sentry」、在「兩者是不同 observability paradigm」&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>Sentry 適合 &lt;em>application-level error debug&lt;/em> — 拿到 error stack trace + minimal context、快速 fix&lt;/li>
&lt;li>Honeycomb 適合 &lt;em>system-level behavior debug&lt;/em> — 看流量分佈 / 多維 correlation / 異常 outlier、找 &lt;em>為什麼這個 user 在這個時段在這個 endpoint 慢&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Migration scope 包含 &lt;em>paradigm reset&lt;/em> — 不是 SDK 換、是 SRE / Dev team 對 observability 的心智模型重設&lt;/strong>。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是跨 vendor migration playbook、cross-link <a href="/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry</a> 跟 <a href="/blog/backend/04-observability/vendors/honeycomb/" data-link-title="Honeycomb" data-link-desc="High-cardinality observability 平台、events-based 模型">Honeycomb</a>。跑 <a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">migration-playbook-methodology 6 維 audit</a> 後對映 <em>Paradigm = High（error tracking ↔ wide-event observability）→ Type E paradigm shift</em>。</p></blockquote>
<h2 id="trace-不是-error是不同-paradigm">Trace 不是 error、是不同 paradigm</h2>
<p>把 Sentry → Honeycomb 當「trace tool 替換」是最常見的誤判 — Sentry trace 是 <em>error 上下文</em>、Honeycomb trace 是 <em>observability 第一性</em>：</p>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>Sentry</th>
          <th>Honeycomb</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>核心 paradigm</td>
          <td>Error tracking + transaction trace</td>
          <td>High-cardinality wide-event observability</td>
      </tr>
      <tr>
          <td>第一性 unit</td>
          <td>Error event</td>
          <td>Wide event (span with N fields)</td>
      </tr>
      <tr>
          <td>Trace 角色</td>
          <td>Error 的「附帶 context」</td>
          <td>Observability 主軸、每 event 是 trace span</td>
      </tr>
      <tr>
          <td>Sampling</td>
          <td>Error 全收 + transaction sample</td>
          <td>Adaptive sampling、保留 <em>anomaly</em></td>
      </tr>
      <tr>
          <td>Query model</td>
          <td>Filter + group by + aggregation</td>
          <td>High-cardinality 多維 query (BubbleUp / heatmap)</td>
      </tr>
      <tr>
          <td>User base</td>
          <td>Developer (debug error)</td>
          <td>SRE + Platform (debug system behavior)</td>
      </tr>
      <tr>
          <td>Cost model</td>
          <td>Per-error event + transaction</td>
          <td>Per-event (wide event volume)</td>
      </tr>
  </tbody>
</table>
<p><strong>核心差異不在「Honeycomb 是 better Sentry」、在「兩者是不同 observability paradigm」</strong>：</p>
<ul>
<li>Sentry 適合 <em>application-level error debug</em> — 拿到 error stack trace + minimal context、快速 fix</li>
<li>Honeycomb 適合 <em>system-level behavior debug</em> — 看流量分佈 / 多維 correlation / 異常 outlier、找 <em>為什麼這個 user 在這個時段在這個 endpoint 慢</em></li>
</ul>
<p><strong>Migration scope 包含 <em>paradigm reset</em> — 不是 SDK 換、是 SRE / Dev team 對 observability 的心智模型重設</strong>。</p>
<h2 id="為什麼遷observability-成熟度--cardinality--cost-三條-driver">為什麼遷：observability 成熟度 / cardinality / cost 三條 driver</h2>
<table>
  <thead>
      <tr>
          <th>Driver</th>
          <th>觸發</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Observability 成熟度</td>
          <td>Application 規模到 <em>跨多 service / multi-tenant</em>、Sentry error tracking 不夠細、SRE 要看 <em>high-cardinality</em> 多維 query</td>
      </tr>
      <tr>
          <td>High-cardinality</td>
          <td>Sentry tag system 限制 cardinality（~1000 unique value）、Honeycomb native 支援 millions cardinality</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td>Per-error pricing 對 high-error volume 場景爆、Honeycomb per-event 在 <em>wide event</em> 場景更可預測</td>
      </tr>
  </tbody>
</table>
<p>反向 driver（Honeycomb → Sentry）：</p>
<ul>
<li>Pure error tracking 場景、Honeycomb wide-event 過度設計</li>
<li>Frontend / mobile 客戶端 error tracking、Sentry 對 web/mobile/desktop SDK 成熟度高</li>
</ul>
<h2 id="6-維-audit">6 維 audit</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>等級</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Schema / API</td>
          <td>Medium（event schema 概念不同、SDK 完全換）</td>
      </tr>
      <tr>
          <td>Operational</td>
          <td>Low（兩者都 SaaS、operational 對等）</td>
      </tr>
      <tr>
          <td>Paradigm</td>
          <td><strong>High</strong>（error tracking ↔ wide-event observability）</td>
      </tr>
      <tr>
          <td>Components</td>
          <td>Low（同 1 個 observability vendor）</td>
      </tr>
      <tr>
          <td>Application change</td>
          <td><strong>High</strong>（SDK 換 + instrumentation 重設計）</td>
      </tr>
      <tr>
          <td>Data topology</td>
          <td>Low</td>
      </tr>
  </tbody>
</table>
<p>Paradigm = High（其他 Low-Medium）→ Type E paradigm shift；application change 雖 High 但是 paradigm 的 downstream。</p>
<h2 id="結構partial-migration--混合架構是-long-term-default">結構：partial migration + 混合架構是 long-term default</h2>
<p>跟 <a href="/blog/backend/03-message-queue/vendors/kafka/migrate-from-to-nats/" data-link-title="Kafka ↔ NATS：不是 migration、是 messaging paradigm 重設計" data-link-desc="Kafka 跟 NATS 不是同類產品（log-based event streaming vs subject-based messaging）、&#39;migration&#39; 字面上不成立；本文釐清兩家 paradigm 邊界、什麼情境真的能換、application 模式重設計的 5 個踩雷（consumer offset 觀念差 / retention model / exactly-once 假設 / schema registry 缺位 / fan-out 模式差）、跟 JetStream 對位 &#43; 混合架構">Kafka ↔ NATS</a> / <a href="/blog/backend/02-cache-redis/vendors/redis/migrate-to-memcached/" data-link-title="Redis → Memcached：Memcached 不是 simpler Redis、是 cache paradigm" data-link-desc="Redis → Memcached 是 Type E paradigm reduction migration — 從 multi-paradigm（KV &#43; 資料結構 &#43; pub/sub &#43; Lua &#43; streams）退到 pure cache；不是「remove Redis features」、是「重新分配 Redis-specific feature 到對應 specialized 服務」；5 個 production 踩雷 &#43; paradigm reduction 路線">Redis → Memcached</a> 同 Type E pattern：</p>
<ul>
<li><strong>不存在 complete migration</strong>：Sentry 對 <em>frontend error tracking</em> 強項、Honeycomb 對 <em>backend system observability</em> 強項</li>
<li><strong>長期混合架構</strong>：frontend / mobile 保留 Sentry、backend / SRE 走 Honeycomb</li>
<li><strong>Application 重設計</strong>：instrumentation 用 OpenTelemetry、避免 vendor SDK lock-in</li>
</ul>
<h2 id="application-重設計範例">Application 重設計範例</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Before: Sentry SDK</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">sentry_sdk</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">sentry_sdk</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">dsn</span><span class="o">=</span><span class="s1">&#39;https://x@sentry.io/y&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">process_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">sentry_sdk</span><span class="o">.</span><span class="n">capture_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">raise</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># After: OpenTelemetry + Honeycomb</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry</span> <span class="kn">import</span> <span class="n">trace</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry.sdk.trace</span> <span class="kn">import</span> <span class="n">TracerProvider</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry.sdk.trace.export</span> <span class="kn">import</span> <span class="n">BatchSpanProcessor</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry.exporter.otlp.proto.grpc.trace_exporter</span> <span class="kn">import</span> <span class="n">OTLPSpanExporter</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">trace</span><span class="o">.</span><span class="n">set_tracer_provider</span><span class="p">(</span><span class="n">TracerProvider</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">trace</span><span class="o">.</span><span class="n">get_tracer_provider</span><span class="p">()</span><span class="o">.</span><span class="n">add_span_processor</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">BatchSpanProcessor</span><span class="p">(</span><span class="n">OTLPSpanExporter</span><span class="p">(</span><span class="n">endpoint</span><span class="o">=</span><span class="s1">&#39;https://api.honeycomb.io&#39;</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s1">&#39;x-honeycomb-team&#39;</span><span class="p">:</span> <span class="s1">&#39;YOUR_API_KEY&#39;</span><span class="p">}))</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">tracer</span> <span class="o">=</span> <span class="n">trace</span><span class="o">.</span><span class="n">get_tracer</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s1">&#39;process_order&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s1">&#39;order.id&#39;</span><span class="p">,</span> <span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s1">&#39;user.id&#39;</span><span class="p">,</span> <span class="n">user_id</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s1">&#39;order.amount&#39;</span><span class="p">,</span> <span class="n">order</span><span class="o">.</span><span class="n">amount</span><span class="p">)</span>  <span class="c1"># high-cardinality 自然</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s1">&#39;order.region&#39;</span><span class="p">,</span> <span class="n">region</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">process_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">span</span><span class="o">.</span><span class="n">set_status</span><span class="p">(</span><span class="n">trace</span><span class="o">.</span><span class="n">Status</span><span class="p">(</span><span class="n">trace</span><span class="o">.</span><span class="n">StatusCode</span><span class="o">.</span><span class="n">OK</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">span</span><span class="o">.</span><span class="n">set_status</span><span class="p">(</span><span class="n">trace</span><span class="o">.</span><span class="n">Status</span><span class="p">(</span><span class="n">trace</span><span class="o">.</span><span class="n">StatusCode</span><span class="o">.</span><span class="n">ERROR</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">span</span><span class="o">.</span><span class="n">record_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">raise</span></span></span></code></pre></div><p>差異：</p>
<ul>
<li>Sentry 只 capture exception + 簡 context</li>
<li>Honeycomb 對每 operation 寫 <em>wide event</em> 含 high-cardinality field（user.id / order.amount / order.region）</li>
<li>SRE 端能跑 <code>WHERE order.region = &quot;us-west-2&quot; AND duration &gt; 5000</code> 的 multi-dim query</li>
</ul>
<h2 id="migration-流程">Migration 流程</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">1. Audit application：列所有 Sentry SDK 使用 + capture pattern
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">2. 分類處理 plan:
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   - Pure error tracking (frontend): 保留 Sentry
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   - Backend system trace: 切 Honeycomb / OTel
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   - Error + context (混合): 雙寫期 evaluate
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">3. OpenTelemetry instrumentation 化:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   - 用 OTel SDK 取代 vendor SDK
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   - Honeycomb 是 OTLP target、跟 vendor lock 解耦
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">4. Backend application 切 Honeycomb (3-6 個月)
</span></span><span class="line"><span class="ln">10</span><span class="cl">5. Frontend / mobile 保留 Sentry
</span></span><span class="line"><span class="ln">11</span><span class="cl">6. SRE training: Honeycomb BubbleUp / heatmap / multi-dim query</span></span></code></pre></div><h2 id="production-故障演練">Production 故障演練</h2>
<h3 id="case-1event-schema-對位失敗sre-不會用-bubbleup">Case 1：Event schema 對位失敗、SRE 不會用 BubbleUp</h3>
<p><strong>徵兆</strong>：切 Honeycomb 後 SRE 用 Sentry 思維 — 找 error → fix；Honeycomb BubbleUp / heatmap 沒人會用、observability 退化到 <em>只看 error count</em>。</p>
<p><strong>根因</strong>：Sentry → Honeycomb migration 不只是 tool 換、是 <em>observability mindset 換</em>；SRE 沒培訓 wide-event query / BubbleUp anomaly detection。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>SRE training</strong>：1-2 週 hands-on Honeycomb BubbleUp + heatmap + multi-dim query</li>
<li><strong>Migration scope 含 sample query playbook</strong>：每個 incident type 對應 Honeycomb query 寫成 runbook</li>
<li><strong>保留 Sentry frontend / mobile</strong>：不要逼 SRE 全切、保留 <em>paradigm fit</em> 的部分</li>
</ol>
<h3 id="case-2sampling-行為差production-cost-飛">Case 2：Sampling 行為差、production cost 飛</h3>
<p><strong>徵兆</strong>：切 Honeycomb 後第 1 個月 event volume 比 Sentry 高 100x；帳單暴漲。</p>
<p><strong>根因</strong>：Sentry 對 transaction 端 sample（10% 預設）、error 全收；Honeycomb 端 <em>每 span 都 wide event</em>、application 端沒設 sampling 全送、event volume 爆。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>Honeycomb Refinery (sampling proxy)</strong>：deploy refinery 在 application 端跟 Honeycomb 之間、tail-based sampling</li>
<li><strong>Sample rule</strong>：保留 <em>anomaly</em> (error / slow / outlier)、drop <em>boring success</em> 90%+</li>
<li><strong>Cost monitoring 第一週密集</strong>：cardinality + event volume + cost dashboard、catch 預期外 spike</li>
</ol>
<h3 id="case-3error-grouping-失效">Case 3：Error grouping 失效</h3>
<p><strong>徵兆</strong>：切 Honeycomb 後 <em>相似 error</em> 沒被 group 成「同類 issue」、SRE 看每 event 獨立、failure 模式淹沒在 noise。</p>
<p><strong>根因</strong>：Sentry 自動 error grouping (by stack trace fingerprint)、Honeycomb 沒對等 — wide event 是 first-class、event grouping 需要 application 端 explicit 設 <code>error.type</code> field。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>Application 端設 error type field</strong>：<code>span.set_attribute('error.type', exception_class)</code></li>
<li><strong>Honeycomb derived column</strong>：用 derived column 算 error fingerprint</li>
<li><strong>保留 Sentry error tracking</strong>：純 error grouping 場景 Sentry 強項、別硬切</li>
</ol>
<h3 id="case-4cost-模型差預估錯">Case 4：Cost 模型差、預估錯</h3>
<p><strong>徵兆</strong>：切 Honeycomb 後預估 50% cost saving、實際只省 10-15%。</p>
<p><strong>根因</strong>：Sentry per-error pricing 對 error-heavy application 貴；Honeycomb per-event pricing 對 <em>wide event volume</em> application 貴；如果 application 是 <em>event volume 高 但 error 少</em>、Honeycomb 反而貴。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>Pre-migration 估</strong>：用 OTel pilot 跑 1-2 週、估真實 event volume</li>
<li><strong>Sample rule 設計</strong>：retention 7 天 hot + 30 天 cold + 1 年 archive、降 cost</li>
<li><strong>混合架構保留</strong>：frontend / mobile 走 Sentry、backend 走 Honeycomb、避免一邊 cost 爆</li>
</ol>
<h3 id="case-5alert-paradigm-不對等">Case 5：Alert paradigm 不對等</h3>
<p><strong>徵兆</strong>：Sentry alert 簡單（error rate / latency p99 threshold）、Honeycomb trigger 配置複雜（SLO + burn rate + BubbleUp）；SOC 學習曲線 1-2 個月。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>Migration 含 alert rebuild scope</strong>：Honeycomb trigger 不直接對位 Sentry alert、要重寫</li>
<li><strong>SLO-driven alert</strong>：用 Honeycomb SLO 取代 Sentry threshold alert、降 alert fatigue</li>
<li><strong>PagerDuty integration</strong>：兩家都支援、routing rule 跟 dedup 要 review</li>
</ol>
<h2 id="capacity--cost">Capacity / cost</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Sentry</th>
          <th>Honeycomb</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pricing model</td>
          <td>Per-error + transaction</td>
          <td>Per-event (wide event)</td>
      </tr>
      <tr>
          <td>Cost (mid-tier)</td>
          <td>$500-2000 / mo</td>
          <td>$400-3000 / mo (依 event volume)</td>
      </tr>
      <tr>
          <td>Sampling</td>
          <td>Built-in transaction sampling</td>
          <td>Refinery (additional component)</td>
      </tr>
      <tr>
          <td>Cardinality</td>
          <td>~1000 unique value / tag</td>
          <td>Millions / field</td>
      </tr>
      <tr>
          <td>Application complexity</td>
          <td>Low (SDK + capture exception)</td>
          <td>Medium (OTel + wide event instrument)</td>
      </tr>
      <tr>
          <td>Migration cost</td>
          <td>-</td>
          <td>2-4 FTE × 2-3 個月</td>
      </tr>
  </tbody>
</table>
<h2 id="整合--下一步">整合 / 下一步</h2>
<h3 id="跟-opentelemetry-整合">跟 OpenTelemetry 整合</h3>
<p>OTel 是 vendor-neutral instrumentation、Honeycomb 是 OTLP backend；application 端 OTel 化後可以同時 ship 到多個 backend（dev 端 Jaeger / production 端 Honeycomb / fallback 端 Tempo）。</p>
<h3 id="跟-datadog--grafana-stack-對位">跟 <a href="/blog/backend/04-observability/vendors/datadog/migrate-to-grafana-stack/" data-link-title="Datadog → Grafana Stack：把 $50K/month bill 拆解到 self-hosted observability" data-link-desc="Datadog 五層計費（host APM / metric / log ingest / log retention / RUM）拆解、對位 Grafana Stack（Mimir / Loki / Tempo / Grafana / Alloy）的 5 層責任；OTel-based agent migration、5 個 production 踩雷（cardinality 爆 / log volume cost / dashboard 不直接轉 / alert routing 換邏輯 / SLO definition 差異）、cost reality check">Datadog → Grafana Stack</a> 對位</h3>
<p>兩條 observability 路線：</p>
<ul>
<li>Grafana Stack (Mimir / Loki / Tempo)：self-host or Grafana Cloud、open source baseline</li>
<li>Honeycomb：SaaS-only、focus wide-event observability</li>
</ul>
<p>選擇取決於 <em>observability paradigm</em>：trace-heavy 走 Tempo / Honeycomb、metric-heavy 走 Mimir / Datadog。</p>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>Source vendor：<a href="/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Sentry</a></li>
<li>Target vendor：<a href="/blog/backend/04-observability/vendors/honeycomb/" data-link-title="Honeycomb" data-link-desc="High-cardinality observability 平台、events-based 模型">Honeycomb</a></li>
<li>平行 migration playbook (Type E)：<a href="/blog/backend/03-message-queue/vendors/kafka/migrate-from-to-nats/" data-link-title="Kafka ↔ NATS：不是 migration、是 messaging paradigm 重設計" data-link-desc="Kafka 跟 NATS 不是同類產品（log-based event streaming vs subject-based messaging）、&#39;migration&#39; 字面上不成立；本文釐清兩家 paradigm 邊界、什麼情境真的能換、application 模式重設計的 5 個踩雷（consumer offset 觀念差 / retention model / exactly-once 假設 / schema registry 缺位 / fan-out 模式差）、跟 JetStream 對位 &#43; 混合架構">Kafka ↔ NATS</a> / <a href="/blog/backend/02-cache-redis/vendors/redis/migrate-to-memcached/" data-link-title="Redis → Memcached：Memcached 不是 simpler Redis、是 cache paradigm" data-link-desc="Redis → Memcached 是 Type E paradigm reduction migration — 從 multi-paradigm（KV &#43; 資料結構 &#43; pub/sub &#43; Lua &#43; streams）退到 pure cache；不是「remove Redis features」、是「重新分配 Redis-specific feature 到對應 specialized 服務」；5 個 production 踩雷 &#43; paradigm reduction 路線">Redis → Memcached</a> / <a href="/blog/backend/05-deployment-platform/vendors/consul/migrate-from-etcd/" data-link-title="etcd → Consul：KV &#43; N 個 extras feature matrix" data-link-desc="etcd → Consul 是 Type E paradigm shift expansion — 從 pure KV store 升到 service mesh / discovery / health check / multi-DC；本文用對照表 &#43; paradigm expansion 路線、5 個 production 踩雷（API 對位 / lock semantics / watch event model / multi-DC topology / ACL system）">etcd → Consul</a></li>
<li>Methodology：<a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">Migration playbook methodology</a></li>
</ul>
]]></content:encoded></item></channel></rss>