Chuyển đến nội dung
Dashboard, alert và runbook debug

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 theo http.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.events theo action và theo outcome, 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 theo outcome, người gửi lạ, lời gọi Bot API theo method và status, độ trễ Bot API, và hango.telegram.edit_lag p50/p95. Claude Code CLI: số process đang chạy, exit theo outcome, bridge tool call theo tool và 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 tracesToLogsV2 với datasourceUid, 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ích trace_id thà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;
    • /readyz thấ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_calls với status khác ok vượt tỉ trọng ngưỡng trong 10 phút, hoặc không có lời gọi getUpdates nào thành công trong 5 phút (bot bị điếc);
    • Claude Code CLI lỗi: hango.claudecli.exits với outcome = error hoặc session_missing vượt ngưỡng trong 30 phút, hoặc hango.claudecli.stream_parse_errors khác 0 (một bản nâng cấp CLI đã đổi định dạng stream);
    • hango.claudecli.unauthorized khá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 placeholder

Instrumentation

  • Phase này không thêm span mới; nó thêm các resource attribute hango.build.version và deployment.environment.name làm biến dashboard, và một gauge tổng hợp hango.telemetry.selftest phá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-verify gử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át hango.telemetry.selftest lúc khởi động kèm các attribute version và environment.
  • internal/telemetry/dashboards_test.go — đọc danh sách hango.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 trong metrics_names.go và 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 — mount deploy/grafana/dashboards và deploy/grafana/provisioning và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ến GF_* 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ỏ sang docs/observability.md), docs/learning/README.md.

Các bước triển khai

  1. 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.
  2. Viết internal/telemetry/selftest.go phát hango.telemetry.selftest = 1 lúc khởi động với service.version và deployment.environment.name.
  3. Soạn deploy/grafana/provisioning/datasources.yaml: Prometheus, Tempo với jsonData.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ích trace_id và 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.
  4. 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, model và environment; mọi panel độ trễ dùng quantile của histogram, mọi panel lỗi dùng tỉ số rate.
  5. Soạn deploy/grafana/provisioning/alerting.yaml vớ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.
  6. Mount mọi thứ trong compose.yaml và thêm các target Makefile.
  7. Viết internal/telemetry/dashboards_test.go: đọc danh sách hango.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 trong metrics_names.go hoặc trong tập label đã khai báo. Đơn giản, xác định, và không parse PromQL.
  8. 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ả qua HANGO_TELEGRAM_API_URL và 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ì.
  9. 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ỏ).
  10. Viết docs/learning/14-debugging-a-failed-run.md là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.
  11. 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:
    1. GET /v1/runs/{id} → đọc state, error, trace_id, iterations, usage.
    2. 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à chat hay execute_tool.
    3. 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.
    4. Tempo → Loki qua tracesToLogsV2 → các dòng log của span đó, lọc theo hango.run.id.
    5. 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.
    6. Replay GET /v1/runs/{id}/events → client thực sự đã thấy gì.
    7. 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.
    8. `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ì đó".
    9. 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.update theo outcome, rồi span link vào run); một run Claude CLI lỗi sau khi nâng cấp (outcome exit của claudecli.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, được dashboards_test.go bắ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 up provision 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-verify qua: trace của run tổng hợp query được bằng trace_id từ 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-api hiển thị hango.audit.events tá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òng audit_events và trace của nó.
  • docs/observability.md tài liệu hóa mọi attribute và metric hango.* do các phase 1-13, 18 và 19 phát ra, đã kiểm chứng với metrics_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 roKhả năng x Tác độngTín hiệuCách xử lý đã định sẵn
Dashboard tham chiếu các metric không tồn tạiCao x Trung bìnhPanel trốngmetrics_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 hangoTrung bình x Trung bìnhData 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ỏ quaCao x Trung bìnhAlert kêu liên tục trong devCá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àyTrung bình x Trung bìnhThiếu nút trace-to-logsCá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 đổiTrung bình x Trung bìnhCác bước tham chiếu attribute đã bị xóaRunbook đượ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 productionTrung bình x CaoProduction trỏ vào container lgtmdocs/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.