嗨大家,我是 Debuguy。
昨天我們成功讓 ChatBot 能處理 Kibana URL,把複雜的格式轉換交給程式碼處理。今天要來處理另一個更常見的場景:Grafana 警報。
📱 凌晨 3:17,手機震動把你從夢中驚醒...
🚨 [CRITICAL] HTTP 4xx/5xx Error Rate Alert
Production API 出現大量錯誤
查看詳細: https://play.grafana.org/d/play-elastic-web-logs?orgId=1&from=893966418000&to=893967033401&viewPanel=panel-5
@debuguy @on-call-team
你掙扎著爬起來,揉揉眼睛看著這個連結,心裡開始盤算:
「又要先看 Grafana,再去 Kibana 翻 log,然後...%#@!為什麼每次都要重複這套流程?」
收到這種警報時的流程:
每個 on-call 工程師都經歷過這個循環。問題不在於技術難度,而在於:
「如果有個 AI 能在我爬起來之前就先看過一遍,該有多好?」
我想要的是這樣的體驗:
🚨 Grafana Alert 發出(同時 @bot)
↓
Bot 自動解析警報中的 Grafana URL
↓
從 URL 參數識別觸發的 Dashboard 和 Panel
↓
透過 Grafana API 取得該 Panel 的查詢設定
↓
提取資料來源和查詢條件
↓
直接去 Elasticsearch 查相關 Log
↓
生成結構化的問題分析報告
↓
值班工程師醒來就看到完整分析,決定下一步行動
核心概念:讓 AI 做初步分析,人類做最終決策。
不是要 AI 自動修 bug(那太危險),而是要它幫我們完成那些重複性的資訊收集和初步判斷。
既然昨天已經接好 Elasticsearch MCP,今天再加 Grafana MCP 應該很簡單吧?
# docker-compose.yml
services:
grafana-mcp:
image: mcp/grafana
ports:
- "8000:8000"
environment:
GRAFANA_URL: ${GRAFANA_URL}
GRAFANA_SERVICE_ACCOUNT_TOKEN: ${GRAFANA_SERVICE_ACCOUNT_TOKEN}
command: ["-t", "streamable-http"]
// GenKit/config/mcp-config.json
{
"mcpServers": {
"grafana": {
"type": "streamable-http",
"url": "http://grafana-mcp:8000/mcp"
}
}
}
「看吧,輕鬆加好了!現在只要告訴 AI 怎麼用就行了。」
我天真地寫了個 Prompt:
幫我看一下這個 Grafana 警報為什麼叫了,原因是什麼?
然後發現... AI 完全不知道該怎麼辦。
讓我仔細分析一下,為什麼 Grafana 的自動化比 Kibana 還要困難?
讓我們比較一下昨天的 Kibana URL 和今天的 Grafana URL:
Kibana URL(昨天):
https://demo.elastic.co/app/discover#/?_g=(time:(from:'2025-10-04T14:30:00.000Z',to:'2025-10-04T15:00:00.000Z'))&_a=(query:(language:kuery,query:'log.level:%20%22error%22%20'))
✅ 時間範圍用 ISO 8601(標準格式)
✅ 查詢條件直接寫在 URL 裡
✅ 所有資訊一目了然
Grafana URL(今天):
https://play.grafana.org/d/play-elastic-web-logs?orgId=1&from=893966418000&to=893967033401&viewPanel=panel-5
❌ 時間用 Unix timestamp(毫秒)
❌ 查詢條件完全沒有
❌ 只有 dashboard 和 panel 的 ID
關鍵差異:
Grafana URL 更像是一個「書籤」,它只告訴你去哪裡看,但不告訴你在看什麼。要知道查詢條件,必須:
Grafana 最大的優勢也是最大的挑戰:它可以接入任何資料源。
這意味著 AI 光看 Grafana URL,根本不知道:
當我終於讓 AI 成功從 Grafana API 取得 Panel 設定後,打開一看:
查詢語法是 Lucene。
例如 status >= 400
的查詢語法是 status:[400 TO *]
「天啊,昨天才搞定 Kibana 的 KQL,今天又來一個 Lucene...」
好消息是,冷靜下來想想:
query_string
直接接受 Lucene{
"query": {
"bool": {
"must": [
{
"query_string": {
"query": "<從_Grafana_解析出的_Lucene_查詢>"
}
}
]
}
}
}
「又是一個可以直接填空的模板!」
Grafana 用的是 Unix timestamp(毫秒),但 Elasticsearch 需要 ISO 8601 格式。
// Grafana URL 的時間參數
from=893966418000
to=893967033401
// Elasticsearch 需要的格式
"@timestamp": {
"gte": "1998-05-01T06:20:18.000Z",
"lte": "1998-05-01T06:30:33.401Z"
}
盤點一下,要讓 AI 自動分析 Grafana 警報,我們需要處理:
「如果是人類工程師,我們也是一步一步來解決這些問題的。那 AI 也需要一個清楚的 SOP。」
讓我們倒回來想,如果是我自己要查這個警報:
1. 點開 Grafana URL
↓
2. 看到觸發警報的 Panel(圖表)
↓
3. 點進 Panel 設定,查看 Query 內容
↓
4. 看到是查 Elasticsearch 的某個 index
↓
5. 把查詢條件複製到 Kibana 或 Elasticsearch
↓
6. 調整時間範圍後執行查詢
↓
7. 分析 log 找出問題
關鍵洞察:人類工程師也需要「分步驟」來拆解這個任務。
所以我設計了一個四步驟的分析流程,模擬人類工程師的思路。
在開始四步驟之前,我們需要先解決時間格式的問題。
你可能會想:
「這麼簡單的功能,應該有現成的 library 吧?」
確實有,但我的需求很單純:
幾行程式碼就能搞定的事,為什麼要多一個依賴?
// GenKit/tools/time_tools.ts
export const createUnixToIsoTool = (ai: Genkit) => {
return ai.dynamicTool({
name: 'unix_to_iso',
description: 'Convert Unix timestamp to ISO 8601 UTC format. Supports both seconds and milliseconds timestamps.',
inputSchema: z.object({
timestamp: z.string().describe('Unix timestamp as string in seconds or milliseconds')
}),
outputSchema: z.object({
iso8601: z.string().describe('The ISO 8601 UTC formatted date string'),
original: z.string().describe('The original timestamp input'),
detected_format: z.string().describe('Whether timestamp was detected as seconds or milliseconds')
})
}, async ({ timestamp }) => {
const numTimestamp = parseFloat(timestamp);
if (isNaN(numTimestamp) || numTimestamp < 0) {
throw new Error('Invalid timestamp: must be a positive number');
}
let milliseconds: number;
let detectedFormat: string;
// 自動偵測格式:大於 100000000000 的認為是毫秒
if (numTimestamp >= 100000000000) {
milliseconds = numTimestamp;
detectedFormat = 'milliseconds';
} else {
milliseconds = numTimestamp * 1000;
detectedFormat = 'seconds';
}
const date = new Date(milliseconds);
if (isNaN(date.getTime())) {
throw new Error('Invalid timestamp: results in invalid date');
}
return {
iso8601: date.toISOString(),
original: timestamp,
detected_format: detectedFormat
};
});
};
設計重點:
昨天處理 Kibana URL 時我們學到:與其讓 AI 自由發揮,不如給它明確的 SOP。
想像你在教一個新人 on-call:
❌ 不好的教法:
「你去看一下那個警報,然後查查看是什麼問題。」
✅ 好的教法:
「收到警報後,第一步先解析 URL 拿到 dashboard ID 和時間範圍,第二步去 Grafana 查這個 panel 的設定...」
AI 也一樣,需要明確的步驟指引。
目的: 從警報訊息中提取關鍵資訊
從 Grafana 警報訊息中找到 URL,提取四個關鍵參數:
- dashboardUid: 識別是哪個儀表板
- panelId: 確定觸發警報的面板
- from: 時間範圍起點(Unix timestamp)
- to: 時間範圍終點(Unix timestamp)
URL template:
https://play.grafana.org/d/<dashboardUid>?orgId=1&from=<from>&to=<to>&viewPanel=<panelId>
給 AI 看範例,它就知道怎麼解析。
目的: 找出這個 Panel 到底在查什麼
這是最複雜的一步,需要拆成三個子步驟:
a. 獲取面板設定:
- 使用 get_dashboard_property 工具
- 傳入 dashboardUid 和 jsonPath
- 取得面板的完整設定 JSON
b. 解析面板設定:
- 從 JSON 找到 targets 陣列
- 解析出 Lucene 查詢語法
- 記錄 datasource 的 UID
c. 獲取資料源資訊:
- 使用 get_datasource_by_uid 工具
- 取得 Elasticsearch 的 index 名稱
透過明確的子步驟,AI 知道該找什麼、怎麼找。
目的: 組合查詢條件,撈出相關 log
1. 使用 unix_to_iso 工具轉換時間範圍
2. 組合 Elasticsearch Query DSL:
{
"query": {
"bool": {
"must": [
{
"query_string": {
"query": "<LUCENE_QUERY_FROM_GRAFANA>"
}
},
{
"range": {
"@timestamp": {
"gte": "<ISO_8601_FROM>",
"lte": "<ISO_8601_TO>"
}
}
}
]
}
},
"size": 100
}
3. 使用 elasticsearch search 工具執行查詢
給了完整的模板,AI 只需要填空。
目的: 把發現整理成人類看得懂的報告
分析日誌並撰寫報告,包含:
- 摘要:1-2 句話概括問題
- 影響範圍:受影響的服務和用戶
- 立即建議:可執行的下一步
- 額外背景:技術細節和觀察
讓我們看看當警報進來時,整個系統是怎麼運作的:
📱 凌晨 3:17 - Grafana 警報進入 Slack(@bot)
↓
🤖 Bot 被觸發,開始執行
↓
📊 Step 1: 解析 URL
✓ dashboardUid: play-elastic-web-logs
✓ panelId: panel-5
✓ 時間範圍: 893966418000 ~ 893967033401
↓
🔍 Step 2: 追蹤 Panel 設定
✓ 調用 get_dashboard_property
✓ 解析出 Lucene 查詢: response:404
✓ 調用 get_datasource_by_uid
✓ 取得 index: logs-*
↓
🕐 轉換時間格式
✓ from: 1998-05-01T06:20:18.000Z
✓ to: 1998-05-01T06:30:33.401Z
↓
📝 Step 3: 查詢 Elasticsearch
✓ 組合 Query DSL
✓ 執行查詢
✓ 取得 100 筆相關 log
↓
📋 Step 4: 生成分析報告
✓ 分析 log 找出異常模式
✓ 評估優先級和影響範圍
✓ 提供可執行建議
↓
💬 發送報告到 Slack thread
↓
😴 工程師醒來時,分析已經完成
🚨 Grafana 警報分析
📌 摘要
在 1998-05-01 06:20-06:30 期間,Production API 出現大量 404 錯誤,
主要集中在 /api/users/legacy 端點。
🎯 影響範圍
- 受影響服務:User Profile API
- 具體影響:使用者無法訪問舊版個人資料頁面
- 受影響路徑:/api/users/legacy/*
💡 立即建議
1. 檢查最近是否有移除舊版 API 端點的部署
2. 確認前端是否還在呼叫已廢棄的端點
3. 考慮加回 301 redirect 或友善的錯誤訊息
📝 額外背景
- 錯誤集中在 06:23-06:27,可能與定時任務有關
- User-Agent 顯示主要來自 iOS app v2.3.1
- 建議更新 app 或加回相容性支援
這份報告的價值:
以前的流程:
1. 被嚇醒 😱
2. 點開 Grafana 看圖表
3. 腦霧中猜測可能原因
4. 去 Kibana 翻 log
5. 在幾千筆 log 裡找線索
6. 如果找不到...拉其他人起來
7. 30 分鐘過去了
心理狀態: 焦慮、不確定、壓力大
現在的流程:
1. 被叫醒(這個躲不掉 😅)
2. 打開 Slack 看 AI 已經準備好的分析報告
3. 基於報告評估情況
4. 做出明智的決策
5. 10 分鐘搞定
心理狀態: 有掌控感、清楚狀況、知道下一步
這不只是效率提升,更是心理壓力的減輕。
從「摸黑前進」到「有地圖導航」
以前半夜被叫起來,你不知道:
現在你不再是「從零開始找問題」,而是「驗證 AI 的分析並做決策」。
這個轉變看似微小,但對凌晨三點的值班工程師來說,意義重大。
你可能會想:「現在的 LLM 這麼強,為什麼不讓它自己想辦法?」
試過了,不行。
沒有明確指引時,AI 會:
關鍵:
AI 很聰明,但它需要知道「期望的工作流程」是什麼。
就像一個聰明的新人,你不能只說「去把這個問題修好」,而是要教他「這類問題通常怎麼查、要注意什麼」。
在這個架構設計下,AI 失誤的機會降低了,但有時還是會失誤。或是一開始給的 prompt 沒有給足資訊。
這時候 Slack Bot 有連續對話的能力就會很有幫助。可以給予 Bot 新的 context,讓它延續前面已經完成的事項,繼續做更深入的分析與查找。
這就是為什麼從 Day 5 開始我們就建立了多輪對話的能力。
從 Day 1 的「想要一個能解決問題的 Bot」到今天的「自動化警報分析系統」,我們用了 21 天的時間,真正實現了從 0 到 1 的產品創造。
Day 21 完成了:
我們創造的不只是技術,而是:
Day 21 的核心洞察:
最好的 AI 工具不是取代工程師,而是成為工程師最強的 first responder。
AI 負責:
人類負責:
完整的原始碼在這裡,包含 Grafana 整合和自動化分析流程!
AI 的發展變化很快,目前這個想法以及專案也還在實驗中。但也許透過這個過程大家可以有一些經驗和想法互相交流,歡迎大家追蹤這個系列。
也歡迎追蹤我的 Threads @debuguy.dev