<?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>Event-Design on Tarragon</title><link>https://tarrragon.github.io/blog/tags/event-design/</link><description>Recent content in Event-Design on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Sat, 20 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/event-design/index.xml" rel="self" type="application/rss+xml"/><item><title>行為事件設計</title><link>https://tarrragon.github.io/blog/monitoring/08-business-analytics/behavior-event-design/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/08-business-analytics/behavior-event-design/</guid><description>&lt;p>行為事件是使用者操作的結構化記錄，每一筆事件回答「誰、在什麼時候、做了什麼、結果如何」。行為分析的品質上限由事件設計決定 — 事件粒度太粗無法回答細節問題，事件粒度太細讓儲存和查詢成本失控。&lt;/p>
&lt;h2 id="事件命名">事件命名&lt;/h2>
&lt;p>行為事件的命名遵循 &lt;code>namespace.action&lt;/code> 格式（&lt;a href="https://tarrragon.github.io/blog/monitoring/01-mental-model/event-naming-convention/" data-link-title="事件命名規範" data-link-desc="namespace.action 格式的事件命名、命名一致性的工程價值、和商業方案命名慣例的對應">模組一 事件命名規範&lt;/a>）。行為分析場景對命名的額外要求是：同一個 funnel 內的事件要能用 namespace 前綴篩選。&lt;/p>
&lt;p>例：註冊流程的事件用共同前綴 &lt;code>signup&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">signup.page.view 使用者看到註冊頁
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">signup.form.submit 使用者送出表單
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">signup.email.verify 使用者點擊驗證信連結
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">signup.complete 註冊完成&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>用 &lt;code>signup.*&lt;/code> 就能篩選出整個註冊流程的事件，不需要事先知道每一步的完整名稱。&lt;/p>
&lt;h2 id="屬性設計">屬性設計&lt;/h2>
&lt;p>每個事件除了名稱，還帶有屬性（properties / parameters）描述事件的 context。屬性分成三層：&lt;/p>
&lt;h3 id="通用屬性每個事件都有">通用屬性（每個事件都有）&lt;/h3>
&lt;ul>
&lt;li>&lt;code>timestamp&lt;/code>：事件發生的時間（UTC，毫秒精度）&lt;/li>
&lt;li>&lt;code>session_id&lt;/code>：當次使用的 session 識別碼&lt;/li>
&lt;li>&lt;code>user_id&lt;/code>：使用者識別碼（去識別化後，見 &lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">模組七&lt;/a>）&lt;/li>
&lt;li>&lt;code>platform&lt;/code>：iOS / Android / Web&lt;/li>
&lt;li>&lt;code>app_version&lt;/code>：app 版本號&lt;/li>
&lt;/ul>
&lt;h3 id="事件類型屬性同類事件共有">事件類型屬性（同類事件共有）&lt;/h3>
&lt;ul>
&lt;li>頁面瀏覽事件：&lt;code>page_name&lt;/code>、&lt;code>referrer&lt;/code>&lt;/li>
&lt;li>按鈕點擊事件：&lt;code>button_id&lt;/code>、&lt;code>button_text&lt;/code>&lt;/li>
&lt;li>搜尋事件：&lt;code>query&lt;/code>、&lt;code>result_count&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="事件專屬屬性特定事件才有">事件專屬屬性（特定事件才有）&lt;/h3>
&lt;ul>
&lt;li>&lt;code>signup.form.submit&lt;/code>：&lt;code>form_method&lt;/code>（email / Google / Apple）&lt;/li>
&lt;li>&lt;code>purchase.complete&lt;/code>：&lt;code>amount&lt;/code>、&lt;code>currency&lt;/code>、&lt;code>product_id&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>屬性設計的判斷標準是：這個屬性是否用於回答一個分析問題。「註冊方式的轉換率差異」需要 &lt;code>form_method&lt;/code> 屬性；如果沒有這個分析問題，就不需要這個屬性。&lt;/p>
&lt;h2 id="funnel-定義">Funnel 定義&lt;/h2>
&lt;p>Funnel 是一連串有順序的事件，代表使用者完成一個目標的步驟。Funnel 定義在事件設計階段完成 — 決定哪些事件構成一個 funnel、順序是什麼、每步之間的最大時間間隔。&lt;/p>
&lt;p>定義一個 funnel 需要：&lt;/p>
&lt;p>&lt;strong>步驟清單&lt;/strong>：funnel 包含哪些事件，順序是什麼。&lt;/p>
&lt;p>&lt;strong>時間窗口&lt;/strong>：步驟之間的最大間隔。使用者在步驟 A 之後 30 天才做步驟 B，是否算在同一個 funnel 內？時間窗口的設定取決於業務場景 — 電商結帳 funnel 通常是 30 分鐘，SaaS onboarding funnel 可能是 7 天。&lt;/p>
&lt;p>&lt;strong>完成條件&lt;/strong>：什麼算「完成」funnel。到達最後一步即完成，還是需要特定屬性值（&lt;code>purchase.complete&lt;/code> 且 &lt;code>status = success&lt;/code>）。&lt;/p>
&lt;h2 id="過度收集的成本">過度收集的成本&lt;/h2>
&lt;p>行為事件收集的邊界是「能回答已知的分析問題」。收集超出分析需求的事件有三個成本：&lt;/p>
&lt;p>&lt;strong>儲存成本&lt;/strong>：每個事件佔一行 JSONL。高頻事件（每次滾動、每次 hover）的資料量遠大於低頻事件（按鈕點擊、頁面瀏覽）。&lt;/p>
&lt;p>&lt;strong>隱私風險&lt;/strong>：收集的事件越多，包含可識別個人行為模式的風險越高（&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">模組七 資安與隱私&lt;/a>）。&lt;/p>
&lt;p>&lt;strong>噪音&lt;/strong>：分析時需要從大量事件中篩選出有意義的模式。事件越多，訊噪比越低。&lt;/p>
&lt;p>設計好的行為事件直接成為 &lt;a href="https://tarrragon.github.io/blog/monitoring/08-business-analytics/funnel-analysis/" data-link-title="Funnel Analysis" data-link-desc="使用者在哪一步流失 — 從事件序列計算每步轉換率、找出流失最嚴重的步驟、區分設計問題和技術問題">Funnel analysis&lt;/a> 的輸入 — funnel 的每一步對應一個行為事件。行為事件在四類事件分類中屬於 Event 類，完整的分類定義見&lt;a href="https://tarrragon.github.io/blog/monitoring/01-mental-model/four-event-types/" data-link-title="四類事件的完整定義" data-link-desc="Event / Error / Metric / Lifecycle 四類事件各自的語意、觸發時機和典型用途 — 分類是監控體系的統一語言">模組一 四類事件定義&lt;/a>。收集行為事件前必須完成&lt;a href="https://tarrragon.github.io/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">去識別化&lt;/a> — 使用者行為模式本身就是可識別資訊。&lt;/p></description><content:encoded><![CDATA[<p>行為事件是使用者操作的結構化記錄，每一筆事件回答「誰、在什麼時候、做了什麼、結果如何」。行為分析的品質上限由事件設計決定 — 事件粒度太粗無法回答細節問題，事件粒度太細讓儲存和查詢成本失控。</p>
<h2 id="事件命名">事件命名</h2>
<p>行為事件的命名遵循 <code>namespace.action</code> 格式（<a href="/blog/monitoring/01-mental-model/event-naming-convention/" data-link-title="事件命名規範" data-link-desc="namespace.action 格式的事件命名、命名一致性的工程價值、和商業方案命名慣例的對應">模組一 事件命名規範</a>）。行為分析場景對命名的額外要求是：同一個 funnel 內的事件要能用 namespace 前綴篩選。</p>
<p>例：註冊流程的事件用共同前綴 <code>signup</code>：</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">signup.page.view          使用者看到註冊頁
</span></span><span class="line"><span class="ln">2</span><span class="cl">signup.form.submit        使用者送出表單
</span></span><span class="line"><span class="ln">3</span><span class="cl">signup.email.verify       使用者點擊驗證信連結
</span></span><span class="line"><span class="ln">4</span><span class="cl">signup.complete           註冊完成</span></span></code></pre></div><p>用 <code>signup.*</code> 就能篩選出整個註冊流程的事件，不需要事先知道每一步的完整名稱。</p>
<h2 id="屬性設計">屬性設計</h2>
<p>每個事件除了名稱，還帶有屬性（properties / parameters）描述事件的 context。屬性分成三層：</p>
<h3 id="通用屬性每個事件都有">通用屬性（每個事件都有）</h3>
<ul>
<li><code>timestamp</code>：事件發生的時間（UTC，毫秒精度）</li>
<li><code>session_id</code>：當次使用的 session 識別碼</li>
<li><code>user_id</code>：使用者識別碼（去識別化後，見 <a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">模組七</a>）</li>
<li><code>platform</code>：iOS / Android / Web</li>
<li><code>app_version</code>：app 版本號</li>
</ul>
<h3 id="事件類型屬性同類事件共有">事件類型屬性（同類事件共有）</h3>
<ul>
<li>頁面瀏覽事件：<code>page_name</code>、<code>referrer</code></li>
<li>按鈕點擊事件：<code>button_id</code>、<code>button_text</code></li>
<li>搜尋事件：<code>query</code>、<code>result_count</code></li>
</ul>
<h3 id="事件專屬屬性特定事件才有">事件專屬屬性（特定事件才有）</h3>
<ul>
<li><code>signup.form.submit</code>：<code>form_method</code>（email / Google / Apple）</li>
<li><code>purchase.complete</code>：<code>amount</code>、<code>currency</code>、<code>product_id</code></li>
</ul>
<p>屬性設計的判斷標準是：這個屬性是否用於回答一個分析問題。「註冊方式的轉換率差異」需要 <code>form_method</code> 屬性；如果沒有這個分析問題，就不需要這個屬性。</p>
<h2 id="funnel-定義">Funnel 定義</h2>
<p>Funnel 是一連串有順序的事件，代表使用者完成一個目標的步驟。Funnel 定義在事件設計階段完成 — 決定哪些事件構成一個 funnel、順序是什麼、每步之間的最大時間間隔。</p>
<p>定義一個 funnel 需要：</p>
<p><strong>步驟清單</strong>：funnel 包含哪些事件，順序是什麼。</p>
<p><strong>時間窗口</strong>：步驟之間的最大間隔。使用者在步驟 A 之後 30 天才做步驟 B，是否算在同一個 funnel 內？時間窗口的設定取決於業務場景 — 電商結帳 funnel 通常是 30 分鐘，SaaS onboarding funnel 可能是 7 天。</p>
<p><strong>完成條件</strong>：什麼算「完成」funnel。到達最後一步即完成，還是需要特定屬性值（<code>purchase.complete</code> 且 <code>status = success</code>）。</p>
<h2 id="過度收集的成本">過度收集的成本</h2>
<p>行為事件收集的邊界是「能回答已知的分析問題」。收集超出分析需求的事件有三個成本：</p>
<p><strong>儲存成本</strong>：每個事件佔一行 JSONL。高頻事件（每次滾動、每次 hover）的資料量遠大於低頻事件（按鈕點擊、頁面瀏覽）。</p>
<p><strong>隱私風險</strong>：收集的事件越多，包含可識別個人行為模式的風險越高（<a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">模組七 資安與隱私</a>）。</p>
<p><strong>噪音</strong>：分析時需要從大量事件中篩選出有意義的模式。事件越多，訊噪比越低。</p>
<p>設計好的行為事件直接成為 <a href="/blog/monitoring/08-business-analytics/funnel-analysis/" data-link-title="Funnel Analysis" data-link-desc="使用者在哪一步流失 — 從事件序列計算每步轉換率、找出流失最嚴重的步驟、區分設計問題和技術問題">Funnel analysis</a> 的輸入 — funnel 的每一步對應一個行為事件。行為事件在四類事件分類中屬於 Event 類，完整的分類定義見<a href="/blog/monitoring/01-mental-model/four-event-types/" data-link-title="四類事件的完整定義" data-link-desc="Event / Error / Metric / Lifecycle 四類事件各自的語意、觸發時機和典型用途 — 分類是監控體系的統一語言">模組一 四類事件定義</a>。收集行為事件前必須完成<a href="/blog/monitoring/07-security-privacy/" data-link-title="模組七：資安與隱私" data-link-desc="SDK redaction / transport 加密 / collector access control / 去識別化 — 蒐集的資料本身就是風險資產">去識別化</a> — 使用者行為模式本身就是可識別資訊。</p>
]]></content:encoded></item><item><title>事件枚舉與補齊檢查</title><link>https://tarrragon.github.io/blog/monitoring/01-mental-model/event-enumeration-method/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/01-mental-model/event-enumeration-method/</guid><description>&lt;p>事件枚舉的目的是為一個服務建立完整的事件清單 — 每個事件有明確的類型、名稱、觸發時機和 data schema。枚舉的方法從操作盤點出發，經過四類補齊檢查，產出可以直接實作 SDK 埋點的事件表。&lt;/p>
&lt;h2 id="從操作盤點推導事件">從操作盤點推導事件&lt;/h2>
&lt;p>每個使用者操作（BDD 操作盤點的產物）至少對應一個 event 類型的事件。操作的失敗路徑對應 error 類型。操作涉及的效能測量對應 metric 類型。操作觸發的系統狀態轉換對應 lifecycle 類型。&lt;/p>
&lt;p>推導鏈：操作 → 四類事件候選 → 命名 → data schema。&lt;/p>
&lt;p>以一個透過 WebSocket 連接遠端終端機的 app 為例，「連線到終端機」這個操作推導出的事件：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>四類&lt;/th>
 &lt;th>事件名稱&lt;/th>
 &lt;th>觸發時機&lt;/th>
 &lt;th>data schema&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>event&lt;/td>
 &lt;td>terminal.connect.start&lt;/td>
 &lt;td>使用者點擊連線按鈕&lt;/td>
 &lt;td>&lt;code>{url, trigger: &amp;quot;manual&amp;quot; | &amp;quot;auto&amp;quot;}&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>event&lt;/td>
 &lt;td>terminal.connect.done&lt;/td>
 &lt;td>連線成功、開始接收 output&lt;/td>
 &lt;td>&lt;code>{url, duration_ms}&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>error&lt;/td>
 &lt;td>terminal.connect.failed&lt;/td>
 &lt;td>連線失敗（逾時、拒絕、認證失敗）&lt;/td>
 &lt;td>&lt;code>{url, error, step}&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>metric&lt;/td>
 &lt;td>terminal.connect.duration&lt;/td>
 &lt;td>連線完成（成功或失敗）&lt;/td>
 &lt;td>&lt;code>{duration_ms, success: bool}&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>lifecycle&lt;/td>
 &lt;td>ws.connected&lt;/td>
 &lt;td>WebSocket 連線狀態轉換&lt;/td>
 &lt;td>&lt;code>{url}&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>lifecycle&lt;/td>
 &lt;td>ws.disconnected&lt;/td>
 &lt;td>WebSocket 斷線&lt;/td>
 &lt;td>&lt;code>{url, reason, code}&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>一個操作推導出六個事件 — 因為這個操作跨越了使用者行為（event）、可能失敗（error）、有效能測量（metric）、涉及系統狀態轉換（lifecycle）四個面向。其中 &lt;code>connect.done&lt;/code> 和 &lt;code>connect.duration&lt;/code> 記錄的是同一事實的兩個面向（見下方邊界案例段），自用場景合併成 &lt;code>connect.done&lt;/code> 帶 &lt;code>duration_ms&lt;/code> 欄位更簡潔。&lt;/p>
&lt;h2 id="四類補齊檢查">四類補齊檢查&lt;/h2>
&lt;p>列完所有操作的事件後，對每個功能區域跑一次四類補齊檢查 — 逐列確認每一類是否都有對應的事件。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>功能區域&lt;/th>
 &lt;th>event&lt;/th>
 &lt;th>error&lt;/th>
 &lt;th>metric&lt;/th>
 &lt;th>lifecycle&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>連線&lt;/td>
 &lt;td>connect.start / connect.done&lt;/td>
 &lt;td>connect.failed&lt;/td>
 &lt;td>connect.duration&lt;/td>
 &lt;td>ws.connected / ws.disconnected&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>認證&lt;/td>
 &lt;td>auth.biometric.attempt&lt;/td>
 &lt;td>auth.biometric.failed&lt;/td>
 &lt;td>auth.duration&lt;/td>
 &lt;td>auth.state_changed&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>輸入&lt;/td>
 &lt;td>input.submit&lt;/td>
 &lt;td>input.parse_error&lt;/td>
 &lt;td>—&lt;/td>
 &lt;td>—&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>配對&lt;/td>
 &lt;td>enrollment.qr.scan / enrollment.done&lt;/td>
 &lt;td>enrollment.failed&lt;/td>
 &lt;td>enrollment.duration&lt;/td>
 &lt;td>—&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>空格是候選遺漏。每個空格問一個問題：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>event 空&lt;/strong>：「這個功能區域有使用者操作嗎？」有 → 補事件；沒有（純系統內部）→ 合理的空格&lt;/li>
&lt;li>&lt;strong>error 空&lt;/strong>：「這個功能區域能失敗嗎？」能 → 補事件；不能失敗的功能極少 → 再想一次&lt;/li>
&lt;li>&lt;strong>metric 空&lt;/strong>：「這個功能區域有值得量測的效能指標嗎？」有 → 補事件；操作瞬間完成且不涉及外部依賴 → 合理的空格&lt;/li>
&lt;li>&lt;strong>lifecycle 空&lt;/strong>：「這個功能區域涉及系統狀態轉換嗎？」有 → 補事件；純資料操作不改系統狀態 → 合理的空格&lt;/li>
&lt;/ul>
&lt;p>上表中「輸入」的 metric 和 lifecycle 空格是合理的 — 文字輸入送出不涉及效能量測和系統狀態轉換。「配對」的 lifecycle 空格也合理 — 配對完成後不改變系統的執行狀態。&lt;/p>
&lt;h2 id="粒度判準">粒度判準&lt;/h2>
&lt;p>事件粒度的判斷用一個 SRP 判準：&lt;strong>一個事件記一個事實&lt;/strong>。&lt;/p>
&lt;h3 id="拆分訊號">拆分訊號&lt;/h3>
&lt;p>一個事件記了兩個獨立的事實 → 拆成兩個事件。&lt;/p>
&lt;p>&lt;code>terminal.connect_and_auth&lt;/code> 同時記錄「連線建立」和「認證通過」。這兩個事實的失敗模式不同（連線失敗是網路問題、認證失敗是帳密問題）、觸發時機不同、消費者不同。拆成 &lt;code>terminal.connect.done&lt;/code> 和 &lt;code>auth.token.sent&lt;/code>。&lt;/p>
&lt;h3 id="合併訊號">合併訊號&lt;/h3>
&lt;p>兩個事件永遠同時觸發且消費者相同 → 合併成一個事件。&lt;/p>
&lt;p>&lt;code>terminal.input.keystroke&lt;/code> 和 &lt;code>terminal.input.keystroke_logged&lt;/code> 永遠同時觸發（每個按鍵一次），data schema 相同。合併成一個 &lt;code>terminal.input.keystroke&lt;/code>。&lt;/p>
&lt;h3 id="邊界案例">邊界案例&lt;/h3>
&lt;p>&lt;code>connect.done&lt;/code> 同時記 event 和 metric（成功事件 + duration）。這是一個事實（連線完成）的兩個面向，可以合併成一個事件帶 &lt;code>duration_ms&lt;/code> 欄位，也可以拆成 event 和 metric 兩筆。判斷依據是查詢需求 — 如果 funnel 分析和效能分析會分開查，拆開讓各自的查詢更簡單；如果都在同一個 dashboard 看，合併減少事件量。&lt;/p>
&lt;h2 id="data-schema-設計">data schema 設計&lt;/h2>
&lt;p>每個事件的 data 欄位回答「發生了什麼的 context」。設計原則：&lt;/p></description><content:encoded><![CDATA[<p>事件枚舉的目的是為一個服務建立完整的事件清單 — 每個事件有明確的類型、名稱、觸發時機和 data schema。枚舉的方法從操作盤點出發，經過四類補齊檢查，產出可以直接實作 SDK 埋點的事件表。</p>
<h2 id="從操作盤點推導事件">從操作盤點推導事件</h2>
<p>每個使用者操作（BDD 操作盤點的產物）至少對應一個 event 類型的事件。操作的失敗路徑對應 error 類型。操作涉及的效能測量對應 metric 類型。操作觸發的系統狀態轉換對應 lifecycle 類型。</p>
<p>推導鏈：操作 → 四類事件候選 → 命名 → data schema。</p>
<p>以一個透過 WebSocket 連接遠端終端機的 app 為例，「連線到終端機」這個操作推導出的事件：</p>
<table>
  <thead>
      <tr>
          <th>四類</th>
          <th>事件名稱</th>
          <th>觸發時機</th>
          <th>data schema</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>event</td>
          <td>terminal.connect.start</td>
          <td>使用者點擊連線按鈕</td>
          <td><code>{url, trigger: &quot;manual&quot; | &quot;auto&quot;}</code></td>
      </tr>
      <tr>
          <td>event</td>
          <td>terminal.connect.done</td>
          <td>連線成功、開始接收 output</td>
          <td><code>{url, duration_ms}</code></td>
      </tr>
      <tr>
          <td>error</td>
          <td>terminal.connect.failed</td>
          <td>連線失敗（逾時、拒絕、認證失敗）</td>
          <td><code>{url, error, step}</code></td>
      </tr>
      <tr>
          <td>metric</td>
          <td>terminal.connect.duration</td>
          <td>連線完成（成功或失敗）</td>
          <td><code>{duration_ms, success: bool}</code></td>
      </tr>
      <tr>
          <td>lifecycle</td>
          <td>ws.connected</td>
          <td>WebSocket 連線狀態轉換</td>
          <td><code>{url}</code></td>
      </tr>
      <tr>
          <td>lifecycle</td>
          <td>ws.disconnected</td>
          <td>WebSocket 斷線</td>
          <td><code>{url, reason, code}</code></td>
      </tr>
  </tbody>
</table>
<p>一個操作推導出六個事件 — 因為這個操作跨越了使用者行為（event）、可能失敗（error）、有效能測量（metric）、涉及系統狀態轉換（lifecycle）四個面向。其中 <code>connect.done</code> 和 <code>connect.duration</code> 記錄的是同一事實的兩個面向（見下方邊界案例段），自用場景合併成 <code>connect.done</code> 帶 <code>duration_ms</code> 欄位更簡潔。</p>
<h2 id="四類補齊檢查">四類補齊檢查</h2>
<p>列完所有操作的事件後，對每個功能區域跑一次四類補齊檢查 — 逐列確認每一類是否都有對應的事件。</p>
<table>
  <thead>
      <tr>
          <th>功能區域</th>
          <th>event</th>
          <th>error</th>
          <th>metric</th>
          <th>lifecycle</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>連線</td>
          <td>connect.start / connect.done</td>
          <td>connect.failed</td>
          <td>connect.duration</td>
          <td>ws.connected / ws.disconnected</td>
      </tr>
      <tr>
          <td>認證</td>
          <td>auth.biometric.attempt</td>
          <td>auth.biometric.failed</td>
          <td>auth.duration</td>
          <td>auth.state_changed</td>
      </tr>
      <tr>
          <td>輸入</td>
          <td>input.submit</td>
          <td>input.parse_error</td>
          <td>—</td>
          <td>—</td>
      </tr>
      <tr>
          <td>配對</td>
          <td>enrollment.qr.scan / enrollment.done</td>
          <td>enrollment.failed</td>
          <td>enrollment.duration</td>
          <td>—</td>
      </tr>
  </tbody>
</table>
<p>空格是候選遺漏。每個空格問一個問題：</p>
<ul>
<li><strong>event 空</strong>：「這個功能區域有使用者操作嗎？」有 → 補事件；沒有（純系統內部）→ 合理的空格</li>
<li><strong>error 空</strong>：「這個功能區域能失敗嗎？」能 → 補事件；不能失敗的功能極少 → 再想一次</li>
<li><strong>metric 空</strong>：「這個功能區域有值得量測的效能指標嗎？」有 → 補事件；操作瞬間完成且不涉及外部依賴 → 合理的空格</li>
<li><strong>lifecycle 空</strong>：「這個功能區域涉及系統狀態轉換嗎？」有 → 補事件；純資料操作不改系統狀態 → 合理的空格</li>
</ul>
<p>上表中「輸入」的 metric 和 lifecycle 空格是合理的 — 文字輸入送出不涉及效能量測和系統狀態轉換。「配對」的 lifecycle 空格也合理 — 配對完成後不改變系統的執行狀態。</p>
<h2 id="粒度判準">粒度判準</h2>
<p>事件粒度的判斷用一個 SRP 判準：<strong>一個事件記一個事實</strong>。</p>
<h3 id="拆分訊號">拆分訊號</h3>
<p>一個事件記了兩個獨立的事實 → 拆成兩個事件。</p>
<p><code>terminal.connect_and_auth</code> 同時記錄「連線建立」和「認證通過」。這兩個事實的失敗模式不同（連線失敗是網路問題、認證失敗是帳密問題）、觸發時機不同、消費者不同。拆成 <code>terminal.connect.done</code> 和 <code>auth.token.sent</code>。</p>
<h3 id="合併訊號">合併訊號</h3>
<p>兩個事件永遠同時觸發且消費者相同 → 合併成一個事件。</p>
<p><code>terminal.input.keystroke</code> 和 <code>terminal.input.keystroke_logged</code> 永遠同時觸發（每個按鍵一次），data schema 相同。合併成一個 <code>terminal.input.keystroke</code>。</p>
<h3 id="邊界案例">邊界案例</h3>
<p><code>connect.done</code> 同時記 event 和 metric（成功事件 + duration）。這是一個事實（連線完成）的兩個面向，可以合併成一個事件帶 <code>duration_ms</code> 欄位，也可以拆成 event 和 metric 兩筆。判斷依據是查詢需求 — 如果 funnel 分析和效能分析會分開查，拆開讓各自的查詢更簡單；如果都在同一個 dashboard 看，合併減少事件量。</p>
<h2 id="data-schema-設計">data schema 設計</h2>
<p>每個事件的 data 欄位回答「發生了什麼的 context」。設計原則：</p>
<p><strong>帶足 debug context</strong>：error 事件的 data 至少包含 error message、發生的步驟、當時的關鍵狀態值。看到這筆 error 事件時、開發者不需要再去查其他來源就能判斷問題方向。</p>
<p><strong>避免過度收集</strong>：data 只帶回答具體問題需要的欄位。<code>terminal.connect.start</code> 帶 URL 和觸發方式就夠了；不需要帶使用者的全部設定。</p>
<p><strong>敏感欄位標記 redaction</strong>：URL 可能含 IP、error message 可能含路徑中的使用者名稱。在事件設計階段標記需要 <a href="/blog/monitoring/knowledge-cards/redaction/" data-link-title="Redaction" data-link-desc="說明在事件資料離開 client 之前把敏感欄位的值替換成遮罩或移除的機制">redaction</a> 的欄位，SDK 實作時自動處理。</p>
<h2 id="事件表的產出格式">事件表的產出格式</h2>
<p>完整的事件表每列七欄：</p>
<table>
  <thead>
      <tr>
          <th>事件名稱</th>
          <th>類型</th>
          <th>觸發時機</th>
          <th>data schema</th>
          <th>redaction 欄位</th>
          <th>保留層級</th>
          <th>備註</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>terminal.connect.start</td>
          <td>event</td>
          <td>使用者點擊連線</td>
          <td><code>{url, trigger}</code></td>
          <td>url</td>
          <td>原始 7d</td>
          <td>funnel 第一步</td>
      </tr>
  </tbody>
</table>
<p>保留層級欄對應分層保留策略 — 哪些事件需要保留原始逐筆資料（debug 用）、哪些只需要聚合摘要（趨勢用）。</p>
<p>事件表是 SDK 埋點的 spec — 開發者照表實作，code review 時逐行勾選。和<a href="/blog/testing/02-client-observability/log-point-in-spec/" data-link-title="功能規格中的 log 點定義方法" data-link-desc="把 log 點設計從 debug 階段前移到功能規格階段 — 每個功能的規格文件新增可觀測性欄位，列出啟動 / 步驟 / 錯誤 / 完成四類 log 點">功能規格中的 log 點定義</a>互補 — log 點是開發期的 debug 設計，事件表是監控期的收集設計。</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/01-mental-model/event-naming-convention/" data-link-title="事件命名規範" data-link-desc="namespace.action 格式的事件命名、命名一致性的工程價值、和商業方案命名慣例的對應">事件命名規範</a></li>
<li>行為事件的 funnel 設計 → <a href="/blog/monitoring/08-business-analytics/behavior-event-design/" data-link-title="行為事件設計" data-link-desc="事件命名規範、屬性設計、funnel 定義 — 行為分析的品質取決於事件設計的品質">行為事件設計</a></li>
<li>事件 schema 的欄位定義 → <a href="/blog/monitoring/02-log-schema/event-schema-fields/" data-link-title="event.schema.json 完整欄位解說" data-link-desc="監控事件的 JSON Schema 定義 — 每個欄位的語意、必填/選填、資料型別和設計理由">event.schema.json 完整欄位解說</a></li>
<li>動機驅動的具體事件對應 → <a href="/blog/monitoring/01-mental-model/motivation-to-event-mapping/" data-link-title="動機驅動的事件設計" data-link-desc="Debug / 商業 / 資安 / 效能四個動機各自需要什麼事件 — 從「為什麼收」反推「收什麼」和「什麼階段啟用」">動機驅動的事件設計</a></li>
</ul>
]]></content:encoded></item><item><title>動機驅動的事件設計</title><link>https://tarrragon.github.io/blog/monitoring/01-mental-model/motivation-to-event-mapping/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/01-mental-model/motivation-to-event-mapping/</guid><description>&lt;p>事件設計是三維結構：動機（為什麼收）決定需要什麼事件、感測器（怎麼收）決定在前端哪裡埋點、生命週期（什麼時候收）決定各事件在哪個產品階段啟用。本章展開&lt;a href="https://tarrragon.github.io/blog/monitoring/01-mental-model/derive-collection-from-requirements/" data-link-title="從需求推導「該收集哪些事件」" data-link-desc="從 debug 需求、行為分析需求、效能需求、合規需求四個方向推導事件收集策略 — 避免「什麼都收」和「什麼都不收」">從需求推導收集策略&lt;/a>的四個方向到具體事件名稱級。從動機出發反推事件清單，比從技術能力出發（「SDK 能收什麼就收什麼」）更精準 — 每個事件都能回指一個具體的消費場景。&lt;/p>
&lt;h2 id="debug-動機">Debug 動機&lt;/h2>
&lt;p>Debug 動機驅動的事件收集目標是「問題發生時、開發者能從事件記錄中重建 context 並定位根因」。&lt;/p>
&lt;h3 id="要偵測的行為">要偵測的行為&lt;/h3>
&lt;ul>
&lt;li>多步驟流程的每一步完成或失敗（連線 → 認證 → 資料交換）&lt;/li>
&lt;li>系統狀態轉換（前景/背景、連線/斷線、登入/登出）&lt;/li>
&lt;li>非預期例外（uncaught exception、network error、timeout）&lt;/li>
&lt;li>使用者最近的操作序列（問題發生前做了什麼）&lt;/li>
&lt;/ul>
&lt;h3 id="事件表">事件表&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>事件名稱&lt;/th>
 &lt;th>類型&lt;/th>
 &lt;th>觸發時機&lt;/th>
 &lt;th>data schema 重點&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>{feature}.step.done&lt;/td>
 &lt;td>lifecycle&lt;/td>
 &lt;td>流程步驟完成&lt;/td>
 &lt;td>step_name, duration_ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>{feature}.step.failed&lt;/td>
 &lt;td>error&lt;/td>
 &lt;td>流程步驟失敗&lt;/td>
 &lt;td>step_name, error, context&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>app.exception&lt;/td>
 &lt;td>error&lt;/td>
 &lt;td>uncaught exception&lt;/td>
 &lt;td>message, stack_trace, component&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ws.connected / ws.disconnected&lt;/td>
 &lt;td>lifecycle&lt;/td>
 &lt;td>連線狀態變化&lt;/td>
 &lt;td>url, reason, code&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>app.foreground / app.background&lt;/td>
 &lt;td>lifecycle&lt;/td>
 &lt;td>app 前後景切換&lt;/td>
 &lt;td>duration_in_background&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>{action}.completed&lt;/td>
 &lt;td>event&lt;/td>
 &lt;td>使用者完成操作&lt;/td>
 &lt;td>action_detail&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="查詢場景">查詢場景&lt;/h3>
&lt;p>&lt;strong>Session 回放&lt;/strong>：按 session_id 過濾、按時間排序，還原「使用者做了什麼 → 系統發生了什麼 → 問題在哪裡出現」。&lt;/p>
&lt;p>&lt;strong>Error 根因定位&lt;/strong>：按 error name GROUP BY，找出最常出現的錯誤。單筆 error 的 stack_trace + 同 session 的 lifecycle 事件組合，判斷失敗發生在流程的哪一步。&lt;/p>
&lt;p>&lt;strong>最近 N 個操作&lt;/strong>：error 發生前的 10-20 個 event/lifecycle 事件，等同 Sentry 的 breadcrumb trail。&lt;/p>
&lt;h3 id="生命週期階段">生命週期階段&lt;/h3>
&lt;p>開發期起全開。Debug 事件是最早需要的 — 實機測試階段就依賴這些事件定位問題。error 類和 lifecycle 類不做取樣（量低且每筆都可能是線索）。&lt;/p>
&lt;h2 id="商業動機">商業動機&lt;/h2>
&lt;p>商業動機驅動的事件收集目標是「回答產品決策的問題 — 使用者在哪裡流失、不同群組行為有什麼差異、哪些功能被使用」。&lt;/p>
&lt;h3 id="要偵測的行為-1">要偵測的行為&lt;/h3>
&lt;ul>
&lt;li>漏斗步驟完成（註冊 → 啟用 → 付費 → 續約的每一步）&lt;/li>
&lt;li>功能使用頻率（哪些功能被頻繁使用、哪些從未被觸發）&lt;/li>
&lt;li>Session 長度和頻率（使用者多常用、每次用多久）&lt;/li>
&lt;li>關鍵轉換事件（首次付費、邀請好友、升級方案）&lt;/li>
&lt;/ul>
&lt;h3 id="事件表-1">事件表&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>事件名稱&lt;/th>
 &lt;th>類型&lt;/th>
 &lt;th>觸發時機&lt;/th>
 &lt;th>data schema 重點&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>funnel.{name}.step_N&lt;/td>
 &lt;td>event&lt;/td>
 &lt;td>漏斗步驟完成&lt;/td>
 &lt;td>step_name, funnel_name&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>feature.{name}.used&lt;/td>
 &lt;td>event&lt;/td>
 &lt;td>使用者使用特定功能&lt;/td>
 &lt;td>feature_name, context&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>session.start / session.end&lt;/td>
 &lt;td>lifecycle&lt;/td>
 &lt;td>session 邊界&lt;/td>
 &lt;td>session_duration&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>conversion.{type}&lt;/td>
 &lt;td>event&lt;/td>
 &lt;td>關鍵轉換&lt;/td>
 &lt;td>conversion_type, value&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="查詢場景-1">查詢場景&lt;/h3>
&lt;p>&lt;strong>Funnel 轉換率&lt;/strong>：每步的完成數 / 上一步的完成數。SQLite 層做每步計數，PostgreSQL 層做 session 級 JOIN 的精確轉換率（見 &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/feature-tier-boundary/" data-link-title="功能分層與 Backend 選擇" data-link-desc="SQLite 層和 PostgreSQL 層各自承載哪些功能 — 分界線是查詢模式而非資料量、觸發升級的是功能需求而非規模成長">功能分層與 Backend 選擇&lt;/a>）。&lt;/p>
&lt;p>&lt;strong>Cohort 留存&lt;/strong>：按「首次使用週」分群，計算每週的回訪率。需要 session.start 事件 + 使用者首次出現的時間戳。&lt;/p>
&lt;p>&lt;strong>功能使用率&lt;/strong>：feature.*.used 事件按 name GROUP BY COUNT，排序找出最常/最少使用的功能。&lt;/p>
&lt;h3 id="生命週期階段-1">生命週期階段&lt;/h3>
&lt;p>上線後啟用。開發期不需要商業事件（沒有真實使用者）。測試期可以用模擬流量驗證 funnel 事件的觸發正確性，但不做分析。&lt;/p>
&lt;h2 id="資安動機">資安動機&lt;/h2>
&lt;p>資安動機驅動的事件收集目標是「偵測非預期的存取模式、追蹤敏感操作、提供事後稽核的 audit trail」。&lt;/p></description><content:encoded><![CDATA[<p>事件設計是三維結構：動機（為什麼收）決定需要什麼事件、感測器（怎麼收）決定在前端哪裡埋點、生命週期（什麼時候收）決定各事件在哪個產品階段啟用。本章展開<a href="/blog/monitoring/01-mental-model/derive-collection-from-requirements/" data-link-title="從需求推導「該收集哪些事件」" data-link-desc="從 debug 需求、行為分析需求、效能需求、合規需求四個方向推導事件收集策略 — 避免「什麼都收」和「什麼都不收」">從需求推導收集策略</a>的四個方向到具體事件名稱級。從動機出發反推事件清單，比從技術能力出發（「SDK 能收什麼就收什麼」）更精準 — 每個事件都能回指一個具體的消費場景。</p>
<h2 id="debug-動機">Debug 動機</h2>
<p>Debug 動機驅動的事件收集目標是「問題發生時、開發者能從事件記錄中重建 context 並定位根因」。</p>
<h3 id="要偵測的行為">要偵測的行為</h3>
<ul>
<li>多步驟流程的每一步完成或失敗（連線 → 認證 → 資料交換）</li>
<li>系統狀態轉換（前景/背景、連線/斷線、登入/登出）</li>
<li>非預期例外（uncaught exception、network error、timeout）</li>
<li>使用者最近的操作序列（問題發生前做了什麼）</li>
</ul>
<h3 id="事件表">事件表</h3>
<table>
  <thead>
      <tr>
          <th>事件名稱</th>
          <th>類型</th>
          <th>觸發時機</th>
          <th>data schema 重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>{feature}.step.done</td>
          <td>lifecycle</td>
          <td>流程步驟完成</td>
          <td>step_name, duration_ms</td>
      </tr>
      <tr>
          <td>{feature}.step.failed</td>
          <td>error</td>
          <td>流程步驟失敗</td>
          <td>step_name, error, context</td>
      </tr>
      <tr>
          <td>app.exception</td>
          <td>error</td>
          <td>uncaught exception</td>
          <td>message, stack_trace, component</td>
      </tr>
      <tr>
          <td>ws.connected / ws.disconnected</td>
          <td>lifecycle</td>
          <td>連線狀態變化</td>
          <td>url, reason, code</td>
      </tr>
      <tr>
          <td>app.foreground / app.background</td>
          <td>lifecycle</td>
          <td>app 前後景切換</td>
          <td>duration_in_background</td>
      </tr>
      <tr>
          <td>{action}.completed</td>
          <td>event</td>
          <td>使用者完成操作</td>
          <td>action_detail</td>
      </tr>
  </tbody>
</table>
<h3 id="查詢場景">查詢場景</h3>
<p><strong>Session 回放</strong>：按 session_id 過濾、按時間排序，還原「使用者做了什麼 → 系統發生了什麼 → 問題在哪裡出現」。</p>
<p><strong>Error 根因定位</strong>：按 error name GROUP BY，找出最常出現的錯誤。單筆 error 的 stack_trace + 同 session 的 lifecycle 事件組合，判斷失敗發生在流程的哪一步。</p>
<p><strong>最近 N 個操作</strong>：error 發生前的 10-20 個 event/lifecycle 事件，等同 Sentry 的 breadcrumb trail。</p>
<h3 id="生命週期階段">生命週期階段</h3>
<p>開發期起全開。Debug 事件是最早需要的 — 實機測試階段就依賴這些事件定位問題。error 類和 lifecycle 類不做取樣（量低且每筆都可能是線索）。</p>
<h2 id="商業動機">商業動機</h2>
<p>商業動機驅動的事件收集目標是「回答產品決策的問題 — 使用者在哪裡流失、不同群組行為有什麼差異、哪些功能被使用」。</p>
<h3 id="要偵測的行為-1">要偵測的行為</h3>
<ul>
<li>漏斗步驟完成（註冊 → 啟用 → 付費 → 續約的每一步）</li>
<li>功能使用頻率（哪些功能被頻繁使用、哪些從未被觸發）</li>
<li>Session 長度和頻率（使用者多常用、每次用多久）</li>
<li>關鍵轉換事件（首次付費、邀請好友、升級方案）</li>
</ul>
<h3 id="事件表-1">事件表</h3>
<table>
  <thead>
      <tr>
          <th>事件名稱</th>
          <th>類型</th>
          <th>觸發時機</th>
          <th>data schema 重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>funnel.{name}.step_N</td>
          <td>event</td>
          <td>漏斗步驟完成</td>
          <td>step_name, funnel_name</td>
      </tr>
      <tr>
          <td>feature.{name}.used</td>
          <td>event</td>
          <td>使用者使用特定功能</td>
          <td>feature_name, context</td>
      </tr>
      <tr>
          <td>session.start / session.end</td>
          <td>lifecycle</td>
          <td>session 邊界</td>
          <td>session_duration</td>
      </tr>
      <tr>
          <td>conversion.{type}</td>
          <td>event</td>
          <td>關鍵轉換</td>
          <td>conversion_type, value</td>
      </tr>
  </tbody>
</table>
<h3 id="查詢場景-1">查詢場景</h3>
<p><strong>Funnel 轉換率</strong>：每步的完成數 / 上一步的完成數。SQLite 層做每步計數，PostgreSQL 層做 session 級 JOIN 的精確轉換率（見 <a href="/blog/monitoring/04-collector/feature-tier-boundary/" data-link-title="功能分層與 Backend 選擇" data-link-desc="SQLite 層和 PostgreSQL 層各自承載哪些功能 — 分界線是查詢模式而非資料量、觸發升級的是功能需求而非規模成長">功能分層與 Backend 選擇</a>）。</p>
<p><strong>Cohort 留存</strong>：按「首次使用週」分群，計算每週的回訪率。需要 session.start 事件 + 使用者首次出現的時間戳。</p>
<p><strong>功能使用率</strong>：feature.*.used 事件按 name GROUP BY COUNT，排序找出最常/最少使用的功能。</p>
<h3 id="生命週期階段-1">生命週期階段</h3>
<p>上線後啟用。開發期不需要商業事件（沒有真實使用者）。測試期可以用模擬流量驗證 funnel 事件的觸發正確性，但不做分析。</p>
<h2 id="資安動機">資安動機</h2>
<p>資安動機驅動的事件收集目標是「偵測非預期的存取模式、追蹤敏感操作、提供事後稽核的 audit trail」。</p>
<h3 id="要偵測的行為-2">要偵測的行為</h3>
<ul>
<li>認證失敗（密碼錯誤、biometric 失敗、token 過期）</li>
<li>權限越界嘗試（嘗試存取非自己的資源、呼叫無權限的 API）</li>
<li>敏感資料存取（查看個資、匯出資料、修改權限設定）</li>
<li>異常存取模式（短時間大量請求、非常規時段存取、來源 IP 變化）</li>
</ul>
<h3 id="事件表-2">事件表</h3>
<table>
  <thead>
      <tr>
          <th>事件名稱</th>
          <th>類型</th>
          <th>觸發時機</th>
          <th>data schema 重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>auth.{method}.failed</td>
          <td>error</td>
          <td>認證失敗</td>
          <td>method, failure_reason, attempt_count</td>
      </tr>
      <tr>
          <td>auth.{method}.success</td>
          <td>event</td>
          <td>認證成功（語意上是系統回呼、歸為 event 是業界慣例）</td>
          <td>method, duration_ms</td>
      </tr>
      <tr>
          <td>authz.denied</td>
          <td>error</td>
          <td>權限檢查拒絕</td>
          <td>resource, action, role</td>
      </tr>
      <tr>
          <td>sensitive.accessed</td>
          <td>event</td>
          <td>敏感資料被存取</td>
          <td>resource_type, accessor_role</td>
      </tr>
      <tr>
          <td>sensitive.exported</td>
          <td>event</td>
          <td>資料被匯出</td>
          <td>export_format, record_count</td>
      </tr>
      <tr>
          <td>admin.setting.changed</td>
          <td>event</td>
          <td>管理設定變更</td>
          <td>setting_key, old_value_hash, new_value_hash</td>
      </tr>
  </tbody>
</table>
<h3 id="查詢場景-2">查詢場景</h3>
<p><strong>認證失敗監控</strong>：auth.*.failed 事件的 count by session_id，短時間內同一 session 多次失敗 → 暴力破解嫌疑。Rule engine 設閾值告警。</p>
<p><strong>Audit trail</strong>：sensitive.* 和 admin.* 事件按時間排列，回答「誰在什麼時候存取/修改了什麼」。合規審計的必要紀錄。</p>
<p><strong>異常 pattern 偵測</strong>：auth 成功後的操作事件頻率和模式分析。正常使用者每 session 操作 10-50 次；自動化腳本可能操作數千次。</p>
<h3 id="生命週期階段-2">生命週期階段</h3>
<p>開發期起全開。安全事件不能延後 — 「先不收安全事件、上線後再加」等於安全審計的空白期。認證相關事件是 auto-intercept 的一部分（見 <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>），不需要手動埋點。</p>
<h3 id="和-redaction-的關係">和 redaction 的關係</h3>
<p>資安事件本身可能包含敏感資訊（失敗的密碼、被存取的個資欄位名稱）。事件的 data schema 設計時標記需要 <a href="/blog/monitoring/knowledge-cards/redaction/" data-link-title="Redaction" data-link-desc="說明在事件資料離開 client 之前把敏感欄位的值替換成遮罩或移除的機制">redaction</a> 的欄位 — auth.failed 記錄失敗原因但不記錄輸入的密碼、sensitive.accessed 記錄資源類型但不記錄資源內容。</p>
<h2 id="效能動機">效能動機</h2>
<p>效能動機驅動的事件收集目標是「發現效能退化趨勢、定位效能瓶頸、為容量規劃提供數據」。</p>
<h3 id="要偵測的行為-3">要偵測的行為</h3>
<ul>
<li>操作回應時間（API 呼叫、頁面載入、動畫轉場）</li>
<li>渲染效能（frame rate、長任務、佈局重排）</li>
<li>資源使用（記憶體、CPU、網路流量）</li>
<li>外部依賴延遲（第三方 API、CDN、資料庫查詢）</li>
</ul>
<h3 id="事件表-3">事件表</h3>
<table>
  <thead>
      <tr>
          <th>事件名稱</th>
          <th>類型</th>
          <th>觸發時機</th>
          <th>data schema 重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>{operation}.duration</td>
          <td>metric</td>
          <td>操作完成</td>
          <td>duration_ms, operation_name</td>
      </tr>
      <tr>
          <td>render.frame_drop</td>
          <td>metric</td>
          <td>掉幀偵測</td>
          <td>dropped_frames, total_frames</td>
      </tr>
      <tr>
          <td>resource.memory</td>
          <td>metric</td>
          <td>定期取樣（30s）</td>
          <td>heap_used, heap_total</td>
      </tr>
      <tr>
          <td>dependency.{name}.latency</td>
          <td>metric</td>
          <td>外部呼叫完成</td>
          <td>dependency_name, latency_ms, status</td>
      </tr>
      <tr>
          <td>web.vitals</td>
          <td>metric</td>
          <td>Web 頁面載入</td>
          <td>lcp_ms, fid_ms, cls_score</td>
      </tr>
  </tbody>
</table>
<h3 id="查詢場景-3">查詢場景</h3>
<p><strong>P95 趨勢</strong>：{operation}.duration 事件按天聚合、計算 percentile_cont(0.95)，觀察回應時間是否隨版本增加。</p>
<p><strong>容量規劃</strong>：resource.memory 事件的趨勢圖，判斷記憶體是否隨使用時間穩定增長（memory leak 訊號）。</p>
<p><strong>依賴健康度</strong>：dependency.*.latency 事件按 dependency_name GROUP BY，比較各依賴的平均延遲和失敗率。</p>
<h3 id="生命週期階段-3">生命週期階段</h3>
<p>測試期起啟用。開發期不需要效能事件（本地環境的效能數據不代表 production）。測試期啟用用於建立效能 baseline。上線後持續收集用於趨勢監控。</p>
<p>效能事件量通常最大（每 30 秒一筆 resource.memory × 活躍使用者數），取樣率需要控制 — 自用場景全收、商業產品取樣 10-50%（見 <a href="/blog/monitoring/03-sdk-design/frontend-sensor-design/" data-link-title="前端感測器設計" data-link-desc="什麼行為值得埋感測器、每類感測器的實作方式、取樣策略和效能影響 — 和 auto-intercept 的被動攔截互補">前端感測器設計</a> 的取樣策略段）。</p>
<h2 id="ab-測試動機">A/B 測試動機</h2>
<p>A/B 測試動機驅動的事件是商業動機的延伸 — 實驗期間收集實驗分組和轉換事件，實驗結束後關閉。</p>
<h3 id="事件表-4">事件表</h3>
<table>
  <thead>
      <tr>
          <th>事件名稱</th>
          <th>類型</th>
          <th>觸發時機</th>
          <th>data schema 重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>experiment.{name}.assigned</td>
          <td>event</td>
          <td>使用者被分配到實驗組</td>
          <td>experiment_name, variant</td>
      </tr>
      <tr>
          <td>experiment.{name}.converted</td>
          <td>event</td>
          <td>使用者完成轉換目標</td>
          <td>experiment_name, variant, conversion_type</td>
      </tr>
  </tbody>
</table>
<h3 id="生命週期階段-4">生命週期階段</h3>
<p>實驗期間啟用，實驗結束後關閉（從 SDK config 或 feature flag 移除）。實驗事件的保留期限跟著實驗週期走 — 實驗結束 + 分析完成後可清除。A/B test 的統計分析見 <a href="/blog/monitoring/08-business-analytics/ab-test-statistics/" data-link-title="A/B Test 的統計基礎" data-link-desc="假設檢定、樣本量計算、多重比較校正 — A/B test 不只是「比較兩個數字」，統計方法決定結論是否可靠">A/B test 的統計基礎</a>。</p>
<h2 id="完整對照總表">完整對照總表</h2>
<table>
  <thead>
      <tr>
          <th>動機</th>
          <th>要偵測的行為</th>
          <th>事件名稱模式</th>
          <th>感測器類型</th>
          <th>生命週期啟用</th>
          <th>查詢模式</th>
          <th>保留層級</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Debug</td>
          <td>流程步驟完成/失敗</td>
          <td>{feature}.step.*</td>
          <td>auto-intercept</td>
          <td>開發期起</td>
          <td>session 回放</td>
          <td>原始 7d</td>
      </tr>
      <tr>
          <td>Debug</td>
          <td>例外拋出</td>
          <td>app.exception</td>
          <td>auto-intercept</td>
          <td>開發期起</td>
          <td>error GROUP BY</td>
          <td>原始 30d</td>
      </tr>
      <tr>
          <td>Debug</td>
          <td>連線狀態</td>
          <td>ws.connected/disconnected</td>
          <td>auto-intercept</td>
          <td>開發期起</td>
          <td>session 回放</td>
          <td>原始 7d</td>
      </tr>
      <tr>
          <td>Debug</td>
          <td>最近操作</td>
          <td>{action}.completed</td>
          <td>手動埋點</td>
          <td>開發期起</td>
          <td>breadcrumb trail</td>
          <td>原始 7d</td>
      </tr>
      <tr>
          <td>商業</td>
          <td>漏斗步驟</td>
          <td>funnel.{name}.step_N</td>
          <td>手動埋點</td>
          <td>上線後</td>
          <td>funnel JOIN</td>
          <td>小時聚合 90d</td>
      </tr>
      <tr>
          <td>商業</td>
          <td>功能使用</td>
          <td>feature.{name}.used</td>
          <td>手動埋點</td>
          <td>上線後</td>
          <td>COUNT GROUP BY</td>
          <td>天聚合 365d</td>
      </tr>
      <tr>
          <td>商業</td>
          <td>Session</td>
          <td>session.start/end</td>
          <td>auto-intercept</td>
          <td>上線後</td>
          <td>cohort 留存</td>
          <td>天聚合 365d</td>
      </tr>
      <tr>
          <td>商業</td>
          <td>轉換</td>
          <td>conversion.{type}</td>
          <td>手動埋點</td>
          <td>上線後</td>
          <td>funnel 最後一步</td>
          <td>原始 90d</td>
      </tr>
      <tr>
          <td>資安</td>
          <td>認證失敗</td>
          <td>auth.{method}.failed</td>
          <td>auto-intercept</td>
          <td>開發期起</td>
          <td>閾值告警</td>
          <td>原始 30d</td>
      </tr>
      <tr>
          <td>資安</td>
          <td>權限拒絕</td>
          <td>authz.denied</td>
          <td>auto-intercept</td>
          <td>開發期起</td>
          <td>pattern 偵測</td>
          <td>原始 30d</td>
      </tr>
      <tr>
          <td>資安</td>
          <td>敏感存取</td>
          <td>sensitive.*</td>
          <td>手動埋點</td>
          <td>開發期起</td>
          <td>audit trail</td>
          <td>原始 365d</td>
      </tr>
      <tr>
          <td>資安</td>
          <td>設定變更</td>
          <td>admin.setting.changed</td>
          <td>手動埋點</td>
          <td>開發期起</td>
          <td>audit trail</td>
          <td>原始 365d</td>
      </tr>
      <tr>
          <td>效能</td>
          <td>操作延遲</td>
          <td>{operation}.duration</td>
          <td>手動埋點</td>
          <td>測試期起</td>
          <td>P95 趨勢</td>
          <td>小時聚合 90d</td>
      </tr>
      <tr>
          <td>效能</td>
          <td>渲染效能</td>
          <td>render.frame_drop</td>
          <td>auto-intercept</td>
          <td>測試期起</td>
          <td>趨勢圖</td>
          <td>小時聚合 90d</td>
      </tr>
      <tr>
          <td>效能</td>
          <td>資源用量</td>
          <td>resource.memory</td>
          <td>定期取樣</td>
          <td>測試期起</td>
          <td>趨勢圖</td>
          <td>小時聚合 90d</td>
      </tr>
      <tr>
          <td>效能</td>
          <td>外部依賴</td>
          <td>dependency.{name}.latency</td>
          <td>手動埋點</td>
          <td>測試期起</td>
          <td>GROUP BY 依賴</td>
          <td>小時聚合 90d</td>
      </tr>
      <tr>
          <td>效能</td>
          <td>Web Vitals</td>
          <td>web.vitals</td>
          <td>auto-intercept</td>
          <td>測試期起</td>
          <td>趨勢圖</td>
          <td>小時聚合 90d</td>
      </tr>
      <tr>
          <td>A/B</td>
          <td>實驗分組</td>
          <td>experiment.{name}.assigned</td>
          <td>手動埋點</td>
          <td>實驗期間</td>
          <td>variant GROUP BY</td>
          <td>實驗結束後清</td>
      </tr>
      <tr>
          <td>A/B</td>
          <td>實驗轉換</td>
          <td>experiment.{name}.converted</td>
          <td>手動埋點</td>
          <td>實驗期間</td>
          <td>轉換率計算</td>
          <td>實驗結束後清</td>
      </tr>
      <tr>
          <td>DevOps</td>
          <td>Collector 存活</td>
          <td>collector.health.check</td>
          <td>Collector 內部</td>
          <td>開發期起</td>
          <td>狀態卡</td>
          <td>原始 7d</td>
      </tr>
      <tr>
          <td>DevOps</td>
          <td>事件吞吐量</td>
          <td>collector.ingestion.count</td>
          <td>Collector 內部</td>
          <td>開發期起</td>
          <td>吞吐曲線</td>
          <td>小時聚合 90d</td>
      </tr>
      <tr>
          <td>DevOps</td>
          <td>儲存用量</td>
          <td>collector.storage.disk_usage</td>
          <td>Collector 內部</td>
          <td>開發期起</td>
          <td>儲存圖</td>
          <td>小時聚合 90d</td>
      </tr>
      <tr>
          <td>DevOps</td>
          <td>SDK 心跳</td>
          <td>sdk.heartbeat</td>
          <td>SDK 端</td>
          <td>開發期起</td>
          <td>連線列表</td>
          <td>原始 7d</td>
      </tr>
      <tr>
          <td>DevOps</td>
          <td>部署事件</td>
          <td>deployment.completed</td>
          <td>CI/CD hook</td>
          <td>開發期起</td>
          <td>部署狀態</td>
          <td>原始 30d</td>
      </tr>
      <tr>
          <td>DevOps</td>
          <td>規則命中</td>
          <td>rule.matched</td>
          <td>Collector 內部</td>
          <td>開發期起</td>
          <td>alert 歷史</td>
          <td>原始 30d</td>
      </tr>
      <tr>
          <td>中台</td>
          <td>使用者首次出現</td>
          <td>user.first_seen</td>
          <td>Collector 計算</td>
          <td>上線後</td>
          <td>cohort 分群</td>
          <td>天聚合 365d</td>
      </tr>
      <tr>
          <td>中台</td>
          <td>通路歸因</td>
          <td>attribution.install_source</td>
          <td>SDK 首次啟動</td>
          <td>上線後</td>
          <td>歸因報表</td>
          <td>原始 90d</td>
      </tr>
      <tr>
          <td>中台</td>
          <td>即時在線</td>
          <td>session.active.count</td>
          <td>Collector 計算</td>
          <td>上線後</td>
          <td>即時大屏</td>
          <td>小時聚合 90d</td>
      </tr>
  </tbody>
</table>
<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/01-mental-model/event-enumeration-method/" data-link-title="事件枚舉與補齊檢查" data-link-desc="從操作盤點系統性地推導出完整的事件清單 — 四類補齊檢查確保沒有遺漏、粒度判準確保每個事件只記一個事實">事件枚舉與補齊檢查</a></li>
<li>前端感測器的具體設計 → <a href="/blog/monitoring/03-sdk-design/frontend-sensor-design/" data-link-title="前端感測器設計" data-link-desc="什麼行為值得埋感測器、每類感測器的實作方式、取樣策略和效能影響 — 和 auto-intercept 的被動攔截互補">前端感測器設計</a></li>
<li>感測器的生命週期控制 → <a href="/blog/monitoring/03-sdk-design/sensor-lifecycle-management/" data-link-title="感測器生命週期管理" data-link-desc="產品生命週期的五個階段各啟用什麼感測器 — feature flag 整合、取樣率動態調整、感測器開關的可觀察性">感測器生命週期管理</a></li>
<li>查詢消費模式的完整展開 → <a href="/blog/monitoring/04-collector/query-consumption-patterns/" data-link-title="查詢消費模式" data-link-desc="Debug / Alerting / 產品決策 / 安全審計 / 效能監控 — 五種查詢場景各需要什麼事件、什麼欄位、什麼查詢模式">查詢消費模式</a></li>
</ul>
]]></content:encoded></item></channel></rss>