Files
pikasTech-unidesk/docs/reference/observability.md
T
2026-05-20 01:41:40 +00:00

12 KiB
Raw Blame History

UniDesk Observability Reference

UniDesk 的可观测性优先级高于静默成功。CLI、服务日志、Docker 日志和数据库状态都必须能通过短命令查询。

CLI Logs

异步 job 的 stdout 和 stderr 位于 .state/jobs/job list 默认只返回最新 50 条摘要;job status 会返回有限尾部,避免输出爆炸,同时保留完整日志文件路径便于继续排查。实现必须只读取日志尾部字节,不得先把完整 job 日志读入 CLI 内存。

Service Logs

服务日志位于 logs/{YYYYMMDD}/,每次 server start 都生成新的本地时间戳前缀。新写入的 UniDesk JSONL 日志必须按小时切片:logs/{YYYYMMDD}/{startStamp}_{YYYYMMDD}_{HH}_{service}.jsonl,一天一个目录,禁止长期追加到单个巨大 JSONL。所有 UniDesk Bun 服务(frontend、provider-gateway、Code Queue、project-manager、baidu-netdisk 以及后续新增 Bun 服务)必须复用 src/components/shared/src/rotating-jsonl.ts 中的 createHourlyJsonlWriterRust backend-core 必须提供等价的 hourly rotation and retention behavior in src/components/backend-core/src/logger.rsLOG_FILE 只作为推导 logs 根目录、启动前缀和 service 后缀的 base path,不得长期追加到单个文件。database 通过 PostgreSQL logging collector 写入同一日期目录。

日志保留默认按日志族限制为 1GiB:服务写入或 Code Queue 导出日志时必须扫描同一 service 后缀的历史文件,超过上限后自动删除最旧切片;当前活跃切片不能被保留清理删除。全局上限由 UNIDESK_LOG_RETENTION_BYTES 控制,服务级上限使用 UNIDESK_<SERVICE>_LOG_MAX_BYTES(如 UNIDESK_FRONTEND_LOG_MAX_BYTESUNIDESK_PROVIDER_GATEWAY_LOG_MAX_BYTES),历史兼容变量只允许作为过渡入口。Codex app-server 的 logs_*.sqlite 仅作为 Codex 上游运行时的短暂缓冲,Code Queue 必须周期性导出为同样按小时切片的 codex-app-server JSONL,并删除/压缩已导出的 SQLite 行,避免 logs_2.sqlite 成为长期大文件。

新增或迁移服务的长期规范:Dockerfile 必须把 src/components/shared 复制到与仓库相同的相对路径,TypeScript 配置必须能解析 shared 引用,Compose 必须传入 LOG_FILEUNIDESK_LOG_RETENTION_BYTES;如果服务需要在内存中暴露 /logs,可以继续维护有限 recentLogs,但落盘只能通过统一 hourly writer。业务归档日志(例如 Code Queue task output archive)可以保留 append-only 文件,但不得复用 UniDesk service JSONL 命名族,也不得替代 /logs 的结构化服务日志。

bun scripts/cli.ts check 必须包含日志轮转门禁:核心 Bun 服务源码不得直接向 LOG_FILE append,且必须引用统一 hourly writerRust backend-core logger must expose equivalent rotation/pruning markers for the same check. 新增服务如果进入主 Compose,也要纳入该门禁的 checked file 列表。

Log Access

bun scripts/cli.ts server logs 同时读取文件日志和 Docker logs 尾部。文件日志是服务崩溃时的第一现场,Docker logs 是容器启动失败和 stdout/stderr 的辅助来源。默认输出必须包含 tail 字节数、是否截断和完整文件路径;扩大读取范围只能通过显式 --tail-bytes N,且 CLI 会对单次 tail 设置硬上限。

Diagnostic Output Limits

所有诊断型 CLI 输出必须优先摘要化、尾部化或分页化,禁止默认倾倒大 JSON、全量日志、全量 trace 或 .state/logs 宽泛搜索结果。当前硬限额入口包括:server logs 默认 3000 bytes tail、job list 默认 50 条、job status 默认 12000 bytes tail、codex task/trace/output 默认分页与文本预览、microservice proxy 默认 body 预览且 --raw 仍受硬限额保护。确实需要完整响应时必须显式使用对应的 --full--full-text--tail-bytes--limit 参数,并在验收记录中说明为什么需要扩大输出。

CLI 写 stdout/stderr 遇到下游 pipe 关闭的 EPIPE 必须安静退出,不能打印 Bun stack trace。常见验证命令是 set -o pipefail; bun scripts/cli.ts server status | head -1,应只看到第一行 JSON 而无额外错误噪声。

Task Liveness

backend-core 必须把 queued、dispatched、running 视为待处理任务,并通过 TASK_PENDING_TIMEOUT_MS 对长时间没有 provider 终态回报的任务做超时处理。超时任务转为 failedresult 中保留 timeout、previousStatus 和 previousResult 摘要,避免 态势总览 的待处理数量长期卡住且无法解释。

Performance Metrics

backend-core 必须提供 /api/performance,返回滚动窗口内的 HTTP 组件请求统计、最近失败请求、内部操作统计、最近慢操作、进程内存、PGDATA 用量和 Code Queue PostgreSQL 存储摘要。组件统计必须包含请求数、失败数、失败率、平均延迟和 P95,内部操作统计必须包含服务名、操作名、次数、平均延迟和 P95;失败和慢操作记录必须保留时间、状态、耗时、路径或细节,避免只给汇总数字而无法定位。

frontend Bun server 必须提供同源 /api/frontend-performance,记录 webui 静态资源、登录/session、API 代理和 frontend->core 代理操作耗时。浏览器中的 运行总览 / 性能面板 必须把 frontend 与 backend-core 指标合并展示为 Bwebui 曲线、组件汇总、最近失败请求、内部操作汇总和最近慢操作;完整性能 JSON 只能通过显式 查看原始JSON 打开。

Low-Memory Diagnostics

主 server 是低资源、低抖动控制面,排查内存时必须先区分共享内存、容器 cgroup 占用和进程私有占用。PostgreSQL 后端进程的 RSS 会重复显示 shared_buffers 等共享映射,不能把多个 postgres 进程 RSS 简单相加当成真实内存消耗;优先看 docker stats unidesk-database、cgroup memory、/proc/<pid>/smaps_rollup 的 PSS/USS、pg_stat_activity 连接数和 pg_settings 中的 shared_buffers/work_mem

如果 PostgreSQL 容器总占用和 PSS 并不异常,不应优先通过压缩 shared_buffers 解决主 server OOM。更高优先级是识别非核心、交互式和开发型进程,例如 web terminal、长驻 agent session、一次性日志调查或大输出 CLI,把它们迁移到 D601、增加 TTL/硬上限,或通过 server logsjob statusmicroservice proxy 的默认输出限额减少瞬时内存尖峰。只有在连接池、真实 cgroup 占用和慢查询证据都指向 PostgreSQL 时,才调整 PostgreSQL 内存参数。

性能优化必须先用这些指标锁定慢操作名称、路径、耗时和代理层级,再改后端查询或前后端通信策略;不得只凭主观体感改 UI。Code Queue 这类控制面页面出现 core_proxyGET /api/microservices/code-queue/proxy/api/tasks/overviewPOST /api/microservices/code-queue/proxy/api/tasks/<id>/read 等超过 1s 的慢操作时,应保留优化前后的性能面板证据,并同时记录 live API 耗时、容器内存、/health 存储摘要和是否仍通过 PostgreSQL/append-only archive 重建历史数据。短 TTL cache、warmup 或页面内存缓存只能作为重复请求抖动保护,性能证据必须证明数据库索引/聚合、分页和渐进式披露本身已把核心路径降到目标内,不能用长缓存遮蔽慢 SQL 或全量 JSON 物化。

当最近失败请求集中出现 frontend core_proxy 502/503/504,路径为 /api/microservices/code-queue/proxy/... 的 overview、trace 或 summary,且 k3s/k8s Pod 仍在运行时,必须先运行 bun scripts/cli.ts microservice diagnostics code-queue,区分 provider-gateway online、WebSocket HTTP tunnel、k3sctl-adapter、Kubernetes API service proxy 和目标 Service 五段状态。provider tunnel 类失败必须记录响应 body/headers 中的 requestIdstagefailureReasonx-unidesk-request-idx-unidesk-tunnel-error;如需主动验证错误结构,运行 bun scripts/cli.ts microservice tunnel-self-test code-queue,该自测应返回预期失败但 ok=true 的诊断结果。随后再继续判断“Kubernetes API service proxy 不可达”“Code Queue 进程不可达”和“Code Queue event loop 被热路径同步工作饿死”。如果 debug health 或 provider-gateway egress health 显示 providerGatewayEgressProxyActiveTunnels 持续偏高、pendingTunnels 非零或 oldestTunnelAgeMs 长时间增长,应先按 provider-gateway egress tunnel 生命周期排障,确认 egress_tcp_open、connect timeout、idle cleanup 与 core socket close 清理是否生效。排障顺序是同时查看 /api/frontend-performance/api/performancek3sctl-adapter /api/control-plane、Kubernetes Pod /live/health、overview/trace-step curl、kubectl top pod 或 Docker stats、容器 RestartCount/OOMKilled 和 Code Queue 日志;如果 Pod 内 /health 也超时,应优先检查实时 output 发布、archive 读取、transcript 构建、统计计算、启动维护、历史 OA backfill 和远程 Provider 准备/SSH 子进程是否阻塞 event loop,而不是先调整 frontend 渲染或代理超时。Code Queue 默认不得在启动时自动执行历史 OA backfill 或通知表索引维护;显式 backfill 必须作为运维动作记录,并在运行期间并发证明 /live/health/api/tasks/overview 仍快速返回。涉及 D601 等远程 Provider 时,还要检查 runCodeQueueSsh/开发容器准备是否仍存在同步子进程、无 timeout 的 SSH、无上限 stdout/stderr 或 stale TUN 重建等待;修复后必须在远程准备探针运行期间并发证明 Pod /health/api/tasks/overview 仍快速返回。

Code Queue task 明明产出最终回复却反复 retry_wait 时,应优先用任务详情里的 latest attempt 字段核查 terminalStatustransportClosedBeforeTerminalappServerExitCodefinalResponseCharsjudge.raw._safetyOverride 和 attempt output。OpenCode 远程任务中,opencode completed status=completed exit=0 加当前 attempt 非空 assistant 输出应对应 terminalStatus=completedtransportClosedBeforeTerminal=false;如果因为缺少 step_finish 事件仍触发 _safetyOverride=terminal_not_completed,说明协议终态归一化有回归。相反,当前 attempt 没有最终 assistant response 时即使 tool/read/bash 证据完整,也必须 retry,不能用旧 task.finalResponse 或 reasoning/tool evidence 代替可见最终回复。

Code Queue Liveness

Code Queue 的“任务是否卡死”不能由单一控制面字段判断。排障必须同时看 PostgreSQL 中的 running/judging 任务、D601 scheduler 本地 active run/active slot/active queue、scheduler-owned heartbeat、Trace/OA 持久化进度和 OA publisher pending/lastError。master code-queue-mgrpostgres-control-plane 视图只证明数据库行存在;当它显示 activeRunSlotCount=0 但 D601 heartbeat 仍新鲜时,正确结论是 control-plane/execution-plane 分裂,diagnostics 应显示 split-braindegraded,不能宣称任务未执行或卡死。诊断输出中的 effectiveLiveness=livesplitBrainLive=truerecommendedAction=continue-supervision 表示这是 heartbeat 新鲜的观测分裂,应继续监督;effectiveLiveness=at-riskrecommendedAction=investigate-heartbeat-risk 表示存在 expired/missing/stale heartbeat 风险,需要优先人工确认。

Trace/OA 长时间没有新 seq 但 scheduler heartbeat 正常时,应归类为 trace gap 或 publisher degraded,不得自动 retry。只有 scheduler 本地没有 active run,且对应 owner heartbeat 已过期时,才允许进入 stale recovery candidate;缺失 heartbeat 只能触发 degraded 诊断和人工确认。任何恢复入口都必须由 scheduler 执行,使用条件更新和审计事件区分 user interrupt、admin stale recovery 与 service restart recovery;禁止直接修改 production PostgreSQL 任务状态来“修复” active run。