<?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>Principles on Tarragon</title><link>https://tarrragon.github.io/blog/tags/principles/</link><description>Recent content in Principles on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/principles/index.xml" rel="self" type="application/rss+xml"/><item><title>欄位設計原則</title><link>https://tarrragon.github.io/blog/monitoring/02-log-schema/field-design-principles/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/02-log-schema/field-design-principles/</guid><description>&lt;p>事件 schema 的欄位設計遵循三個原則：來源可追溯、擴展不破壞、版本可辨識。這三個原則讓 schema 從自用工具的 grep 查詢一直到商業方案的資料管線都能正常運作。&lt;/p>
&lt;h2 id="原則一source-標明來源">原則一：source 標明來源&lt;/h2>
&lt;p>每筆事件的 source 欄位記錄「這筆事件從哪裡來」。App 名稱、版本、平台、OS 版本 — 這些資訊在事件產生時由 SDK 自動填入，不依賴使用者或開發者手動標記。&lt;/p>
&lt;p>source 的設計要點是「足夠區分但不過度」。&lt;code>sdk&lt;/code> 和 &lt;code>platform&lt;/code> 是必填——sdk 標明事件由哪個 SDK 實作產生（&lt;code>js&lt;/code> / &lt;code>flutter&lt;/code> / &lt;code>python&lt;/code> / &lt;code>go&lt;/code>），platform 標明運行平台（&lt;code>ios&lt;/code> / &lt;code>android&lt;/code> / &lt;code>web&lt;/code> / &lt;code>macos&lt;/code>）。兩者不能互相推導：同一個 platform（iOS）上可能有不同的 SDK（Flutter SDK 或 Swift 原生 SDK），同一個 SDK（Flutter）可能跑在不同 platform（iOS / Android / Web）。App 名稱和版本能區分「這是哪個 app 的哪個版本送來的事件」。OS 版本用於分析平台特定的問題（「這個 error 只出現在 iOS 17.4」）。&lt;/p>
&lt;p>不需要在 source 放裝置 ID 或使用者 ID — 這些屬於個人識別資訊，放在 source 會讓每一筆事件都攜帶 PII，增加去識別化的複雜度。Session ID 用於關聯同次使用的事件，已足夠取代裝置/使用者級別的追蹤。&lt;/p>
&lt;h2 id="原則二data-自由欄位">原則二：data 自由欄位&lt;/h2>
&lt;p>data 欄位是事件的附加資料區域，接受任意 JSON object。核心欄位（type、name、timestamp、source）有固定的 schema 驗證，data 的內容不做 schema 驗證（或做寬鬆驗證）。&lt;/p>
&lt;p>自由欄位的設計理由是「不同事件需要不同的附加資料」。&lt;code>terminal.connect.done&lt;/code> 需要 URL 和 duration；&lt;code>auth.biometric.failed&lt;/code> 需要 error code 和 fallback 方式。為每種事件定義固定的 data schema 會讓 schema 膨脹且頻繁變動。&lt;/p>
&lt;p>自由的代價是查詢時無法保證 data 內某個欄位一定存在。處理策略：查詢時用 optional access（&lt;code>data?.duration_ms&lt;/code>），統計時跳過缺少目標欄位的事件。&lt;/p>
&lt;h2 id="原則三v-版本演進">原則三：v 版本演進&lt;/h2>
&lt;p>v 欄位是整數版本號，標明「這筆事件是用哪個版本的 schema 產生的」。&lt;/p>
&lt;p>版本號解決的問題是 schema 變更時的向後相容。新版本的 SDK 產生 v=2 的事件，舊版本的 SDK 仍在產生 v=1 的事件。Collector 收到事件時根據 v 決定用哪個版本的驗證和處理邏輯。&lt;/p>
&lt;p>版本號的遞增規則：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>新增選填欄位&lt;/strong>：不需要遞增版本號。舊版事件缺少新欄位，collector 用預設值處理。&lt;/li>
&lt;li>&lt;strong>新增必填欄位&lt;/strong>：遞增版本號。舊版事件沒有這個欄位，collector 需要區分版本處理。&lt;/li>
&lt;li>&lt;strong>刪除或改名欄位&lt;/strong>：遞增版本號。collector 需要同時支援新舊版本的事件格式。&lt;/li>
&lt;li>&lt;strong>改變欄位型別&lt;/strong>：遞增版本號。string 改成 integer 等型別變更需要不同的解析邏輯。&lt;/li>
&lt;/ul>
&lt;h2 id="欄位命名慣例">欄位命名慣例&lt;/h2>
&lt;p>欄位名稱使用 snake_case（&lt;code>duration_ms&lt;/code>、&lt;code>error_code&lt;/code>），和 JSON 的慣例一致。避免在欄位名稱中編碼單位（&lt;code>duration&lt;/code> 不夠明確 — 是秒還是毫秒？），在名稱中加上單位後綴（&lt;code>duration_ms&lt;/code>、&lt;code>size_bytes&lt;/code>）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>完整欄位定義 → &lt;a href="https://tarrragon.github.io/blog/monitoring/02-log-schema/event-schema-fields/" data-link-title="event.schema.json 完整欄位解說" data-link-desc="監控事件的 JSON Schema 定義 — 每個欄位的語意、必填/選填、資料型別和設計理由">event.schema.json 完整欄位解說&lt;/a>&lt;/li>
&lt;li>Schema 版本演進的具體策略 → &lt;a href="https://tarrragon.github.io/blog/monitoring/02-log-schema/schema-versioning/" data-link-title="Schema 版本演進策略" data-link-desc="Backward compatible 的增量變更 — 新增欄位不改版、改名或改型別才改版、collector 同時支援多版本">Schema 版本演進策略&lt;/a>&lt;/li>
&lt;li>和 OpenTelemetry 的比較 → &lt;a href="https://tarrragon.github.io/blog/monitoring/02-log-schema/otel-comparison/" data-link-title="跟 OpenTelemetry 的 schema 差異對照" data-link-desc="自架 event schema 和 OTLP 的設計差異 — 為什麼 client-side 監控用簡化 schema、什麼時候切換到 OTLP">跟 OpenTelemetry 的 schema 差異對照&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>事件 schema 的欄位設計遵循三個原則：來源可追溯、擴展不破壞、版本可辨識。這三個原則讓 schema 從自用工具的 grep 查詢一直到商業方案的資料管線都能正常運作。</p>
<h2 id="原則一source-標明來源">原則一：source 標明來源</h2>
<p>每筆事件的 source 欄位記錄「這筆事件從哪裡來」。App 名稱、版本、平台、OS 版本 — 這些資訊在事件產生時由 SDK 自動填入，不依賴使用者或開發者手動標記。</p>
<p>source 的設計要點是「足夠區分但不過度」。<code>sdk</code> 和 <code>platform</code> 是必填——sdk 標明事件由哪個 SDK 實作產生（<code>js</code> / <code>flutter</code> / <code>python</code> / <code>go</code>），platform 標明運行平台（<code>ios</code> / <code>android</code> / <code>web</code> / <code>macos</code>）。兩者不能互相推導：同一個 platform（iOS）上可能有不同的 SDK（Flutter SDK 或 Swift 原生 SDK），同一個 SDK（Flutter）可能跑在不同 platform（iOS / Android / Web）。App 名稱和版本能區分「這是哪個 app 的哪個版本送來的事件」。OS 版本用於分析平台特定的問題（「這個 error 只出現在 iOS 17.4」）。</p>
<p>不需要在 source 放裝置 ID 或使用者 ID — 這些屬於個人識別資訊，放在 source 會讓每一筆事件都攜帶 PII，增加去識別化的複雜度。Session ID 用於關聯同次使用的事件，已足夠取代裝置/使用者級別的追蹤。</p>
<h2 id="原則二data-自由欄位">原則二：data 自由欄位</h2>
<p>data 欄位是事件的附加資料區域，接受任意 JSON object。核心欄位（type、name、timestamp、source）有固定的 schema 驗證，data 的內容不做 schema 驗證（或做寬鬆驗證）。</p>
<p>自由欄位的設計理由是「不同事件需要不同的附加資料」。<code>terminal.connect.done</code> 需要 URL 和 duration；<code>auth.biometric.failed</code> 需要 error code 和 fallback 方式。為每種事件定義固定的 data schema 會讓 schema 膨脹且頻繁變動。</p>
<p>自由的代價是查詢時無法保證 data 內某個欄位一定存在。處理策略：查詢時用 optional access（<code>data?.duration_ms</code>），統計時跳過缺少目標欄位的事件。</p>
<h2 id="原則三v-版本演進">原則三：v 版本演進</h2>
<p>v 欄位是整數版本號，標明「這筆事件是用哪個版本的 schema 產生的」。</p>
<p>版本號解決的問題是 schema 變更時的向後相容。新版本的 SDK 產生 v=2 的事件，舊版本的 SDK 仍在產生 v=1 的事件。Collector 收到事件時根據 v 決定用哪個版本的驗證和處理邏輯。</p>
<p>版本號的遞增規則：</p>
<ul>
<li><strong>新增選填欄位</strong>：不需要遞增版本號。舊版事件缺少新欄位，collector 用預設值處理。</li>
<li><strong>新增必填欄位</strong>：遞增版本號。舊版事件沒有這個欄位，collector 需要區分版本處理。</li>
<li><strong>刪除或改名欄位</strong>：遞增版本號。collector 需要同時支援新舊版本的事件格式。</li>
<li><strong>改變欄位型別</strong>：遞增版本號。string 改成 integer 等型別變更需要不同的解析邏輯。</li>
</ul>
<h2 id="欄位命名慣例">欄位命名慣例</h2>
<p>欄位名稱使用 snake_case（<code>duration_ms</code>、<code>error_code</code>），和 JSON 的慣例一致。避免在欄位名稱中編碼單位（<code>duration</code> 不夠明確 — 是秒還是毫秒？），在名稱中加上單位後綴（<code>duration_ms</code>、<code>size_bytes</code>）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>完整欄位定義 → <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>Schema 版本演進的具體策略 → <a href="/blog/monitoring/02-log-schema/schema-versioning/" data-link-title="Schema 版本演進策略" data-link-desc="Backward compatible 的增量變更 — 新增欄位不改版、改名或改型別才改版、collector 同時支援多版本">Schema 版本演進策略</a></li>
<li>和 OpenTelemetry 的比較 → <a href="/blog/monitoring/02-log-schema/otel-comparison/" data-link-title="跟 OpenTelemetry 的 schema 差異對照" data-link-desc="自架 event schema 和 OTLP 的設計差異 — 為什麼 client-side 監控用簡化 schema、什麼時候切換到 OTLP">跟 OpenTelemetry 的 schema 差異對照</a></li>
</ul>
]]></content:encoded></item></channel></rss>