<?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>AWS CloudWatch on Tarragon</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/</link><description>Recent content in AWS CloudWatch on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 01 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/index.xml" rel="self" type="application/rss+xml"/><item><title>CloudWatch Logs Insights 查詢與日誌治理</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/logs-insights-governance/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/logs-insights-governance/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">AWS CloudWatch&lt;/a> 的 vendor deep article，深化 overview「Logs Insights query」跟「Logs lifecycle」段。初次接觸 CloudWatch 的讀者建議先讀 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">CloudWatch 服務頁&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;h2 id="問題情境">問題情境&lt;/h2>
&lt;p>CloudWatch Logs 的成本模型跟 self-hosted log stack 不同 — ingestion、storage 跟 query 分開計費，每一層都有明確的 cost lever。理解 log group 設計、retention 設定與 subscription filter 的組合，才能在 AWS-native 環境下控制日誌成本而不犧牲事故判讀能力。&lt;/p>
&lt;h2 id="log-group-設計">Log group 設計&lt;/h2>
&lt;h3 id="拆分粒度">拆分粒度&lt;/h3>
&lt;p>Log group 是 CloudWatch Logs 的計費與 retention 邊界。同一個 log group 內的所有 log stream 共用 retention policy 和 access control（IAM resource policy）。&lt;/p>
&lt;p>合理的拆分粒度是 &lt;strong>一個服務一個 log group&lt;/strong>，而非一個帳號一個或一個 container 一個。服務級拆分讓 retention、查詢範圍與 IAM 權限自然對齊服務 ownership。&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>一個服務一個 log group&lt;/td>
 &lt;td>多數 production 服務&lt;/td>
 &lt;td>log group 數量增長需要 naming convention&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一個環境一個 log group&lt;/td>
 &lt;td>非常小的團隊、staging/dev 環境&lt;/td>
 &lt;td>混合多個服務的日誌，查詢時需要額外 filter&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一個 Lambda function 一個 log group&lt;/td>
 &lt;td>Lambda 預設行為&lt;/td>
 &lt;td>Lambda 數量多時 log group 爆量，管理成本高&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Lambda 的預設行為是每個 function 自動建一個 log group（&lt;code>/aws/lambda/&amp;lt;function-name&amp;gt;&lt;/code>）。function 數量超過數十個後，需要用 naming convention 加 tag 控制，否則 retention policy 難以統一套用。&lt;/p>
&lt;h3 id="naming-convention">Naming convention&lt;/h3>
&lt;p>推薦格式：&lt;code>/&amp;lt;environment&amp;gt;/&amp;lt;service&amp;gt;/&amp;lt;component&amp;gt;&lt;/code>，例如 &lt;code>/prod/checkout-api/app&lt;/code>、&lt;code>/prod/checkout-api/access-log&lt;/code>。統一前綴讓 Logs Insights 的 multi-log-group query 用 prefix matching 篩選。&lt;/p>
&lt;h2 id="logs-insights-查詢語法">Logs Insights 查詢語法&lt;/h2>
&lt;h3 id="核心語法">核心語法&lt;/h3>
&lt;p>Logs Insights 的查詢結構是 pipe-based：每行用 &lt;code>|&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">fields @timestamp, @message, @logStream
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">| filter @message like /ERROR/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">| parse @message &amp;#34;order_id=* status=*&amp;#34; as order_id, status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">| stats count(*) as error_count by status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">| sort error_count desc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">| limit 20&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>常用 command 對照：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Command&lt;/th>
 &lt;th>用途&lt;/th>
 &lt;th>注意事項&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>fields&lt;/code>&lt;/td>
 &lt;td>選擇要顯示的欄位&lt;/td>
 &lt;td>&lt;code>@timestamp&lt;/code>、&lt;code>@message&lt;/code> 是內建欄位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>filter&lt;/code>&lt;/td>
 &lt;td>條件篩選&lt;/td>
 &lt;td>支援 &lt;code>like /regex/&lt;/code>、&lt;code>=&lt;/code>、&lt;code>&amp;gt;&lt;/code>、&lt;code>in []&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>parse&lt;/code>&lt;/td>
 &lt;td>從非結構化 log 擷取欄位&lt;/td>
 &lt;td>glob pattern 用 &lt;code>*&lt;/code>、regex 用 &lt;code>/pattern/&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>stats&lt;/code>&lt;/td>
 &lt;td>聚合計算&lt;/td>
 &lt;td>&lt;code>count&lt;/code>、&lt;code>avg&lt;/code>、&lt;code>sum&lt;/code>、&lt;code>min&lt;/code>、&lt;code>max&lt;/code>、&lt;code>pct&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>sort&lt;/code>&lt;/td>
 &lt;td>排序&lt;/td>
 &lt;td>預設 &lt;code>@timestamp desc&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>display&lt;/code>&lt;/td>
 &lt;td>只顯示指定欄位（跟 &lt;code>fields&lt;/code> 互補）&lt;/td>
 &lt;td>用在 &lt;code>stats&lt;/code> 後只要看聚合結果&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="json-自動解析">JSON 自動解析&lt;/h3>
&lt;p>CloudWatch Logs 會自動辨識 JSON 格式的 log event。JSON 欄位用 dot notation 存取：&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">AWS CloudWatch</a> 的 vendor deep article，深化 overview「Logs Insights query」跟「Logs lifecycle」段。初次接觸 CloudWatch 的讀者建議先讀 <a href="/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">CloudWatch 服務頁</a>。</p></blockquote>
<h2 id="問題情境">問題情境</h2>
<p>CloudWatch Logs 的成本模型跟 self-hosted log stack 不同 — ingestion、storage 跟 query 分開計費，每一層都有明確的 cost lever。理解 log group 設計、retention 設定與 subscription filter 的組合，才能在 AWS-native 環境下控制日誌成本而不犧牲事故判讀能力。</p>
<h2 id="log-group-設計">Log group 設計</h2>
<h3 id="拆分粒度">拆分粒度</h3>
<p>Log group 是 CloudWatch Logs 的計費與 retention 邊界。同一個 log group 內的所有 log stream 共用 retention policy 和 access control（IAM resource policy）。</p>
<p>合理的拆分粒度是 <strong>一個服務一個 log group</strong>，而非一個帳號一個或一個 container 一個。服務級拆分讓 retention、查詢範圍與 IAM 權限自然對齊服務 ownership。</p>
<table>
  <thead>
      <tr>
          <th>拆分策略</th>
          <th>適合場景</th>
          <th>風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>一個服務一個 log group</td>
          <td>多數 production 服務</td>
          <td>log group 數量增長需要 naming convention</td>
      </tr>
      <tr>
          <td>一個環境一個 log group</td>
          <td>非常小的團隊、staging/dev 環境</td>
          <td>混合多個服務的日誌，查詢時需要額外 filter</td>
      </tr>
      <tr>
          <td>一個 Lambda function 一個 log group</td>
          <td>Lambda 預設行為</td>
          <td>Lambda 數量多時 log group 爆量，管理成本高</td>
      </tr>
  </tbody>
</table>
<p>Lambda 的預設行為是每個 function 自動建一個 log group（<code>/aws/lambda/&lt;function-name&gt;</code>）。function 數量超過數十個後，需要用 naming convention 加 tag 控制，否則 retention policy 難以統一套用。</p>
<h3 id="naming-convention">Naming convention</h3>
<p>推薦格式：<code>/&lt;environment&gt;/&lt;service&gt;/&lt;component&gt;</code>，例如 <code>/prod/checkout-api/app</code>、<code>/prod/checkout-api/access-log</code>。統一前綴讓 Logs Insights 的 multi-log-group query 用 prefix matching 篩選。</p>
<h2 id="logs-insights-查詢語法">Logs Insights 查詢語法</h2>
<h3 id="核心語法">核心語法</h3>
<p>Logs Insights 的查詢結構是 pipe-based：每行用 <code>|</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">fields @timestamp, @message, @logStream
</span></span><span class="line"><span class="ln">2</span><span class="cl">| filter @message like /ERROR/
</span></span><span class="line"><span class="ln">3</span><span class="cl">| parse @message &#34;order_id=* status=*&#34; as order_id, status
</span></span><span class="line"><span class="ln">4</span><span class="cl">| stats count(*) as error_count by status
</span></span><span class="line"><span class="ln">5</span><span class="cl">| sort error_count desc
</span></span><span class="line"><span class="ln">6</span><span class="cl">| limit 20</span></span></code></pre></div><p>常用 command 對照：</p>
<table>
  <thead>
      <tr>
          <th>Command</th>
          <th>用途</th>
          <th>注意事項</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>fields</code></td>
          <td>選擇要顯示的欄位</td>
          <td><code>@timestamp</code>、<code>@message</code> 是內建欄位</td>
      </tr>
      <tr>
          <td><code>filter</code></td>
          <td>條件篩選</td>
          <td>支援 <code>like /regex/</code>、<code>=</code>、<code>&gt;</code>、<code>in []</code></td>
      </tr>
      <tr>
          <td><code>parse</code></td>
          <td>從非結構化 log 擷取欄位</td>
          <td>glob pattern 用 <code>*</code>、regex 用 <code>/pattern/</code></td>
      </tr>
      <tr>
          <td><code>stats</code></td>
          <td>聚合計算</td>
          <td><code>count</code>、<code>avg</code>、<code>sum</code>、<code>min</code>、<code>max</code>、<code>pct</code></td>
      </tr>
      <tr>
          <td><code>sort</code></td>
          <td>排序</td>
          <td>預設 <code>@timestamp desc</code></td>
      </tr>
      <tr>
          <td><code>display</code></td>
          <td>只顯示指定欄位（跟 <code>fields</code> 互補）</td>
          <td>用在 <code>stats</code> 後只要看聚合結果</td>
      </tr>
  </tbody>
</table>
<h3 id="json-自動解析">JSON 自動解析</h3>
<p>CloudWatch Logs 會自動辨識 JSON 格式的 log event。JSON 欄位用 dot notation 存取：</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">fields @timestamp, requestId, level, message
</span></span><span class="line"><span class="ln">2</span><span class="cl">| filter level = &#34;ERROR&#34;
</span></span><span class="line"><span class="ln">3</span><span class="cl">| stats count(*) by bin(5m)</span></span></code></pre></div><p>如果 log 是 JSON 格式，<code>parse</code> 通常不需要 — 直接用欄位名稱。混合格式（部分 JSON、部分 plain text）時，需要用 <code>isPresent()</code> 判斷欄位是否存在。</p>
<h3 id="效能考量">效能考量</h3>
<p>Logs Insights 的查詢成本按掃描的 data 量計費（每 GB scanned），不按結果數。減少掃描量的方式：</p>
<ul>
<li>縮短時間範圍：事故判讀先查最近 30 分鐘，確認 pattern 後再擴大</li>
<li>指定 log group：避免對所有 log group 做全域查詢</li>
<li>用 <code>limit</code> 限制結果集大小（不影響掃描量，但減少資料傳輸）</li>
</ul>
<p>跨 log group 查詢最多同時查 50 個 log group。超過時需要拆成多次查詢或用 subscription filter 把資料匯到集中儲存。</p>
<h2 id="retention-policy">Retention policy</h2>
<h3 id="設定方式">設定方式</h3>
<p>Retention policy 在 log group 級別設定。每個 log group 可以獨立選擇 1 天到 10 年、或永不過期。</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">aws logs put-retention-policy <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --log-group-name /prod/checkout-api/app <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --retention-in-days <span class="m">30</span></span></span></code></pre></div><p>常見 retention 策略按服務性質分：</p>
<table>
  <thead>
      <tr>
          <th>服務類型</th>
          <th>建議 retention</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>核心交易路徑（checkout、payment）</td>
          <td>90-365 天</td>
          <td>事故回溯、合規稽核</td>
      </tr>
      <tr>
          <td>一般 API 服務</td>
          <td>30-90 天</td>
          <td>事故回溯足夠，cost 可控</td>
      </tr>
      <tr>
          <td>Background job / worker</td>
          <td>14-30 天</td>
          <td>失敗時看最近數天即可</td>
      </tr>
      <tr>
          <td>Lambda / short-lived function</td>
          <td>7-14 天</td>
          <td>高量低價值，過期快速清理</td>
      </tr>
      <tr>
          <td>Audit log</td>
          <td>365 天以上或永不過期</td>
          <td>法規要求，見 <a href="/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">4.12 Audit Log Governance</a></td>
      </tr>
  </tbody>
</table>
<p>未設定 retention 的 log group 預設永不過期 — 這是 CloudWatch 日誌成本超支的常見原因。新 log group 建立後應立即設定 retention。</p>
<h3 id="fintech-合規場景的-log-group-分離">FinTech 合規場景的 log group 分離</h3>
<p><a href="/blog/backend/04-observability/cases/fintech-audit-evidence-observability/" data-link-title="FinTech：審計證據鏈的可觀測性設計" data-link-desc="把交易與存取事件轉成可回查證據，降低合規審核與事故判讀落差。">FinTech 審計證據案例</a>揭露一個常見問題：audit log 跟 operational log 混在同一個 log group，retention 只能統一設定。結果要嘛 operational log 為了合規被迫留太久（成本浪費）、要嘛 audit log 跟著 operational log 的短 retention 被刪掉（合規風險）。</p>
<p>CloudWatch 的 log group 設計天然支援這種分離 — audit log 跟 operational log 用不同 log group、各自設定 retention：</p>
<table>
  <thead>
      <tr>
          <th>Log 類型</th>
          <th>Log group 命名</th>
          <th>Retention</th>
          <th>Log class</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>交易 audit log</td>
          <td><code>/prod/checkout-api/audit</code></td>
          <td>2555 天（7 年）</td>
          <td>Infrequent Access</td>
      </tr>
      <tr>
          <td>Application operational log</td>
          <td><code>/prod/checkout-api/app</code></td>
          <td>30 天</td>
          <td>Standard</td>
      </tr>
      <tr>
          <td>Access log（ALB / API Gateway）</td>
          <td><code>/prod/checkout-api/access</code></td>
          <td>90 天</td>
          <td>Standard</td>
      </tr>
  </tbody>
</table>
<p>Audit log group 的額外治理：</p>
<ul>
<li><strong>IAM 權限分離</strong>：audit log group 的讀取權限（<code>logs:GetLogEvents</code>）限縮到 compliance team 跟 security team，application developer 只能讀 operational log group。避免 audit log 被隨意查詢或汙染</li>
<li><strong>Immutability</strong>：CloudWatch Logs 本身不支援 WORM（write once read many），合規要求 immutable 存檔時用 subscription filter 把 audit log 同步送到 S3 + Object Lock</li>
<li><strong>Cross-account 集中</strong>：audit log 的 cross-account aggregation（見下方段落）的 IAM 權限要比 operational log 嚴格 — aggregated sink 的 destination 只能由 security team 控制</li>
</ul>
<h3 id="infrequent-access-log-class">Infrequent Access log class</h3>
<p>CloudWatch Logs 提供兩種 log class：<strong>Standard</strong>（完整查詢、即時 subscription filter、metric filter）跟 <strong>Infrequent Access</strong>（僅支援 Logs Insights 查詢、不支援即時 subscription filter 跟 metric filter、ingestion 成本約降 50%）。</p>
<p>Audit log 的存取模式通常是「寫入頻繁、查詢極少（只在稽核或事故時才查）」— 正好符合 Infrequent Access 的定位。把 7 年 retention 的 audit log group 設成 Infrequent Access，ingestion 成本直接砍半。</p>
<p>注意 Infrequent Access 的限制：不能用 subscription filter 即時轉發到 Lambda 或 Kinesis，不能用 metric filter 從 log 產生 CloudWatch metric。如果 audit log 需要即時異常偵測（例如偵測大量失敗交易），要用 Standard class + subscription filter 做即時處理、再用 Lambda 寫到長期 audit log group（Infrequent Access）。</p>
<h3 id="自動化套用">自動化套用</h3>
<p>用 AWS Config rule 或 CloudFormation / CDK 的 log group 定義統一設定 retention。Lambda function 自動建立的 log group 不會自動套用 retention，需要額外自動化（Lambda post-hook 或 EventBridge rule + Lambda 設定 retention）。</p>
<h2 id="cross-account-log-aggregation">Cross-account log aggregation</h2>
<h3 id="架構模式">架構模式</h3>
<p>多帳號環境下，常見做法是設立一個「觀測帳號」（observability account），把其他帳號的 logs 匯入。</p>
<p>兩種匯入方式：</p>
<p><strong>Subscription filter + Kinesis Data Firehose</strong>：每個 source 帳號的 log group 設 subscription filter，把 log event 送到 observability 帳號的 Kinesis Data Firehose，再寫到 S3 或 OpenSearch。適合需要長期存檔或進階查詢的場景。</p>
<p><strong>CloudWatch cross-account observability</strong>：AWS 原生功能，在 monitoring account 直接查詢 source accounts 的 CloudWatch 資料（metrics、logs、traces）。設定較簡單，但查詢延遲較高，且 Logs Insights 的 cross-account 查詢有 region 限制。</p>
<table>
  <thead>
      <tr>
          <th>匯入方式</th>
          <th>適合場景</th>
          <th>限制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Subscription filter + Firehose</td>
          <td>需要 S3 archive、OpenSearch 全文搜尋、離線分析</td>
          <td>每個 log group 最多 2 個 subscription filter</td>
      </tr>
      <tr>
          <td>Cross-account observability</td>
          <td>只需要 CloudWatch console 統一查詢</td>
          <td>同 region 限制、查詢延遲較高</td>
      </tr>
  </tbody>
</table>
<h3 id="subscription-filter-實務">Subscription filter 實務</h3>
<p>Subscription filter 可以把 log event 送到 Lambda（即時處理）、Kinesis Data Stream（緩衝）、Kinesis Data Firehose（直接寫 S3/OpenSearch）或另一個 log group。</p>
<p>每個 log group 最多 2 個 subscription filter — 這是硬限制。如果同一個 log group 需要同時送 S3 archive 跟即時 alerting，要用 Kinesis Data Stream 做 fan-out，讓 stream 下游各自消費。</p>
<p>filter pattern 語法支援 JSON 欄位匹配：</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">{ $.level = &#34;ERROR&#34; }</span></span></code></pre></div><p>只把 ERROR 級別的 log 送到 alerting pipeline，可以大幅降低下游處理量跟成本。</p>
<h2 id="cost-governance">Cost governance</h2>
<h3 id="計費結構">計費結構</h3>
<p>CloudWatch Logs 的成本由三個維度組成：</p>
<table>
  <thead>
      <tr>
          <th>計費項目</th>
          <th>計費方式</th>
          <th>常見比例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Ingestion</td>
          <td>每 GB ingested</td>
          <td>通常佔 50-70%</td>
      </tr>
      <tr>
          <td>Storage</td>
          <td>每 GB-month stored</td>
          <td>通常佔 20-40%</td>
      </tr>
      <tr>
          <td>Query（Logs Insights）</td>
          <td>每 GB scanned</td>
          <td>通常佔 5-15%</td>
      </tr>
  </tbody>
</table>
<p>Ingestion 是最大成本。降低 ingestion 的手段：</p>
<ul>
<li><strong>調整 log level</strong>：production 只保留 INFO 以上，DEBUG 只在問題排查時短暫開啟</li>
<li><strong>去除重複資訊</strong>：access log 跟 application log 不要記錄相同欄位</li>
<li><strong>用 metric filter 替代 log query</strong>：高頻計數（error count、request count）用 CloudWatch Metric Filter 從 log 產生 metric，查詢成本從 log scan 轉成 metric query</li>
</ul>
<h3 id="成本觀測">成本觀測</h3>
<p>用 CloudWatch 自己的 metric 觀測 log 成本：</p>
<ul>
<li><code>IncomingBytes</code>（per log group）：監控哪個 log group ingestion 最大</li>
<li><code>IncomingLogEvents</code>（per log group）：監控 event 數量</li>
<li>AWS Cost Explorer 按 CloudWatch 拆分：看 log ingestion vs storage vs API call 的比例</li>
</ul>
<h3 id="降本決策樹">降本決策樹</h3>
<p>判斷成本是否合理的順序：</p>
<ol>
<li>最大 ingestion 的 log group 是哪個？是否合理（核心服務的 access log 量大是正常的）</li>
<li>Retention 是否都有設定？未設定的 log group 會持續累積 storage 成本</li>
<li>是否有 DEBUG 級別 log 在 production 長期開啟？</li>
<li>是否有 subscription filter 把全量 log 送到外部？能否加 filter pattern 只送需要的部分</li>
</ol>
<h2 id="整合與下一步">整合與下一步</h2>
<ul>
<li>觀測管線整合：CloudWatch Logs → Subscription Filter → Kinesis Firehose → S3 / OpenSearch，見 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a></li>
<li>Audit log 治理：合規場景的 log retention 跟 access control，見 <a href="/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">4.12 Audit Log Governance</a></li>
<li>Evidence package：把 Logs Insights query link 跟時間窗放進 evidence，見 <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>OTel 整合：ADOT 可以把 log 送到 CloudWatch Logs 或其他 backend，見 <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>
</ul>
]]></content:encoded></item><item><title>CloudWatch Alarms 與 Composite Alarms 操作實務</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/alarms-composite-operations/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/alarms-composite-operations/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">AWS CloudWatch&lt;/a> 的 vendor deep article，深化 overview「Alarm + Composite alarm + EventBridge rule」段。初次接觸 CloudWatch 的讀者建議先讀 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">CloudWatch 服務頁&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;h2 id="問題情境">問題情境&lt;/h2>
&lt;p>CloudWatch Alarm 是 AWS 原生的告警機制，跟 Prometheus Alertmanager 或 Datadog Monitor 的定位相同 — 把 metric 異常轉成可操作通知。CloudWatch Alarm 的特性是跟 AWS 服務深度整合（Auto Scaling、SNS、Lambda、Systems Manager），但告警邏輯表達力比 PromQL alerting rule 弱。Composite Alarm 是 CloudWatch 用來降低 alert noise 的方式，把多個 alarm 的布林組合當成觸發條件。&lt;/p>
&lt;h2 id="metric-alarm-基礎">Metric Alarm 基礎&lt;/h2>
&lt;h3 id="alarm-參數">Alarm 參數&lt;/h3>
&lt;p>每個 metric alarm 由五個參數決定行為：&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>Metric&lt;/td>
 &lt;td>要監控的 metric（namespace + metric name + dimension）&lt;/td>
 &lt;td>&lt;code>AWS/EC2 CPUUtilization InstanceId=i-xxx&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Statistic&lt;/td>
 &lt;td>聚合方式（Average / Sum / Maximum / Minimum / p99）&lt;/td>
 &lt;td>根據 metric 性質選擇&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Period&lt;/td>
 &lt;td>每個 data point 的時間窗&lt;/td>
 &lt;td>60s（standard）/ 10s（high-resolution）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Evaluation periods&lt;/td>
 &lt;td>連續幾個 period 超過閾值才觸發&lt;/td>
 &lt;td>3-5 個 period 減少 flapping&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Threshold&lt;/td>
 &lt;td>觸發閾值&lt;/td>
 &lt;td>跟 SLO 對齊&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Evaluation periods 的意義是「連續 N 個 period 都違反閾值才進入 ALARM 狀態」。設太低（1 個 period）容易 flapping，設太高（10 個 period）會延遲告警。多數場景 3 個 period × 60 秒 = 3 分鐘是合理起點。&lt;/p>
&lt;h3 id="datapoints-to-alarm">Datapoints to Alarm&lt;/h3>
&lt;p>除了 evaluation periods，CloudWatch 還有 &lt;code>Datapoints to Alarm&lt;/code> 參數 — 在 evaluation periods 的窗口中，至少幾個 datapoint 超過閾值就觸發。例如 &lt;code>3 of 5&lt;/code> 代表最近 5 個 period 中有 3 個超過閾值就觸發。&lt;/p>
&lt;p>這個設計讓告警在有缺失 datapoint 的環境下更穩健。容器重啟、Lambda cold start 或 scrape timeout 都可能造成某些 period 沒有 datapoint，&lt;code>M of N&lt;/code> 模式避免因為缺失資料而延遲告警。&lt;/p>
&lt;h2 id="anomaly-detection-alarm">Anomaly Detection Alarm&lt;/h2>
&lt;h3 id="用途">用途&lt;/h3>
&lt;p>Anomaly Detection alarm 用機器學習模型建立 metric 的 baseline band，metric 偏離 band 就觸發。適合沒有固定閾值的 metric — 例如 request count 在白天高、晚上低，用固定閾值會在晚上誤報或白天漏報。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">AWS CloudWatch</a> 的 vendor deep article，深化 overview「Alarm + Composite alarm + EventBridge rule」段。初次接觸 CloudWatch 的讀者建議先讀 <a href="/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">CloudWatch 服務頁</a>。</p></blockquote>
<h2 id="問題情境">問題情境</h2>
<p>CloudWatch Alarm 是 AWS 原生的告警機制，跟 Prometheus Alertmanager 或 Datadog Monitor 的定位相同 — 把 metric 異常轉成可操作通知。CloudWatch Alarm 的特性是跟 AWS 服務深度整合（Auto Scaling、SNS、Lambda、Systems Manager），但告警邏輯表達力比 PromQL alerting rule 弱。Composite Alarm 是 CloudWatch 用來降低 alert noise 的方式，把多個 alarm 的布林組合當成觸發條件。</p>
<h2 id="metric-alarm-基礎">Metric Alarm 基礎</h2>
<h3 id="alarm-參數">Alarm 參數</h3>
<p>每個 metric alarm 由五個參數決定行為：</p>
<table>
  <thead>
      <tr>
          <th>參數</th>
          <th>說明</th>
          <th>常見設定</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Metric</td>
          <td>要監控的 metric（namespace + metric name + dimension）</td>
          <td><code>AWS/EC2 CPUUtilization InstanceId=i-xxx</code></td>
      </tr>
      <tr>
          <td>Statistic</td>
          <td>聚合方式（Average / Sum / Maximum / Minimum / p99）</td>
          <td>根據 metric 性質選擇</td>
      </tr>
      <tr>
          <td>Period</td>
          <td>每個 data point 的時間窗</td>
          <td>60s（standard）/ 10s（high-resolution）</td>
      </tr>
      <tr>
          <td>Evaluation periods</td>
          <td>連續幾個 period 超過閾值才觸發</td>
          <td>3-5 個 period 減少 flapping</td>
      </tr>
      <tr>
          <td>Threshold</td>
          <td>觸發閾值</td>
          <td>跟 SLO 對齊</td>
      </tr>
  </tbody>
</table>
<p>Evaluation periods 的意義是「連續 N 個 period 都違反閾值才進入 ALARM 狀態」。設太低（1 個 period）容易 flapping，設太高（10 個 period）會延遲告警。多數場景 3 個 period × 60 秒 = 3 分鐘是合理起點。</p>
<h3 id="datapoints-to-alarm">Datapoints to Alarm</h3>
<p>除了 evaluation periods，CloudWatch 還有 <code>Datapoints to Alarm</code> 參數 — 在 evaluation periods 的窗口中，至少幾個 datapoint 超過閾值就觸發。例如 <code>3 of 5</code> 代表最近 5 個 period 中有 3 個超過閾值就觸發。</p>
<p>這個設計讓告警在有缺失 datapoint 的環境下更穩健。容器重啟、Lambda cold start 或 scrape timeout 都可能造成某些 period 沒有 datapoint，<code>M of N</code> 模式避免因為缺失資料而延遲告警。</p>
<h2 id="anomaly-detection-alarm">Anomaly Detection Alarm</h2>
<h3 id="用途">用途</h3>
<p>Anomaly Detection alarm 用機器學習模型建立 metric 的 baseline band，metric 偏離 band 就觸發。適合沒有固定閾值的 metric — 例如 request count 在白天高、晚上低，用固定閾值會在晚上誤報或白天漏報。</p>
<h3 id="設定">設定</h3>





<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">aws cloudwatch put-anomaly-detector <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --namespace AWS/ApplicationELB <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --metric-name RequestCount <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --dimensions <span class="nv">Name</span><span class="o">=</span>LoadBalancer,Value<span class="o">=</span>app/my-alb/xxx <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --stat Sum</span></span></code></pre></div><p>Anomaly Detection 需要至少兩週的歷史資料才能建立可靠 baseline。新服務上線初期先用固定閾值 alarm，等累積足夠資料後再切換。</p>
<h3 id="band-width-控制">Band width 控制</h3>
<p>Anomaly Detection band 的寬度用標準差倍數控制（預設 2）。band 太窄（1x）容易誤報，太寬（3x）漏報。生產經驗是 API latency 用 2x、batch job duration 用 3x（batch 的自然波動較大）。</p>
<h2 id="composite-alarm">Composite Alarm</h2>
<h3 id="問題alert-noise">問題：Alert noise</h3>
<p>單一 metric alarm 太多時，on-call 會收到大量相關但重複的通知。一個下游服務故障可能同時觸發 latency alarm、error rate alarm、timeout alarm、queue lag alarm — 都指向同一個根因，但各自通知。</p>
<h3 id="解法布林組合">解法：布林組合</h3>
<p>Composite Alarm 用布林表達式組合多個 alarm，只在組合條件成立時觸發。</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">ALARM(&#34;checkout-latency-high&#34;)
</span></span><span class="line"><span class="ln">2</span><span class="cl">AND ALARM(&#34;payment-error-rate-high&#34;)
</span></span><span class="line"><span class="ln">3</span><span class="cl">AND NOT ALARM(&#34;scheduled-maintenance-window&#34;)</span></span></code></pre></div><p>這個組合代表：checkout latency 高且 payment error rate 也高，但排除了計畫維護視窗 — 才通知 on-call。</p>
<h3 id="設計原則">設計原則</h3>
<p>Composite Alarm 的設計應該反映事故判讀邏輯，而非機械式組合。三個常見模式：</p>
<p><strong>Symptom + cause 組合</strong>：外部症狀（latency 高）加上內部原因（DB connection pool 飽和）同時成立才通知。避免 latency 短暫抖動就告警。</p>
<p><strong>Cross-service correlation</strong>：多個服務同時出現異常時觸發「可能是 shared dependency 問題」的 composite alarm。一個服務異常可能是部署問題，多個同時異常更可能是共用依賴（load balancer、DNS、shared database）。</p>
<p><strong>Suppression window</strong>：用 maintenance window alarm 做 NOT 條件，在計畫維護期間抑制告警。</p>
<h3 id="限制">限制</h3>
<ul>
<li>Composite Alarm 最多引用 5 個 child alarm</li>
<li>巢狀深度最多 1 層（composite 不能引用另一個 composite）</li>
<li>Composite Alarm 本身不產生 metric，只做觸發邏輯</li>
</ul>
<p>超過 5 個 child alarm 時，需要把相關 alarm 先組成一個 composite，再讓上層 composite 引用。但因為不支援巢狀，實際能組合的 alarm 數量有限。複雜告警邏輯需要用 EventBridge rule 搭配 Lambda 處理。</p>
<h2 id="alarm-actions">Alarm actions</h2>
<h3 id="常見-action-類型">常見 action 類型</h3>
<p>Alarm 進入 ALARM 狀態時可以觸發多種 action：</p>
<table>
  <thead>
      <tr>
          <th>Action 類型</th>
          <th>用途</th>
          <th>設定方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SNS Topic</td>
          <td>通知 on-call（email、SMS、PagerDuty integration）</td>
          <td>alarm action → SNS ARN</td>
      </tr>
      <tr>
          <td>Auto Scaling policy</td>
          <td>自動擴容</td>
          <td>alarm action → scaling policy ARN</td>
      </tr>
      <tr>
          <td>Lambda function</td>
          <td>自訂邏輯（建 ticket、關閉服務、修改 config）</td>
          <td>alarm action → Lambda ARN（透過 SNS）</td>
      </tr>
      <tr>
          <td>Systems Manager runbook</td>
          <td>自動執行 remediation runbook</td>
          <td>alarm action → SSM automation ARN</td>
      </tr>
      <tr>
          <td>EC2 action</td>
          <td>停止 / 重啟 / 終止 instance</td>
          <td>alarm action → EC2 action（僅限 EC2 metric）</td>
      </tr>
  </tbody>
</table>
<p>生產環境通常同時設定 ALARM 跟 OK action — ALARM 時通知 on-call，回到 OK 時自動 resolve incident。忘記設 OK action 會造成 on-call 收到告警但不知道何時恢復。</p>
<h3 id="跟-eventbridge-整合">跟 EventBridge 整合</h3>
<p>CloudWatch Alarm 狀態變更會自動送到 EventBridge（事件類型 <code>CloudWatch Alarm State Change</code>）。EventBridge rule 可以做更靈活的路由：</p>
<ul>
<li>根據 alarm name pattern 路由到不同 SNS topic</li>
<li>根據 alarm description 中的 severity tag 決定通知管道</li>
<li>多個 alarm 同時進入 ALARM 時觸發 incident 建立</li>
</ul>
<p>EventBridge 的路由能力彌補了 CloudWatch Alarm 本身路由邏輯簡單的限制。</p>
<h2 id="missing-data-處理">Missing data 處理</h2>
<h3 id="四種策略">四種策略</h3>
<p>Alarm evaluation 遇到缺失 datapoint 時，有四種處理方式：</p>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>行為</th>
          <th>適合場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>missing</code></td>
          <td>維持上一個狀態</td>
          <td>多數場景的預設選擇</td>
      </tr>
      <tr>
          <td><code>breaching</code></td>
          <td>視為超過閾值</td>
          <td>metric 消失本身就是問題（heartbeat metric）</td>
      </tr>
      <tr>
          <td><code>notBreaching</code></td>
          <td>視為正常</td>
          <td>metric 在低流量時段自然消失</td>
      </tr>
      <tr>
          <td><code>ignore</code></td>
          <td>跳過該 period</td>
          <td>不影響 evaluation window</td>
      </tr>
  </tbody>
</table>
<p><code>breaching</code> 適合 heartbeat 類型的 metric — 服務應該持續回報 metric，停止回報代表服務掛了。<code>notBreaching</code> 適合流量驅動的 metric — 凌晨沒有 request 時自然沒有 latency datapoint，不應該觸發告警。</p>
<p>選錯 missing data 策略是 alarm flapping 的常見原因。Lambda function 的 metric 在沒有 invocation 時沒有 datapoint，用預設的 <code>missing</code> 或 <code>breaching</code> 都會造成問題。Lambda metric alarm 應該用 <code>notBreaching</code>。</p>
<h2 id="cross-region-限制">Cross-region 限制</h2>
<p>CloudWatch Alarm 跟 metric 綁定在同一個 region。跨 region 告警的兩種方式：</p>
<p><strong>Cross-account observability</strong>：monitoring account 可以看到 source account 的 CloudWatch 資料，但 alarm 仍然必須建在 metric 所在的 region。</p>
<p><strong>Custom metric replication</strong>：用 Lambda 或 Kinesis 把 metric 從 source region publish 到 central region，在 central region 建立統一 alarm。增加複雜度跟延遲，但能集中管理告警。</p>
<p>多數團隊選擇在每個 region 建各自的 alarm，用統一的 SNS topic（跨 region publish 到 central topic）收斂通知。告警邏輯去中心化，通知管道集中化。</p>
<h2 id="cost-考量">Cost 考量</h2>
<p>CloudWatch Alarm 的主要成本來自：</p>
<table>
  <thead>
      <tr>
          <th>計費項目</th>
          <th>計費方式</th>
          <th>常見數量</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Standard resolution alarm</td>
          <td>每 alarm / month</td>
          <td>多數服務 10-50 個 alarm</td>
      </tr>
      <tr>
          <td>High-resolution alarm（10s）</td>
          <td>每 alarm / month（3 倍 standard）</td>
          <td>只用在關鍵 SLI</td>
      </tr>
      <tr>
          <td>Anomaly Detection alarm</td>
          <td>每 alarm / month（含 ML 模型）</td>
          <td>比 standard 貴約 2-3 倍</td>
      </tr>
      <tr>
          <td>Composite Alarm</td>
          <td>免費</td>
          <td>只算 child alarm</td>
      </tr>
  </tbody>
</table>
<p>數量控制的判準：每個服務 10-30 個 metric alarm 加 2-5 個 composite alarm 是合理範圍。超過 100 個 alarm 時先檢查是否有冗餘（同一 metric 不同 period 的重複 alarm）。</p>
<h2 id="整合與下一步">整合與下一步</h2>
<ul>
<li>告警設計原則：alarm 跟 dashboard 的搭配，見 <a href="/blog/backend/04-observability/dashboard-alert/" data-link-title="4.4 dashboard 與 alert 設計" data-link-desc="讓 dashboard 與 alert 對應 runbook 與容量趨勢">4.4 Dashboard 與 Alert 設計</a></li>
<li>SLI/SLO 對齊：把 alarm 閾值跟 SLO 對齊，見 <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">4.6 SLI 量測與 SLO 訊號設計</a></li>
<li>Log-based alerting：從 log 產生 metric 再建 alarm，見 <a href="../logs-insights-governance/">CloudWatch Logs Insights 查詢與日誌治理</a></li>
<li>事故響應整合：alarm → EventBridge → PagerDuty / incident tool，見 <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>