<?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>Error-Tracking on Tarragon</title><link>https://tarrragon.github.io/blog/tags/error-tracking/</link><description>Recent content in Error-Tracking 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/error-tracking/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>Developer Dashboard 設計</title><link>https://tarrragon.github.io/blog/monitoring/04-collector/dashboard-developer/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/04-collector/dashboard-developer/</guid><description>&lt;p>Developer dashboard 聚焦 error 追蹤和 debug。開發者的核心問題是「哪裡壞了、影響多少人、怎麼重現」。這個 dashboard 的所有視圖都圍繞 error 事件展開，其他三類事件（event / metric / lifecycle）作為 debug context 輔助。&lt;/p>
&lt;p>和 &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/dashboard-devops/" data-link-title="DevOps Dashboard 設計" data-link-desc="Collector 和 SDK 是否健康 — 日常監控的服務狀態卡、吞吐量曲線、儲存用量，以及告警觸發後的排障視圖">DevOps dashboard&lt;/a> 的差異：DevOps 看「基礎設施是否健康」，Developer 看「程式碼是否正確」。Error 趨勢上升在 DevOps 眼中是「事件量異常」，在 Developer 眼中是「程式碼 bug」。&lt;/p>
&lt;h2 id="日常監控視圖">日常監控視圖&lt;/h2>
&lt;h3 id="error-摘要">Error 摘要&lt;/h3>
&lt;p>一個數字卡顯示最近 24 小時的 error 總數 + 和前一天的比較（上升 / 下降 / 持平）。旁邊標注「新 error」數量 — 過去 24 小時首次出現的 error name。&lt;/p>
&lt;p>新 error 的偵測邏輯：&lt;code>error.name&lt;/code> 在最近 24 小時的事件中存在、但在更早的事件中不存在。這是開發者最需要立即注意的 — 新版本引入的 bug 通常表現為「之前沒見過的 error name」。&lt;/p>
&lt;h3 id="error-列表">Error 列表&lt;/h3>
&lt;p>表格按 &lt;code>error.name&lt;/code> 分群，每行顯示：error 名稱、最近 24 小時出現次數、影響的 session 數、首次出現時間、最近出現時間。按出現次數降序排列。&lt;/p>
&lt;p>點擊某行進入 Error 詳情視圖。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- SQLite 層可用
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">COUNT&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">count&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">COUNT&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">DISTINCT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">session_id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">sessions&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">MIN&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ts&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">first_seen&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">MAX&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ts&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">last_seen&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">type&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;error&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">AND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ts&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;gt;=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">datetime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;now&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;-1 day&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">GROUP&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">ORDER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">count&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">DESC&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="error-趨勢">Error 趨勢&lt;/h3>
&lt;p>折線圖顯示過去 7 天每天的 error 數量。可選按 &lt;code>error.name&lt;/code> 過濾看單一 error 的趨勢，或看全部 error 的總趨勢。&lt;/p>
&lt;p>趨勢的判讀訊號：&lt;/p>
&lt;ul>
&lt;li>穩定持平 → 已知的 recurring error，排優先處理&lt;/li>
&lt;li>新版本部署後突然上升 → 該版本引入的 regression&lt;/li>
&lt;li>逐漸上升 → 累積性問題（記憶體洩漏、資源耗盡）&lt;/li>
&lt;/ul>
&lt;h3 id="版本健康">版本健康&lt;/h3>
&lt;p>按 &lt;code>source.version&lt;/code> 分群的 error 率比較。每個版本顯示：error 數量、error rate（error / 總事件比）、最常見的 error name。&lt;/p>
&lt;p>版本健康視圖幫助判斷「這個版本該不該 rollback」— 如果新版本的 error rate 顯著高於前一版，rollback 決策有數字依據。&lt;/p>
&lt;h2 id="debug-深入視圖">Debug 深入視圖&lt;/h2>
&lt;p>從日常監控的 Error 列表點擊某個 error 進入深入視圖。&lt;/p>
&lt;h3 id="error-詳情">Error 詳情&lt;/h3>
&lt;p>單個 error name 的完整資訊：&lt;/p>
&lt;ul>
&lt;li>Stack trace（最近一次出現的 &lt;code>error.data.stack_trace&lt;/code>）&lt;/li>
&lt;li>首次出現時間和總出現次數&lt;/li>
&lt;li>影響的 session 數和佔比&lt;/li>
&lt;li>按版本分佈（哪些版本有、哪些沒有）&lt;/li>
&lt;li>按平台分佈（iOS / Android / Web）&lt;/li>
&lt;li>最近 10 次出現的時間軸&lt;/li>
&lt;/ul>
&lt;h3 id="session-回放">Session 回放&lt;/h3>
&lt;p>選擇一個受影響的 session，顯示該 session 的完整事件序列。事件按時間排列，每筆事件顯示類型、名稱、時間、data 摘要。Error 事件用顯眼的樣式標記，讓開發者快速定位「error 發生前使用者做了什麼」。&lt;/p></description><content:encoded><![CDATA[<p>Developer dashboard 聚焦 error 追蹤和 debug。開發者的核心問題是「哪裡壞了、影響多少人、怎麼重現」。這個 dashboard 的所有視圖都圍繞 error 事件展開，其他三類事件（event / metric / lifecycle）作為 debug context 輔助。</p>
<p>和 <a href="/blog/monitoring/04-collector/dashboard-devops/" data-link-title="DevOps Dashboard 設計" data-link-desc="Collector 和 SDK 是否健康 — 日常監控的服務狀態卡、吞吐量曲線、儲存用量，以及告警觸發後的排障視圖">DevOps dashboard</a> 的差異：DevOps 看「基礎設施是否健康」，Developer 看「程式碼是否正確」。Error 趨勢上升在 DevOps 眼中是「事件量異常」，在 Developer 眼中是「程式碼 bug」。</p>
<h2 id="日常監控視圖">日常監控視圖</h2>
<h3 id="error-摘要">Error 摘要</h3>
<p>一個數字卡顯示最近 24 小時的 error 總數 + 和前一天的比較（上升 / 下降 / 持平）。旁邊標注「新 error」數量 — 過去 24 小時首次出現的 error name。</p>
<p>新 error 的偵測邏輯：<code>error.name</code> 在最近 24 小時的事件中存在、但在更早的事件中不存在。這是開發者最需要立即注意的 — 新版本引入的 bug 通常表現為「之前沒見過的 error name」。</p>
<h3 id="error-列表">Error 列表</h3>
<p>表格按 <code>error.name</code> 分群，每行顯示：error 名稱、最近 24 小時出現次數、影響的 session 數、首次出現時間、最近出現時間。按出現次數降序排列。</p>
<p>點擊某行進入 Error 詳情視圖。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- SQLite 層可用
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="n">name</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">       </span><span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="k">count</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">       </span><span class="k">COUNT</span><span class="p">(</span><span class="k">DISTINCT</span><span class="w"> </span><span class="n">session_id</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">sessions</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">       </span><span class="k">MIN</span><span class="p">(</span><span class="n">ts</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">first_seen</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">       </span><span class="k">MAX</span><span class="p">(</span><span class="n">ts</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">last_seen</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">events</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="k">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;error&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">  </span><span class="k">AND</span><span class="w"> </span><span class="n">ts</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">datetime</span><span class="p">(</span><span class="s1">&#39;now&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;-1 day&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="k">GROUP</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">name</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="k">count</span><span class="w"> </span><span class="k">DESC</span><span class="p">;</span></span></span></code></pre></div><h3 id="error-趨勢">Error 趨勢</h3>
<p>折線圖顯示過去 7 天每天的 error 數量。可選按 <code>error.name</code> 過濾看單一 error 的趨勢，或看全部 error 的總趨勢。</p>
<p>趨勢的判讀訊號：</p>
<ul>
<li>穩定持平 → 已知的 recurring error，排優先處理</li>
<li>新版本部署後突然上升 → 該版本引入的 regression</li>
<li>逐漸上升 → 累積性問題（記憶體洩漏、資源耗盡）</li>
</ul>
<h3 id="版本健康">版本健康</h3>
<p>按 <code>source.version</code> 分群的 error 率比較。每個版本顯示：error 數量、error rate（error / 總事件比）、最常見的 error name。</p>
<p>版本健康視圖幫助判斷「這個版本該不該 rollback」— 如果新版本的 error rate 顯著高於前一版，rollback 決策有數字依據。</p>
<h2 id="debug-深入視圖">Debug 深入視圖</h2>
<p>從日常監控的 Error 列表點擊某個 error 進入深入視圖。</p>
<h3 id="error-詳情">Error 詳情</h3>
<p>單個 error name 的完整資訊：</p>
<ul>
<li>Stack trace（最近一次出現的 <code>error.data.stack_trace</code>）</li>
<li>首次出現時間和總出現次數</li>
<li>影響的 session 數和佔比</li>
<li>按版本分佈（哪些版本有、哪些沒有）</li>
<li>按平台分佈（iOS / Android / Web）</li>
<li>最近 10 次出現的時間軸</li>
</ul>
<h3 id="session-回放">Session 回放</h3>
<p>選擇一個受影響的 session，顯示該 session 的完整事件序列。事件按時間排列，每筆事件顯示類型、名稱、時間、data 摘要。Error 事件用顯眼的樣式標記，讓開發者快速定位「error 發生前使用者做了什麼」。</p>
<p>Session 回放需要同一個 session_id 的所有四類事件。這是 event-enumeration-method 中「Debug — 最近操作」事件的核心消費場景。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- SQLite 層可用
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="k">type</span><span class="p">,</span><span class="w"> </span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">ts</span><span class="p">,</span><span class="w"> </span><span class="k">data</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">events</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="n">session_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">ts</span><span class="p">;</span></span></span></code></pre></div><h3 id="平台分佈">平台分佈</h3>
<p>某個 error name 在不同平台和 OS 版本的分佈圖。幫助判斷「這個 error 是全平台問題、還是特定平台的 bug」。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- SQLite 層可用
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="n">json_extract</span><span class="p">(</span><span class="k">source</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;$.platform&#39;</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">platform</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">       </span><span class="n">json_extract</span><span class="p">(</span><span class="k">source</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;$.os&#39;</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">os_version</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">       </span><span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="k">count</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">events</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="k">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;error&#39;</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w"></span><span class="k">GROUP</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">platform</span><span class="p">,</span><span class="w"> </span><span class="n">os_version</span><span class="p">;</span></span></span></code></pre></div><h2 id="事件覆蓋確認">事件覆蓋確認</h2>
<p>Developer dashboard 需要的所有事件在目前的事件設計中已完整覆蓋：</p>
<table>
  <thead>
      <tr>
          <th>視圖</th>
          <th>需要的事件</th>
          <th>對應的事件名稱</th>
          <th>覆蓋狀態</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Error 列表</td>
          <td>error GROUP BY name</td>
          <td><code>app.exception</code></td>
          <td>已覆蓋</td>
      </tr>
      <tr>
          <td>Error 趨勢</td>
          <td>error 時間序列</td>
          <td><code>app.exception</code></td>
          <td>已覆蓋</td>
      </tr>
      <tr>
          <td>版本比較</td>
          <td>error GROUP BY source.version</td>
          <td><code>app.exception</code> + source schema</td>
          <td>已覆蓋</td>
      </tr>
      <tr>
          <td>Session 回放</td>
          <td>同 session 全部事件</td>
          <td>四類事件 + session_id</td>
          <td>已覆蓋</td>
      </tr>
      <tr>
          <td>Stack trace</td>
          <td>error.data.stack_trace</td>
          <td><code>app.exception</code> data 欄位</td>
          <td>已覆蓋</td>
      </tr>
      <tr>
          <td>影響範圍</td>
          <td>COUNT DISTINCT session_id</td>
          <td>session_id schema</td>
          <td>已覆蓋</td>
      </tr>
      <tr>
          <td>平台分佈</td>
          <td>GROUP BY source.platform</td>
          <td>source schema</td>
          <td>已覆蓋</td>
      </tr>
  </tbody>
</table>
<h2 id="sqlite-層-vs-postgresql-層">SQLite 層 vs PostgreSQL 層</h2>
<p>Developer dashboard 的多數視圖在 SQLite 層就能運作 — 都是單表 GROUP BY 和 WHERE 過濾。</p>
<table>
  <thead>
      <tr>
          <th>視圖</th>
          <th>SQLite 層</th>
          <th>PostgreSQL 層新增</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Error 列表</td>
          <td>可用</td>
          <td></td>
      </tr>
      <tr>
          <td>Error 趨勢</td>
          <td>可用（7 天以內）</td>
          <td>長期趨勢（30 天以上）</td>
      </tr>
      <tr>
          <td>版本比較</td>
          <td>可用</td>
          <td></td>
      </tr>
      <tr>
          <td>Session 回放</td>
          <td>可用</td>
          <td></td>
      </tr>
      <tr>
          <td>平台分佈</td>
          <td>可用</td>
          <td></td>
      </tr>
      <tr>
          <td>Error 詳情</td>
          <td>可用</td>
          <td></td>
      </tr>
      <tr>
          <td>跨版本 P95 回應</td>
          <td>不可用</td>
          <td>percentile 函數</td>
      </tr>
  </tbody>
</table>
<p>開發者 debug 場景不需要 PostgreSQL — SQLite 層的查詢能力已涵蓋所有核心視圖。PostgreSQL 的需求來自效能指標的高級分析（P95 趨勢），但這屬於效能監控動機而非 debug 動機。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>DevOps dashboard 設計 → <a href="/blog/monitoring/04-collector/dashboard-devops/" data-link-title="DevOps Dashboard 設計" data-link-desc="Collector 和 SDK 是否健康 — 日常監控的服務狀態卡、吞吐量曲線、儲存用量，以及告警觸發後的排障視圖">DevOps Dashboard 設計</a></li>
<li>中台 dashboard 設計 → <a href="/blog/monitoring/04-collector/dashboard-business/" data-link-title="中台 Dashboard 設計" data-link-desc="使用者怎麼用、在哪流失、怎麼讓他們回來 — 營運和行銷的日常指標監控與深入分析視圖，全部需要 PostgreSQL 層">中台 Dashboard 設計</a></li>
<li>Error 事件的枚舉方法 → <a href="/blog/monitoring/01-mental-model/event-enumeration-method/" data-link-title="事件枚舉與補齊檢查" data-link-desc="從操作盤點系統性地推導出完整的事件清單 — 四類補齊檢查確保沒有遺漏、粒度判準確保每個事件只記一個事實">事件枚舉與補齊檢查</a></li>
<li>功能分層與 Backend 選擇 → <a href="/blog/monitoring/04-collector/feature-tier-boundary/" data-link-title="功能分層與 Backend 選擇" data-link-desc="SQLite 層和 PostgreSQL 層各自承載哪些功能 — 分界線是查詢模式而非資料量、觸發升級的是功能需求而非規模成長">功能分層與 Backend 選擇</a></li>
<li>Error fingerprint 分群取代 name 分群 → <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>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></channel></rss>