Dashboard, alert và runbook debug
Tổng quan
Các phase 1-13, 18 và 19 phát ra telemetry; phase này làm cho nó dùng được. Provision các dashboard của hango vào stack Grafana LGTM, nối tương quan Tempo↔Loki↔Prometheus để một cú click chuyển được giữa một trace, các log và các metric của nó, thêm các alert rule cho những kiểu lỗi mà các phase trước đã gọi tên, chuẩn hóa quy ước tag và metadata của Langfuse, và viết runbook từ đầu đến cuối đưa một run id bị lỗi về đúng span, dòng log và tool call đã gây ra lỗi. Không có tính năng server mới: phase này biến “đã có instrumentation” thành “vận hành được”.
Yêu cầu
Chức năng
- Các dashboard Grafana được provision sẵn (JSON nằm trong repo, mount vào lgtm):
hango-runs: run theo state và tenant, độ sâu hàng đợi theo từng lane, tuổi của run queued cũ nhất, số lần claim thắng/thua và số run bị reclaim, thời lượng run p50/p95/p99, histogram số iteration, số lần cancel, các sự kiện drain.hango-llm: token usage theo model/type/purpose, chi phí theo tenant/model/purpose, độ trễ lời gọi model p50/p95, time-to-first-chunk, tỉ lệ cache-read, tỉ lệ lỗi và retry của provider, tỉ lệ compaction.hango-tools: lượng tool call, thời lượng theo từng tool, tỉ lệ lỗi theo từng tool, số lần chặn gọi lặp, số lần offload kết quả, các thao tác filesystem.hango-api: tốc độ request, độ trễ và status theohttp.route(label được thêm ở phase 1), lỗi auth theo reason gồm cả các lần từ chối trước xác thực, các lần từ chối do rate limit và quota, số subscriber SSE, số topic của broadcaster và số subscriber bị lag, cộng một nhóm panel Audit:hango.audit.eventstheoactionvà theooutcome, tỉ lệ bị từ chối (outcome="denied"trên tổng số sự kiện), vàhango.audit.write_failures.hango-channels: hai đường vào/ra không đi qua HTTP API của hango. Telegram: update theooutcome, người gửi lạ, lời gọi Bot API theomethodvàstatus, độ trễ Bot API, vàhango.telegram.edit_lagp50/p95. Claude Code CLI: số process đang chạy, exit theooutcome, bridge tool call theotoolvàstatus, các request bridge không được phép, và lỗi parse stream.hango-runtime: metric runtime của Go (goroutine, heap, GC pause, thread) cộng mức bão hòa của pgx pool.
- Data source Tempo cấu hình
tracesToLogsV2vớidatasourceUid,tags(hango.tenant.id,hango.run.id),filterByTraceID: true, và các độ lệch thời gian của span; derived field của Loki tríchtrace_idthành một link sang Tempo. - Các alert rule (Grafana unified alerting, provision bằng YAML):
- tỉ lệ run thất bại vượt ngưỡng trong 15 phút;
- đột biến 429/5xx từ provider;
- độ sâu hàng đợi vượt ngưỡng trong 5 phút;
- lỗi ghi usage-record khác 0;
- sự kiện tính giá model không biết khác 0;
- đột biến subscriber SSE bị lag;
/readyzthất bại hoặc drain vượt timeout;- tỉ lệ lỗi xác thực vượt ngưỡng trong 5 phút, tách theo `reason`, với các lần từ chối trước xác thực là một series riêng (tín hiệu brute-force);
- `hango.run.oldest_queued_age` vượt ngưỡng trong 5 phút (run bị kẹt ở `queued`);
- `hango.run_events.write_failures` khác 0;
- đột biến tỉ lệ audit bị từ chối: `hango.audit.events{outcome="denied"}` chiếm tỉ trọng trên tổng số audit event vượt ngưỡng trong 15 phút, đó là tín hiệu có người đang dò API bằng các credential không còn mang quyền truy cập như trước, và `hango.audit.write_failures` khác 0 (một lỗ hổng audit tự nó là một sự cố);
- Telegram Bot API lỗi:
hango.telegram.api_callsvớistatuskhácokvượt tỉ trọng ngưỡng trong 10 phút, hoặc không có lời gọigetUpdatesnào thành công trong 5 phút (bot bị điếc); - Claude Code CLI lỗi:
hango.claudecli.exitsvớioutcome=errorhoặcsession_missingvượt ngưỡng trong 30 phút, hoặchango.claudecli.stream_parse_errorskhác 0 (một bản nâng cấp CLI đã đổi định dạng stream); hango.claudecli.unauthorizedkhác 0 (có thứ gì đó không phải process CLI của owner đã gọi tool bridge).
- Tài liệu quy ước Langfuse: dùng những tag nào, những key
langfuse.trace.metadata.*nào (tenant_id, agent_id, run_id, lane, subagent depth), cách tìm một run, cách so sánh các phiên bản prompt, và những gì cố ý không gửi (nội dung prompt trừ khi được bật). docs/learning/14-debugging-a-failed-run.md: runbook, viết như một bản walkthrough theo đúng nghĩa đen của một run thất bại thật.
Phi chức năng
- Dashboard được provision từ file, không dựng tay trên UI, để review được và tái lập được.
- Mọi query của panel chỉ dùng các metric và label key mà các phase trước thực sự phát ra. Việc kiểm chứng là một **phép kiểm tra label-key đơn giản**, không phải trích xuất PromQL bằng regex: mỗi JSON dashboard mang một mảng `hango.metrics` liệt kê các hằng số metric nó dùng, và test khẳng định mọi mục đều tồn tại trong `internal/telemetry/metrics_names.go` và mọi label key được tham chiếu trong legend hoặc biến của một panel đều là key mà phase phát ra metric đó có khai báo. Parse PromQL tùy ý bằng regex không phải một hợp đồng đáng để duy trì.
- `metrics_names.go` đã có từ phase 1 và mọi phase, kể cả 18 và 19, đều đăng ký qua nó, nên phase này **không có đợt refactor vá tên metric** nào.
- Ngưỡng alert là các giá trị mặc định được ghi trong config và tài liệu, không phải những phỏng đoán chưa chỉnh được trình bày như sự thật.
Kiến trúc
Không có Go interface và không có bảng. Sản phẩm bàn giao là các artifact provisioning cộng một test kiểm chứng.
deploy/grafana/
dashboards/hango-runs.json
dashboards/hango-llm.json
dashboards/hango-tools.json
dashboards/hango-api.json
dashboards/hango-channels.json
dashboards/hango-runtime.json
provisioning/dashboards.yaml # file provider pointing at /var/lib/grafana/dashboards
provisioning/datasources.yaml # Tempo (tracesToLogsV2) + Loki (derived field) + Prometheus
provisioning/alerting.yaml # alert rules and a contact point placeholderInstrumentation
- Phase này không thêm span mới; nó thêm các resource attribute
hango.build.versionvàdeployment.environment.namelàm biến dashboard, và một gauge tổng hợphango.telemetry.selftestphát một lần lúc khởi động để dashboard chứng minh được pipeline còn sống ngay cả khi không có traffic. - Target
make obs-verifygửi một run tổng hợp qua hệ thống và khẳng định series chính của từng dashboard có mặt trong Prometheus và trace query được trong Tempo.
Các file liên quan
Tạo mới
deploy/grafana/dashboards/*.json(sáu dashboard).deploy/grafana/provisioning/dashboards.yaml,datasources.yaml,alerting.yaml.internal/telemetry/selftest.go— pháthango.telemetry.selftestlúc khởi động kèm các attribute version và environment.internal/telemetry/dashboards_test.go— đọc danh sáchhango.metricsđã khai báo của từng dashboard cùng các label key của panel và khẳng định chúng tồn tại trongmetrics_names.govà trong các tập label đã khai báo.docs/observability.md— data source, dashboard, alert, quy ước Langfuse, cái gì được và không được gửi.docs/learning/14-debugging-a-failed-run.md.
Sửa
compose.yaml— mountdeploy/grafana/dashboardsvàdeploy/grafana/provisioningvào container lgtm (các điểm mount đã được ghi lại là/otel-lgtm/...và đường dẫn provisioning của Grafana), và thêm các biếnGF_*cần cho việc provisioning.Makefile—obs-up,obs-verify,obs-reset.internal/telemetry/telemetry.go— gọi bộ phát self-test.- Không refactor việc đăng ký metric: các phase 1-13, 18 và 19 đã đăng ký qua `metrics_names.go`. Phase này chỉ thêm hằng số nào mà dashboard cần mà không phase nào phát ra, đó là một lỗi của phase kia, không phải một đợt refactor ở đây.
README.md,docs/system-architecture.md(mục Observability trở thành một con trỏ sangdocs/observability.md),docs/learning/README.md.
Các bước triển khai
- Rà soát `internal/telemetry/metrics_names.go` (được xây dần từ phase 1) đối chiếu với những gì các phase 1-13 thực sự phát ra, cùng với các phase 18 và 19, và sửa phase nào đã phát ra một literal. Đây là hợp đồng mà các dashboard được xây dựa vào; ở đây nó được kiểm chứng chứ không được tạo ra.
- Viết
internal/telemetry/selftest.gopháthango.telemetry.selftest = 1lúc khởi động vớiservice.versionvàdeployment.environment.name. - Soạn
deploy/grafana/provisioning/datasources.yaml: Prometheus, Tempo vớijsonData.tracesToLogsV2(datasourceUid: loki,tags: [{key: "hango.tenant.id"}, {key: "hango.run.id"}],filterByTraceID: true,spanStartTimeShift: -5m,spanEndTimeShift: 5m), và Loki với một derived field tríchtrace_idvà liên kết tới Tempo. Ghi chú trong comment rằng lgtm tự provision các data source của nó, nên các file này ghi đè chúng theo uid. - Soạn sáu dashboard. Dựng mỗi panel từ các hằng số metric; thêm các biến template cho
tenant_id,agent_id,modelvàenvironment; mọi panel độ trễ dùng quantile của histogram, mọi panel lỗi dùng tỉ số rate. - Soạn
deploy/grafana/provisioning/alerting.yamlvới mười lăm rule, mỗi rule mang một summary nêu nguyên nhân có thể và link tới mục runbook liên quan. Các phase 15 và 16 mỗi phase tự thêm rule của mình vào file này như một bước tường minh (gauge approvals-pending; các lần RLS từ chối), để bộ alert luôn theo kịp hệ thống. - Mount mọi thứ trong
compose.yamlvà thêm các targetMakefile. - Viết
internal/telemetry/dashboards_test.go: đọc danh sáchhango.metricsđã khai báo của từng dashboard cùng các label key của panel và fail nếu bất kỳ cái nào vắng trongmetrics_names.gohoặc trong tập label đã khai báo. Đơn giản, xác định, và không parse PromQL. - Viết
make obs-verify: khởi động stack, tạo tenant/user/key/agent, chạy một run theo kịch bản có một tool call, một compaction và một subagent, trỏ Telegram bot vào một server Bot API giả quaHANGO_TELEGRAM_API_URLvà chuyển một update từ một user đã được ánh xạ, rồi query Prometheus các series chính và Tempo cho trace id trả về từGET /v1/runs/{id}, thất bại nếu thiếu bất kỳ thứ gì. - Viết
docs/observability.md: sơ đồ luồng dữ liệu (app → collector → lgtm/Langfuse), từ điển attribute (hango.*, GenAI,langfuse.*), danh mục dashboard, danh mục alert với ngưỡng mặc định, quy ước tag và metadata của Langfuse, và chính sách riêng tư nội dung (HANGO_TRACE_LLM_CONTENT, collector loại bỏ). - Viết
docs/learning/14-debugging-a-failed-run.mdlàm runbook (dàn ý bên dưới), dùng một run thất bại thật được tạo ra có chủ đích (một tool lỗi bên trong subagent sau một compaction) và ghi lại đúng các query và cú click. - Chạy toàn bộ gate cộng
make obs-verify.
Dàn ý tài liệu học
docs/learning/14-debugging-a-failed-run.md phải có:
- Khái niệm: observability như một hợp đồng debug — hệ thống phải trả lời được những câu hỏi nào, và chuỗi attribute tenant → agent → session → run → turn → model call → tool call trả lời chúng.
- Runbook, dưới dạng một walkthrough có đánh số xuất phát từ một run id bị lỗi:
GET /v1/runs/{id}→ đọcstate,error,trace_id,iterations, usage.- Tempo: dán trace id → đọc gốc
invoke_agent, tìm span đầu tiên có trạng thái lỗi, ghi nhận nó làchathayexecute_tool. - Attribute của span → model chính xác, finish reason, tên tool, tool call id, và turn đó có bị compact hay không.
- Tempo → Loki qua
tracesToLogsV2→ các dòng log của span đó, lọc theohango.run.id. - Langfuse: mở trace theo
metadata.run_id→ xem input/output của generation (khi bật content tracing) và các observation subagent lồng nhau → xác định prompt đã sinh ra tool call sai. - Replay
GET /v1/runs/{id}/events→ client thực sự đã thấy gì. read_file /large_tool_results/{tool_call_id}và/conversation_history/{session_id}.md→ các payload mà model đã thấy nhưng trace không mang theo.- `GET /v1/audit-events?since=` cho tenant của run quanh thời điểm lỗi, nối theo `trace_id` — ai đã đổi agent, quota, một file hay một approval ngay trước khi run hỏng, và có gì bị từ chối hay không. Đây là bước phân biệt "hệ thống lỗi" với "có người đã đổi thứ gì đó".
- Metric: đây là một run đơn lẻ hay một khuôn mẫu (tỉ lệ lỗi theo tool, theo model, theo tenant)?
- Bảy dấu hiệu lỗi đã làm mẫu, kèm query đầu tiên cho từng cái: bão rate limit của provider; context tràn sau một compaction thất bại; tool timeout bên trong một subagent; 404 chéo tenant do sai API key; client SSE không thấy gì vì subscriber bị lag; một tin nhắn Telegram của người nhà không có phản hồi (bắt đầu từ
telegram.updatetheooutcome, rồi span link vào run); một run Claude CLI lỗi sau khi nâng cấp (outcome exit củaclaudecli.process, rồi stderr đã ghi lại trong Loki). - Cách debug chính tầng này: không có dữ liệu trong Grafana (log của collector, endpoint,
HANGO_OTEL_ENABLED), có dữ liệu trong Tempo nhưng không có trong Langfuse (basic auth, header ingestion-version, hàng đợi của exporter Langfuse trong collector), có metric nhưng dashboard trống (metric-name drift, đượcdashboards_test.gobắt). - So sánh với goclaw: lưu span trong DB với flush định kỳ và một OTLP exporter được chặn bằng build tag, tức là một hệ thống tracing tự chế không có dashboard hay tương quan; đánh đổi là vận hành khép kín so với một hệ sinh thái công cụ.
- So sánh với deepagents: observability đến từ LangSmith, cho sẵn cây trace và việc xem prompt nhưng không có metric hạ tầng — nhánh Langfuse trong pipeline của hango là bản tương đương có chủ ý, còn Grafana lo phần LangSmith không làm.
- Vì sao hango đi khác: OTel chuẩn với collector fan-out, để việc debug ở mức LLM và mức hạ tầng mỗi bên dùng đúng công cụ của nó.
- Bài tập: cố tình làm hỏng một tool và đi theo runbook từ đầu đến cuối; tắt collector và quan sát alert nào kêu.
- Ngân sách thời gian: 10h cài đặt + 2h tài liệu học (runbook chính là tài liệu học).
Tiêu chí thành công
-
go build ./... && go vet ./... && go test ./...đều qua, gồm cảdashboards_test.go. -
docker compose upprovision cả sáu dashboard và ba data source vào Grafana mà không cần bước thủ công nào. - Từ một trace trong Tempo, một cú click tới được các log Loki tương ứng; từ một dòng Loki, một cú click tới được trace.
-
make obs-verifyqua: trace của run tổng hợp query được bằngtrace_idtừ API, và series chính của mọi dashboard đều có trong Prometheus. - Cả mười lăm alert rule nạp không lỗi và mỗi rule có summary nêu một nguyên nhân có thể.
- Nhóm panel Audit của
hango-apihiển thịhango.audit.eventstách theo action và outcome với một run thật, và bước audit của runbook truy một thay đổi bị từ chối có chủ đích về đúng dòngaudit_eventsvà trace của nó. -
docs/observability.mdtài liệu hóa mọi attribute và metrichango.*do các phase 1-13, 18 và 19 phát ra, đã kiểm chứng vớimetrics_names.go. -
docs/learning/14-debugging-a-failed-run.mdđi qua một run thất bại thật từ đầu đến cuối với các query thực tế đã dùng. - Một run thất bại có chủ đích có thể được chẩn đoán đến đúng tool call trong chưa đầy năm phút, chỉ theo runbook (có bấm giờ, ghi lại trong tài liệu).
Đánh giá rủi ro
| Rủi ro | Khả năng x Tác động | Tín hiệu | Cách xử lý đã định sẵn |
|---|---|---|---|
| Dashboard tham chiếu các metric không tồn tại | Cao x Trung bình | Panel trống | metrics_names.go là nguồn duy nhất từ phase 1; dashboards_test.go kiểm tra danh sách metric và label key đã khai báo của từng dashboard và làm hỏng build khi có drift |
| Provisioning riêng của lgtm xung đột với của hango | Trung bình x Trung bình | Data source bị trùng hoặc bị ghi đè | Provision theo uid cố định (prometheus, tempo, loki) để file của hango ghi đè chứ không nhân đôi; ghi lại trong docs/observability.md |
| Ngưỡng alert gây nhiễu và bị bỏ qua | Cao x Trung bình | Alert kêu liên tục trong dev | Các giá trị mặc định được ghi là điểm khởi đầu kèm quy trình tinh chỉnh; alert theo từng môi trường và mặc định tắt trong dev |
| Cấu hình tương quan chưa được kiểm chứng với phiên bản lgtm này | Trung bình x Trung bình | Thiếu nút trace-to-logs | Các key tracesToLogsV2 lấy từ tài liệu data source Tempo; phép kiểm tra một cú click là một tiêu chí nghiệm thu, và phương án dự phòng là một query Loki thủ công theo trace_id đã được ghi lại |
| Runbook cũ dần khi các phase thay đổi | Trung bình x Trung bình | Các bước tham chiếu attribute đã bị xóa | Runbook được kiểm chứng bằng make obs-verify và được đọc lại trong lượt rà soát tài liệu cuối của phase 16 |
| lgtm chỉ dành cho dev nhưng lại lên production | Trung bình x Cao | Production trỏ vào container lgtm | docs/deployment-guide.md (phase 16) bắt buộc dùng backend được quản lý; docs/observability.md nêu giới hạn này ngay đầu tài liệu |
Rollback
Chỉ là các artifact. Gỡ các mount provisioning đưa Grafana về mặc định của lgtm; ứng dụng vẫn phát telemetry như cũ, nên không có gì quan sát được bị thụt lùi.