Back to Qwen Code

可观测性与调试

docs/developers/daemon/19-observability.md

0.18.112.3 KB
Original Source

可观测性与调试

概览

qwen serve 当下带 OpenTelemetry span instrumentation结构化文件日志DaemonLogger)、per-request access-log、debug stderr 日志、结构化 preflight cell、内存权限审计环。本文是一份针对当前 surface 的实用指南,外加排查时应当意识到的现状缺口。

当下有什么

Surface位置用途
QWEN_SERVE_DEBUG stderr 日志bridge.ts 及调用点env 设 1 / true / on / yes(不区分大小写),stderr 出现 qwen serve debug: ...
OpenTelemetry span instrumentationserver.ts daemonTelemetryMiddleware每个 HTTP 请求包在 withDaemonRequestSpan 中;属性含 route、sessionId、clientId、status code。权限路由有独立 span。prompt lifecycle 全程 tracing。配置见 settings.jsontelemetry
DaemonLogger 结构化文件日志serve/daemonLogger.ts结构化 JSON-like 日志行写入文件(启动时打印路径 daemon log -> <path>);支持 info/warn/error 级别,上下文含 routesessionIdclientIdchildPidchannelId 等结构化字段
per-request access-log middlewareserver.tsbearerAuth 之前注册)每请求完成时记录 methodpathstatusdurationMssessionIdclientId(跳过 GET /health 和 heartbeat)。4xx+ 用 warn 级,成功用 info
/healthserver.ts 路由Liveness 探针;?deep=1 返回扩展信息
/capabilitiesserver.ts 路由pre-flight feature(见 11-capabilities-versioning.md
/workspace/preflight路由 → DaemonStatusProvider结构化 readiness cell(Node 版本、CLI 入口、ripgrep、git、npm,子进程活着后多出 ACP 级 cell)
/workspace/env路由 → DaemonStatusProviderdaemon 进程 env 快照(机密 env 只报存在性、剥去 proxy URL 凭证)
/workspace/mcp路由 → bridge extMethod池 / 预算 / 拒绝快照
/workspace/skills/workspace/providers路由ACP 侧实时快照(无 session 时返回空 idle)
per-session SSEGET /session/:id/events实时事件流
/demo 调试控制台GET /demopackages/cli/src/serve/demo.ts浏览器可访问的单页控制台(聊天 + 事件日志 + workspace 检视 + 权限 UX)。loopback 上 http://127.0.0.1:4170/demo 直接开 —— 不写 SDK 就能端到端把 daemon 跑起来的最快方式。loopback-vs-auth 注册规则见 02-serve-runtime.md
PermissionAuditRingpermissionAudit.ts内存 FIFO(512 条)权限决策
mediator 的 decisionReason 审计permissionMediator.ts内部结构化「为什么这样裁决」记录

当下没有什么

  • 没有 Prometheus / metrics 端点。没有 process_cpu_seconds_totalhttp_requests_totalevent_bus_queue_depth 等。
  • PermissionAuditRing 无外部 audit sink 接线 —— 环存在,但向 SIEM / 外部存储扇出的钩子还没。

调试套路

1. daemon 还活着吗?

bash
curl -s http://127.0.0.1:4170/health
# {"status":"ok"}

curl -s 'http://127.0.0.1:4170/health?deep=1' | jq
# {"status":"ok","workspaceCwd":"/path","sessions":N,...}

loopback 上 401 → 看 --require-auth 是否开(或 QWEN_SERVE_DEBUG=1 看启动日志)。

2. daemon 广播了哪些 feature?

bash
curl -s http://127.0.0.1:4170/capabilities | jq

看:mcp_workspace_pool(F2 开?)、require_auth(加固?)、permission_mediation.modes(支持哪些策略?)、policy.permission(激活哪一条?)。

3. daemon-host readiness 如何?

bash
curl -s http://127.0.0.1:4170/workspace/preflight | jq

status: 'not_started' 是 ACP 级;首次 session attach 后才填。status: 'fail' 带封闭 errorKind(见 18-error-taxonomy.md),渲染结构化修复。

4. 终端里 tail 一个 session 的 SSE

bash
curl -N -H 'Accept: text/event-stream' \
     -H 'Authorization: Bearer XYZ' \
     -H 'X-Qwen-Client-Id: debug-tail' \
     -H 'Last-Event-ID: 0' \
     'http://127.0.0.1:4170/session/<sid>/events'

-N 关 curl 输出 buffer。Last-Event-ID: 0 请求重放 ring 内 id > 0 的事件。

5. 这次权限为什么这么 resolve?

PermissionAuditRing 是内存的;今天没 HTTP surface 暴露。开 QWEN_SERVE_DEBUG=1 重跑;mediator 每次投票 / 裁决在 stderr 出结构化行,带 decisionReason.type。后续 PR 会通过 HTTP 路由暴露 ring。

6. 慢消费者在哪?

slow_client_warning 每个 overflow episode 在队列 75% 满时发一次。订阅 session SSE 看合成帧;payload 带 queueSizemaxQueuedlastEventId。重复警告 = 一个粘住的慢消费者;查 SDK 消费方的 for await 循环。

7. 为什么某 MCP server 被拒?

/workspace/mcp 快照的 per-cell disabledReason: 'budget' + refusedServerNames 列表 + mcp_child_refused_batch SSE 事件合起来告诉你这一 pass 拒了什么。对照 /capabilitiesmcp_guardrails.modesenforce 是否激活?)与 live --mcp-client-budget(在 getReservedSlots() 可见)。

8. daemon 关不掉

第一信号触发优雅退出(见 02-serve-runtime.md)。卡过 10s 时看:

  • 卡住的 ACP 子进程不响应 graceful close。
  • 长 SSE 把 HTTP server.close() 挂过 SHUTDOWN_FORCE_CLOSE_MS(5s)。

第二个 SIGTERM/SIGINT 触发 bridge.killAllSync() + process.exit(1),刻意用。

流程

典型 triage 流

mermaid
flowchart TD
    A[User reports issue] --> B{daemon alive?}
    B -->|no| BD[check process; check boot logs]
    B -->|yes| C{capabilities match expectations?}
    C -->|no| CD["check --require-auth, QWEN_SERVE_NO_MCP_POOL, settings.json"]
    C -->|yes| D{preflight all green?}
    D -->|no| DD["fix the errorKind cell"]
    D -->|yes| E{issue is session-specific?}
    E -->|yes| ES["tail SSE for that session;
QWEN_SERVE_DEBUG=1 + reproduce"]
    E -->|no| EW["check /workspace/mcp,
/workspace/env"]

状态与生命周期

  • QWEN_SERVE_DEBUG 每次检查时读(isServeDebugMode(),从 debugMode.ts 导出),切换不需重启 —— 但 daemon 已启动后启动日志就没了,除非启动时就配上。
  • PermissionAuditRing 有界(512 条,FIFO),老记录静默丢。
  • DaemonStatusProvider 每请求重建 cell(无缓存),preflight 不便宜,别没必要狂轮询。

依赖

  • process.stderr.write(debug stderr)。
  • DaemonLogger(结构化文件日志)。
  • OpenTelemetry SDK(initializeTelemetrycreateDaemonBridgeTelemetry)。
  • node:process 看 env / 信号。

配置

旋钮效果
QWEN_SERVE_DEBUG开 stderr 详细(见 17-configuration.md
settings.json telemetry控制 OTel 行为:enabledotlpEndpointotlpProtocol、per-signal endpoint 等
DaemonLogger 日志路径boot 时自动生成,打印到 stderr daemon log -> <path>
PermissionAuditRing size硬编码 512,当下不可配
slow_client_warning 阈值0.75 / 0.375 硬编码在 eventBus.ts

注意 & 已知局限

  • DaemonLogger 文件日志是结构化的,可按 route/sessionId/clientId 过滤。QWEN_SERVE_DEBUG stderr 日志仍是非结构化纯文本。
  • OpenTelemetry span 已包含 per-request 关联。每个 HTTP 请求的 span 属性带 route、sessionId、clientId,可通过 trace backend 关联。
  • /workspace/preflight 的 ACP 级 cell 需要 session 活着。idle daemon 上 auth / MCP / skills / providers 都 status: 'not_started',是预期不是失败。
  • /workspace/env 对机密只报存在不报值;响应不要扔到对不可信受众暴露存在性也敏感的位置。
  • 审计环是进程局部,daemon 重启历史丢。
  • 没有压测套路。性能 baseline 在 test/perf-daemon-baseline 分支;本文不是合适的地方。

参考

  • packages/cli/src/serve/daemonStatusProvider.ts
  • packages/cli/src/serve/daemonLogger.tsDaemonLoggerbuildDaemonLogLine
  • packages/cli/src/serve/debugMode.tsisServeDebugMode
  • packages/acp-bridge/src/permissionMediator.tsPermissionDecisionReason
  • packages/cli/src/serve/server.tsdaemonTelemetryMiddleware、access-log middleware)
  • 配置:17-configuration.md
  • 错误分类:18-error-taxonomy.md
  • 用户运维指南:../../users/qwen-serve.md