查詢是監控資料的消費介面。Collector 提供兩種查詢方式:CLI 直接操作 JSONL 檔案(grep + jq),和 HTTP 查詢 endpoint。兩種方式服務不同的消費者 — CLI 給開發者即時探索,HTTP endpoint 給自動化工具和非 CLI 使用者。

CLI 查詢:grep + jq

JSONL 格式的最大優勢是原生支援 Unix 文字處理工具。不需要額外的查詢語言、不需要客戶端工具、不需要連線到 database。

常見查詢模式

按事件類型過濾:

1grep '"type":"error"' events-2026-06-19.jsonl | jq .

按 namespace 過濾:

1grep '"name":"terminal.connect' events-2026-06-19.jsonl | jq .

按時間範圍過濾(跨檔案):

1cat events-2026-06-1{8,9}.jsonl | jq 'select(.ts >= "2026-06-18T18:00:00")'

統計每種事件的數量:

1jq -r '.name' events-2026-06-19.jsonl | sort | uniq -c | sort -rn

grep 友好的 JSONL 設計

JSONL 的每行 JSON 結構影響 grep 的查詢效率和準確性。

把常用過濾欄位放在 JSON 的前面。grep 是字串匹配,把 typename 放在行首讓 grep pattern 更簡單、誤匹配更少。

避免 JSON 值中包含雙引號。事件名稱和型別用簡單字串(不含特殊字元),讓 grep 的 pattern 不需要處理 escape。

每行 JSON 不換行。JSONL 的定義就是每行一個 JSON,但格式化工具可能自動加換行。寫入時用 json.Marshal(Go)或 JSON.stringify(JS)確保單行輸出。

HTTP 查詢 endpoint

HTTP 查詢 endpoint 讓非 CLI 使用者(dashboard、自動化腳本、其他服務)能查詢事件資料。

Endpoint 設計

1GET /v1/events?type=error&name=terminal.connect.*&from=2026-06-18T00:00:00Z&to=2026-06-19T00:00:00Z&limit=100

查詢參數:

參數說明預設值
type事件類型(event/error/metric/lifecycle)全部
name事件名稱(支援 * 萬用字元)全部
from起始時間(ISO 8601)24 小時前
to結束時間(ISO 8601)現在
limit回傳筆數上限100
offset分頁偏移0

回應格式

 1{
 2  "events": [
 3    {
 4      "v": 1,
 5      "type": "error",
 6      "timestamp": "2026-06-19T08:42:00Z",
 7      "source": { "sdk": "python", "platform": "macos", "app": "claude-hooks" },
 8      "name": "hook.failure",
 9      "level": "error",
10      "data": { "hook": "branch-status-reminder", "step": "validation" },
11      "error": { "message": "KeyError: 'status'", "stack": "Traceback...", "type": "KeyError" },
12      "context": { "session_id": "sess-abc-123" }
13    }
14  ],
15  "total": 42,
16  "limit": 100,
17  "offset": 0
18}

events 陣列按 timestamp 降序排列。total 是符合篩選條件的全量筆數(不受 limit 截斷),讓呼叫端計算分頁(total_pages = ceil(total / limit))。分頁用 offset-based(offset=100 取第二頁),適合資料量在十萬筆以下的場景。資料量大到 offset 效能不足時,改用 cursor-based(after=<last_event_id>),但 cursor-based 是 PostgreSQL 層的演進,SQLite 層用 offset 足夠。

實作策略

HTTP 查詢 endpoint 的底層實作可以直接讀取 JSONL 檔案 — 根據 from/to 確定要讀哪些日期的檔案,逐行 parse 並過濾。這個實作在資料量小(單日萬筆以下)時足夠快。

當查詢效能成為問題時,在 JSONL 之上加一層索引(按 type/name 建立反向索引),或演進到 SQLite 儲存(見 規模演進)。

聚合查詢

逐筆查詢回答「發生了什麼」,聚合查詢回答「發生了多少」。Error 調查的第一步是定位最頻繁的 error — 「哪些 error 最多」需要按 name 分群計數的聚合結果,逐筆列表在這個階段資訊量太大。

Endpoint 設計

1GET /v1/events/summary?type=error&from=2026-06-18T00:00:00Z&to=2026-06-19T00:00:00Z&group_by=name

回傳按 name 分群的統計:

1{
2  "groups": [
3    { "name": "hook.failure", "count": 15, "last_seen": "2026-06-19T08:42:00Z" },
4    { "name": "terminal.connect.failed", "count": 3, "last_seen": "2026-06-19T07:10:00Z" }
5  ],
6  "total": 18,
7  "from": "2026-06-18T00:00:00Z",
8  "to": "2026-06-19T00:00:00Z"
9}

查詢參數和逐筆查詢共用(type、name、from、to),額外的 group_by 指定分群欄位(name 或 type)。

SQL 實作

SQLite backend 下直接用 GROUP BY:

1SELECT name, COUNT(*) as count, MAX(timestamp) as last_seen
2FROM events
3WHERE type = 'error' AND timestamp BETWEEN ? AND ?
4GROUP BY name
5ORDER BY count DESC
6LIMIT 100

有 type + timestamp 複合索引時,這個查詢在 10 萬筆資料內的效能和逐筆查詢相當 — GROUP BY 在索引掃描後做,不需要全表掃描。

和逐筆查詢的定位差異

面向逐筆查詢 /v1/events聚合查詢 /v1/events/summary
回答發生了什麼(事件列表)發生了多少(統計摘要)
用途看單筆 error 的 stack trace找出最頻繁的 error
回傳事件陣列(含完整 JSON)分群摘要(name + count + last_seen)
資料量大(完整事件 body)小(只有統計值)
典型工作流聚合查詢找到問題 name → 逐筆查詢看細節首先使用

兩者是互補的工作流 — 聚合查詢定位問題方向,逐筆查詢深入細節。Dashboard 的 Error 列表頁面直接消費聚合查詢的結果。

CLI vs HTTP 的定位

面向CLI (grep + jq)HTTP endpoint
使用者開發者自動化工具、dashboard
適合即時探索、ad-hoc 查詢結構化查詢、程式化存取
優勢零安裝、可組合遠端存取、標準化
限制需要 SSH 存取 server需要 collector 啟動

兩種介面共存 — CLI 用於開發者日常 debug,HTTP endpoint 用於自動化和遠端存取。兩者底層讀取同一份 JSONL 檔案,結果一致。

下一步路由