Skip to main content

Command Palette

Search for a command to run...

來玩 OTel Go Metric Exemplar

Updated
6 min read
來玩 OTel Go Metric Exemplar

在今年寫書時,第 6.4 小節關於 Metric Exemplar 的演示,當時我使用的是 Prometheus Exempar 來演示,因為當時這項功能還處於提案階段,但最近幾次的 Release(v1.31.0v1.32.0) ,已經 OTel Go Metric Exemplar 功能已經稍稍完整了,就讓我們來玩玩看。

Exemplar 是用來讓 metric 與 trace 能相互關聯重要資料。之前的版本之所以不夠完整的原因是缺少了 ExemplarFilter 機制的完整支援,內建的機制有 AlwaysOn、AlwaysOff 和 TraceBased。以及 ExamplarReservoir 機制,以及 View。這些書上會解釋介紹,這裡就先不多做介紹了。TraceBased 主要是看 Span 的 Sampled Flag而已,這在書上的第6-1小節有介紹。

為什麼一定要有這些機制才算基本完整?因為連 Trace 都有 sampling 機制,Metric Exemplar 沒有的話會很怪,Trace 如果都不被紀錄了,Exemplar 卻被記錄下來也沒作用。

// 建立 meter provider 時,也選定 Exemplar Filter。
meterProvider := metric.NewMeterProvider(
        metric.WithExemplarFilter(exemplar.TraceBasedFilter),
        metric.WithReader(metric.NewPeriodicReader(metricExporter,
            // Default is 1m. Set to 3s for demonstrative purposes.
            metric.WithInterval(3*time.Second))),
)

Exemplar 的用途

  1. 快速定位問題

    • 當某個 metric(例如高延遲或高錯誤率)異常時,可以快速通過 Exemplar 跳轉到分散式追蹤查看具體原因。畢竟 Alert 與值班人員看的都是 metric,不會一直盯著 Trace 與 Log 在那邊看。大家看股票也是看最新價格跟掛單簿,不會細到在那邊一直看每一筆交易的細節。
  2. 補充上下文資訊

    • 讓 metric 數據更有意義,與系統行為建立更緊密的關聯。因為 metric 其實是個非常粗顆粒度的遙測資料,因為一筆 metric data 就只能說明一個事情表達的數值,如果要說明足夠廣泛的事情,那就要多種類的 metric,且不是任何事情都能轉成數值。但是 metric 很直觀便於決策,但卻難以做細部的展開與分析。
  3. 高效率問題分析

    • 適合於高併發場景下的性能診斷,例如 HTTP 請求的異常分析。

假設:

  • 您有一個 HTTP 服務,記錄了延遲(Latency)和請求數量(Request Count)。

  • 在某個桶(如延遲為 100ms 至 200ms)中,系統發現了異常增長。

解決:

  • 通過 Exemplar,您可以快速定位該延遲範圍內的具體請求。

  • 通過 Exemplar 提供的 TraceIDSpanID,查看該請求的完整分布式追蹤,找出導致延遲的根本原因(例如下游服務超時)。

Show Examplar on Grafana

來到 Grafana 選到 metric 尾字是bucket 的,然後中間的 Exemplars 功能要打開,就能看見一堆小點點在儀表板上,那就是 Exemplar。

接著,隨便選一個,我們就能看見上面的資訊,其中最特別的就是 TraceId與SpanId了。

要能夠從 metric 的 trace_id 連結至 trace 資料頁面,我們需要去 Grfana Data Source → Prometheus 設定中的 Exemplars 設定完成能了。

再回來 Explore 頁面上就會看見 trace_id 旁邊有個 Query with TraceDemo 的按鈕了。

然後我們就能在一個畫面中,跟具有異常的指標區間,找到想了解的 Exemplar data,然後展開對應的 tracing 來了解此時的系統之間的行為。

進行探索的過程中,metric 是很好的直觀展示數據的方式。也許大家只在資源用了多少,但其實系統表現與性能更為重要。靠 log 其實有點太細了,雖然能夠快速 filter,但你會發現分析時,你還是會將 log 做成圖表在看,因為我們要找到的是時間連續中的異常表現區間。

Metric Instrument 種類

在書上的第5-3小節中,有提到幾種計量類型 Counter、UpDownCounter、Gauge 與 Histogram 。我們在演示程式中也是在程式中宣告這些類型在測量這些事件。

    rollCnt, err = meter.Int64Counter("dice.rolls",
        metric.WithDescription("The number of rolls by roll value"),
        metric.WithUnit("{roll}"))

    rollCntHistogram, err = meter.Int64Histogram("dice.rolls",
        metric.WithDescription("The number of rolls by roll value"),
        metric.WithUnit("{roll}"),
    )

    // 增加 Span Counter 並附加 Exemplar
    rollCnt.Add(ctx, 1, metric.WithAttributes(rollValueAttr))

    // 記錄到 Histogram 並附加 Exemplar
    rollCntHistogram.Record(ctx, int64(roll), metric.WithAttributes(rollValueAttr))

Data Point

{
    "Resource": [...],
    "ScopeMetrics": [
        {
            "Scope": {
                "Name": "go.opentelemetry.io/otel/example/dice",
                "Version": "",
                "SchemaURL": "",
                "Attributes": null
            },
            "Metrics": [
                {
                    "Name": "dice.rolls",
                    "Description": "The number of rolls by roll value",
                    "Unit": "{roll}",
                    "Data": {
                        "DataPoints": [
                            {
                                "Attributes": [
                                    {
                                        "Key": "roll.value",
                                        "Value": {
                                            "Type": "INT64",
                                            "Value": 1
                                        }
                                    }
                                ],
                                "StartTime": "2024-11-21T01:52:45.284089623+08:00",
                                "Time": "2024-11-21T01:52:51.285312908+08:00",
                                "Value": 1,
                                "Exemplars": [
                                    {
                                        "FilteredAttributes": null,
                                        "Time": "2024-11-21T01:52:47.929584861+08:00",
                                        "Value": 1,
                                        "SpanID": "/nfde45R6CM=",
                                        "TraceID": "J+uh/Jv7OzD0v6aF6ID+Xw=="
                                    }
                                ]
                            },
                            {
                                "Attributes": [
                                    {
                                        "Key": "roll.value",
                                        "Value": {
                                            "Type": "INT64",
                                            "Value": 4
                                        }
                                    }
                                ],
                                "StartTime": "2024-11-21T01:52:45.284089623+08:00",
                                "Time": "2024-11-21T01:52:51.285312908+08:00",
                                "Value": 1,
                                "Exemplars": [
                                    {
                                        "FilteredAttributes": null,
                                        "Time": "2024-11-21T01:52:49.185654994+08:00",
                                        "Value": 1,
                                        "SpanID": "ZnMwibK/1Cs=",
                                        "TraceID": "vmjwm8lDTqfTCGF9iKShHA=="
                                    }
                                ]
                            },
                            {
                                "Attributes": [
                                    {
                                        "Key": "roll.value",
                                        "Value": {
                                            "Type": "INT64",
                                            "Value": 3
                                        }
                                    }
                                ],
                                "StartTime": "2024-11-21T01:52:45.284089623+08:00",
                                "Time": "2024-11-21T01:52:51.285312908+08:00",
                                "Value": 1,
                                "Exemplars": [
                                    {
                                        "FilteredAttributes": null,
                                        "Time": "2024-11-21T01:52:46.471871714+08:00",
                                        "Value": 1,
                                        "SpanID": "MvoQs46zXbY=",
                                        "TraceID": "884mmUbC1WruNPrWeyzMhw=="
                                    }
                                ]
                            }
                        ],
                        "Temporality": "CumulativeTemporality",
                        "IsMonotonic": true
                    }
                },
                {
                    "Name": "dice.rolls",
                    "Description": "The histogram of rolls by roll value",
                    "Unit": "{roll}",
                    "Data": {
                        "DataPoints": [
                            {
                                "Attributes": [
                                    {
                                        "Key": "roll.value",
                                        "Value": {
                                            "Type": "INT64",
                                            "Value": 3
                                        }
                                    }
                                ],
                                "StartTime": "2024-11-21T01:52:45.284125019+08:00",
                                "Time": "2024-11-21T01:52:51.285330742+08:00",
                                "Count": 1,
                                "Bounds": [
                                    0,
                                    5,
                                    10,
                                    25,
                                    50,
                                    75,
                                    100,
                                    250,
                                    500,
                                    750,
                                    1000,
                                    2500,
                                    5000,
                                    7500,
                                    10000
                                ],
                                "BucketCounts": [
                                    0,
                                    1,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0
                                ],
                                "Min": 3,
                                "Max": 3,
                                "Sum": 3,
                                "Exemplars": [
                                    {
                                        "FilteredAttributes": null,
                                        "Time": "2024-11-21T01:52:46.471880671+08:00",
                                        "Value": 3,
                                        "SpanID": "MvoQs46zXbY=",
                                        "TraceID": "884mmUbC1WruNPrWeyzMhw=="
                                    }
                                ]
                            },
                            {
                                "Attributes": [
                                    {
                                        "Key": "roll.value",
                                        "Value": {
                                            "Type": "INT64",
                                            "Value": 1
                                        }
                                    }
                                ],
                                "StartTime": "2024-11-21T01:52:45.284125019+08:00",
                                "Time": "2024-11-21T01:52:51.285330742+08:00",
                                "Count": 1,
                                "Bounds": [
                                    0,
                                    5,
                                    10,
                                    25,
                                    50,
                                    75,
                                    100,
                                    250,
                                    500,
                                    750,
                                    1000,
                                    2500,
                                    5000,
                                    7500,
                                    10000
                                ],
                                "BucketCounts": [
                                    0,
                                    1,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0
                                ],
                                "Min": 1,
                                "Max": 1,
                                "Sum": 1,
                                "Exemplars": [
                                    {
                                        "FilteredAttributes": null,
                                        "Time": "2024-11-21T01:52:47.929600621+08:00",
                                        "Value": 1,
                                        "SpanID": "/nfde45R6CM=",
                                        "TraceID": "J+uh/Jv7OzD0v6aF6ID+Xw=="
                                    }
                                ]
                            },
                            {
                                "Attributes": [
                                    {
                                        "Key": "roll.value",
                                        "Value": {
                                            "Type": "INT64",
                                            "Value": 4
                                        }
                                    }
                                ],
                                "StartTime": "2024-11-21T01:52:45.284125019+08:00",
                                "Time": "2024-11-21T01:52:51.285330742+08:00",
                                "Count": 1,
                                "Bounds": [
                                    0,
                                    5,
                                    10,
                                    25,
                                    50,
                                    75,
                                    100,
                                    250,
                                    500,
                                    750,
                                    1000,
                                    2500,
                                    5000,
                                    7500,
                                    10000
                                ],
                                "BucketCounts": [
                                    0,
                                    1,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0
                                ],
                                "Min": 4,
                                "Max": 4,
                                "Sum": 4,
                                "Exemplars": [
                                    {
                                        "FilteredAttributes": null,
                                        "Time": "2024-11-21T01:52:49.185663229+08:00",
                                        "Value": 4,
                                        "SpanID": "ZnMwibK/1Cs=",
                                        "TraceID": "vmjwm8lDTqfTCGF9iKShHA=="
                                    }
                                ]
                            }
                        ],
                        "Temporality": "CumulativeTemporality"
                    }
                }
            ]
        }
    ]
}

這段資料是一個 OpenTelemetry SDK 輸出的 Metrics 資料,包含了有關擲骰子遊戲的 metric data,特別是每次擲骰子的結果。以下是對該資料的詳細說明:

"Metrics" 是一個包含多個 metric 的數組。在此資料中,有兩個名為 "dice.rolls" 的指標,但它們的描述和數據類型不同。

  1. dice.rolls - Counter

     {
         "Name": "dice.rolls",
         "Description": "The number of rolls by roll value",
         "Unit": "{roll}",
         "Data": { ... }
     }
    
    • Namedice.rolls,指標名稱。

    • DescriptionThe number of rolls by roll value,描述該 metric 表示每個擲骰子結果的次數。

    • Unit{roll},單位為「擲骰子次數」。

    • Data:包含具體的DataPoints

Data 詳細解析
  • DataPointsDataPoint 的列表,每個 DataPoint 代表一個擲骰子結果的統計。

  • TemporalityCumulativeTemporality,表示數據是累積的。書裡還有講到 Delta

  • IsMonotonictrue,表示計數器的值只會遞增,不會減少。

DataPoints 內容

每個DataPoints包含以下信息:

  1. Attributes:屬性,包含 roll.value,即擲骰子的結果(點數)。

  2. StartTime:該DataPoint開始收集的時間。

  3. Time:該DataPoint的記錄時間。

  4. Value:計數器的累積值,表示該點數的擲骰子次數。

  5. Exemplars:示例數據,包含了與該DataPoint相關的詳細上下文。

Exemplars
  • FilteredAttributes:過濾的屬性,這裡為 null

  • Time:示例的時間戳。

  • Value:示例的值,與DataPointValue 相同。

  • SpanIDTraceID:與該數據點相關聯的追蹤信息,可用於鏈接到具體的 Tracing中,便於進一步分析。

範例
  • 擲出點數 1 的數據點

      jsonCopy code{
          "Attributes": [
              {
                  "Key": "roll.value",
                  "Value": {
                      "Type": "INT64",
                      "Value": 1
                  }
              }
          ],
          "Value": 1,
          "Exemplars": [
              {
                  "Time": "2024-11-21T01:52:47.929584861+08:00",
                  "Value": 1,
                  "SpanID": "/nfde45R6CM=",
                  "TraceID": "J+uh/Jv7OzD0v6aF6ID+Xw=="
              }
          ]
      }
    
    • 表示從開始時間到目前,共擲出了 1 次點數為 1 的結果。
  1. dice.rolls - Histogram

     {
         "Name": "dice.rolls",
         "Description": "The histogram of rolls by roll value",
         "Unit": "{roll}",
         "Data": { ... }
     }
    
  • Name:同樣為 dice.rolls

  • DescriptionThe histogram of rolls by roll value,表示擲骰子結果的直方圖分布。

  • Unit{roll},單位為「擲骰子點數」。

  • Data:包含直方圖數據。

Data 詳細解析
  • DataPoints:直方圖數據點的列表。

  • TemporalityCumulativeTemporality,累積的數據。

DataPoints 內容

每個數據點包含:

  1. Attributes:與上述相同,包含 roll.value

  2. StartTimeTime:數據點的時間範圍。

  3. Count:樣本總數,表示該點數出現的次數。

  4. Bounds:直方圖的桶邊界,定義了直方圖的分布範圍。

  5. BucketCounts:每個桶中的計數,表示落在該範圍內的樣本數量。

  6. MinMax:該數據點的最小值和最大值。

  7. Sum:所有樣本值的總和。

  8. Exemplars:與上述相同的示例數據。

範例
  • 擲出點數 3 的直方圖數據點

      {
          "Attributes": [
              {
                  "Key": "roll.value",
                  "Value": {
                      "Type": "INT64",
                      "Value": 3
                  }
              }
          ],
          "Count": 1,
          "Bounds": [0, 5, 10, 25, ...],
          "BucketCounts": [0, 1, 0, 0, ...],
          "Min": 3,
          "Max": 3,
          "Sum": 3,
          "Exemplars": [
              {
                  "Time": "2024-11-21T01:52:46.471880671+08:00",
                  "Value": 3,
                  "SpanID": "MvoQs46zXbY=",
                  "TraceID": "884mmUbC1WruNPrWeyzMhw=="
              }
          ]
      }
    
  • Count:1,表示總共擲出 1 次點數為 3。

  • BucketCounts:表示在第二個桶(範圍為 0-5)內有 1 個樣本。

  • Min/Max/Sum:因為只有一個樣本,所以最小值、最大值和總和都為 3。

  • Exemplars:包含了與該數據點相關的追蹤信息。


  • 這些資料反映了在某個時間範圍內(從 StartTimeTime),擲骰子遊戲的結果統計。

  • 計數器指標(第一個 dice.rolls):

    • 記錄了每個點數出現的累積次數。

    • 每個點數(1、3、4)都有一個數據點,表示該點數出現的總次數為 1。

  • 直方圖指標(第二個 dice.rolls):

    • 記錄了擲骰子結果的分布情況。

    • 將結果按照定義的桶(Bounds)進行分類,計算每個桶內的樣本數(BucketCounts)。

    • 由於骰子點數為 1-6,樣本都落在第二個桶(0-5)內。

  • Exemplars

    • 為每個數據點提供了具體的示例,包含了時間、值、以及相關的 SpanIDTraceID

    • 可以用於追蹤該DataPoint在分散是系統中的上下文,方便進一步的性能分析和問題排查。

  • 性能監控

    • 通過監控 dice.rolls 指標,可以了解不同點數出現的頻率,檢查是否存在偏差(例如,某個點數出現的次數異常高或低)。
  • 分布分析

    • 使用直方圖可以分析擲骰子結果的分布情況,確認結果是否符合預期的均勻分布。
  • 問題排查

    • 利用 Exemplar 中的 TraceIDSpanID,可以追蹤到具體的操作或請求,幫助定位可能的問題。

Event Model

以上這些都還是在應用程式中,你們覺得這些資料屬於下圖的哪個呢?

Events → Data Stream → Timeseries Diagram

答案那一大坨 Data Points 都是屬於 In Transit︰OTLP Stream Model。而骰骰子,API的請求阿等等的,那些 Data Points 發生被捕或的地方,才是屬於 Event Model

Event Model 的特徵

  1. 原始觀測事件:

    • 事件模型的基礎是記錄實時或按需的數據點(如單次 HTTP 請求大小)。

    • 記錄的是原始數據,尚未聚合或壓縮。

  2. 數據轉換的重要性:

    • 將原始事件數據轉換成適合後端存儲和分析的指標流(Metric Streams)。

    • 避免直接報告大量事件數據至後端,減少網路和處理資源的壓力。

  3. 多重輸出能力:

    • 同一事件數據可映射成多種指標格式,靈活滿足不同的監控需求。

    • 例如,ValueRecorder 可生成 Gauge(即時量規)、Histogram(直方圖)、Sum(總和)等多種 OTLP Stream Model。

OpenTelemetry 中的事件模型關鍵點

  • 檢測工具的彈性:

    • OpenTelemetry 的檢測工具設計允許靈活的 Stream Model 生成,並提供合理的默認映射。
  • 應用場景:

    • 適用於需要記錄和壓縮高頻率數據的場景,例如記錄 HTTP 請求、記錄數據分佈等。
  • 高效傳輸:

    • 通過壓縮和聚合減少資料量,提升傳輸和儲存效率。不管是應用程式傳輸到 OTel Collector 還是 OTel Collector 之間相互傳輸,傳遞的都是 OTLP stream model。包括你在 OTel Collector 用 debug 輸出在 terminal 上看到的也是。

Metric Instrument 轉成 OTLP stream model 的程式碼在exporter.go的Export()

// Export transforms and transmits metric data to an OTLP receiver.
//
// This method returns an error if called after Shutdown.
// This method returns an error if the method is canceled by the passed context.
func (e *Exporter) Export(ctx context.Context, rm *metricdata.ResourceMetrics) error {
    ...
    otlpRm, err := transform.ResourceMetrics(rm)
    ...
}

而 OTLP stream model 的定義則在otlp/metrics/v1/metrics.pb.go。這種換過程中的設計有用到 Go gerneric type 一些技巧來減少程式碼的重複,有興趣之後再寫一篇。

OTLP Metrics Model

上一段提到的是 Go exporter 的檔案定義在哪,那這裡我們接著看看 OTel collector 的檔案定義又在哪?程式碼從 OTLP receiver 爬著爬著來到這隻pdata/internal/data/protogen/metrics/v1/metrics.pb.go。看檔名好像跟前面題的不同,不是說都是傳遞OTLP stream model嘛?

別急,讓我們看看它編譯的來源檔案。來源都是這隻// source: opentelemetry/proto/metrics/v1/metrics.proto,所以其實都一樣的。

Time Seriese Model

但這些遙測資料最終還是要落地的。因為 OTel 框架並沒提供一個儲存方案來儲存,所以最後還是要儲存像 Prometheus、ClickHouse這類的服務中。

所以 TimeSeries Model 是指 OpenTelemetry 收集的觀測數據,經過轉換與處理後,存儲在後端系統中的模型形式。它通常用於長期分析和查詢。這個模型將指標數據組織成時間序列(Time Series),每條時間序列由唯一的標籤組合時間戳數據點構成。

它是指標數據的「靜止狀態」,也是可觀測系統的核心之一,負責儲存並提供支持多維度分析的能力。這種模型為長期查詢與性能優化提供了堅實的數據基礎,也是各家可觀測性平台(如 Prometheus 與 Grafana)的核心設計理念之一。

總結

當我們談到 Exemplar 的應用時,它帶來了 OpenTelemetry 指標世界的一大進步。過去,指標數據只能提供高度彙總的視角,例如「延遲分布」或「錯誤總數」,但它卻無法直接讓你跳進一個具體的問題事件。而 Exemplar 解決了這個核心問題:它讓你可以從聚合數據直接鏈接到具體的 Trace 和上下文,從而更快定位問題根源。

在 Grafana 的儀表板中,Exemplar 的價值進一步放大。只要你正確配置了 Prometheus 的 Exemplar 支持,你就能在圖表中看到帶有 TraceIDSpanID 的小點點,這些點點就是 Exemplar。通過點擊它們,你可以直接進入 Trace 的詳細視圖。

這樣一來,從高延遲指標到具體的請求上下文,你只需點擊幾下即可完成,這種無縫跳轉的體驗,極大提升了性能分析和問題診斷的效率。

總結來說,Exemplar 將 OpenTelemetry 的指標數據從單純的趨勢觀察,拓展到問題追蹤的深度分析。透過它,工程師能在指標與事件間無縫切換,顯著提升系統可觀測性與效能優化能力。

有興趣能購買OpenTelemetry 入門指南:建立全面可觀測性架構(iThome鐵人賽系列書) 電子書來閱讀。

今天的範例程式碼都在[OpenTelemetry 入門指南︰建立全面可觀測性架構 Ch 6當中](https://github.com/tedmax100/OpenTelemetryEntryBeook/tree/main/ch6/exemplar)。

More from this blog

Claude Code 監控秘錄:OpenTelemetry(OTel/OTLP)實戰指南

稟告主公:此乃司馬懿進呈之兵書,詳解如何以 OpenTelemetry 陣法,令臥龍神算之一舉一動盡在掌握,知糧草消耗、察兵器效能、辨戰報異常,使主公運籌帷幄於大帳之中。 為何需要斥候情報? 司馬懿稟告主公: 臥龍神算(Claude Code)乃當世利器,然若無斥候回報,主公便如蒙眼行軍——兵器耗損幾何、糧草消費幾許、哪路斥候出了差錯,一概不知。臣以為,此乃兵家大忌。 無情報之弊,有四: 軍

Feb 19, 202610 min read162
Claude Code 監控秘錄:OpenTelemetry(OTel/OTLP)實戰指南

工程師的 Claude Code 實戰指南:從零開始到高效開發

工程師的 Claude Code 實戰指南:從零開始到高效開發 本文整合 Anthropic 官方 Best Practices 與社群實戰 Tips,帶你由淺入深掌握 Claude Code。 什麼是 Claude Code?為什麼值得學? 如果你還在用「複製程式碼貼到 ChatGPT,再複製答案貼回去」的工作流程,Claude Code 會讓你大開眼界。 Claude Code 是 Anthropic 推出的命令列工具,它直接活在你的 terminal 裡,能夠讀懂你的整個 codeb...

Feb 18, 20265 min read72
工程師的 Claude Code 實戰指南:從零開始到高效開發

System Design Interview Ch 12 Digital Wallet

確立問題與設計範疇 角色對話內容 面試者我們應該只關注兩個數位錢包之間的餘額轉帳操作嗎?我們是否需要擔心其他功能? 面試官讓我們只關注餘額轉帳操作。 面試者該系統需要支援多少 TPS(每秒交易次數)? 面試官讓我們假設是 1,000,000 TPS (每秒 100 萬次交易)。 面試者數位錢包對正確性有嚴格的要求。我們可以假設事務保證 就足夠了嗎? 面試官聽起來不錯。 面試者我們需要證明正確性嗎? 面試官這是一個很好的問題。正確性(Correctness)通常只有在交...

Feb 2, 202610 min read190
System Design Interview Ch 12 Digital Wallet

Claude Code 利用 Event-Driven Hooks 打造自動化開發大腦

在現代 AI 輔助開發中,我們不僅需要 AI 寫程式,更需要它懂規則、記性好,並且能自動處理那些繁瑣的雜事。透過 Claude Code Hooks 機制,我們可以介入 AI 的思考與執行迴圈,實現真正的「人機協作自動化」。 一、 動機與痛點:為什麼你需要介入 AI 的生命週期? 在預設狀態下,Claude Code 雖然強大,但它是「被動」且「無狀態」的,這導致了開發者常遇到以下痛點: 記憶重置 (Session Amnesia): 痛點:每次重啟終端機,AI 就像失憶一樣。 解法:你...

Jan 24, 20266 min read441
Claude Code 利用 Event-Driven Hooks 打造自動化開發大腦
M

MicroFIRE

71 posts