
可观测性与审计三条信道、append-only 与「空白即线索」语言 / Language中文 系列第十一章目录 上一章前端与流式交互 下一章部署交付实录项目Agentdemo007 —— 电商智能客服 Agent技术栈Java 17 / traceMDCJSON 日志/ auditappend-only 事件链/ Micrometer进程内指标/ SSE progress实时进度周期随各 Phase 埋点 → 09-16 延迟攻坚补出站日志 → 09-18 管理台与闸口 → 09-20 提交制收口验证规模StepOutcomeAuditor/AuditEventType/AgentMetrics 单测钉死口径SseProgressEmitter 序列化形状与发送容错测试源码github.com/Gavincui123/Agentdemo007前言可观测不是多打点日志。这个项目落到最后是三条语义正交的信道加一条纪律。trace 信道管链路标识MDC traceId 进 JSON 日志、回写响应头x-trace-id一路走到浏览器气泡audit 信道管业务事件AuditEventappend-only、异步落audit_event表只增不改Proceed/Retry 不产生审计progress 信道管实时进度SSE 推 step_started/step_finished/reply_chunk/reply_ready 给浏览器metrics 则只经AgentMetrics门面打点出/api/obs/summary快照。纪律只有一句任何没有日志的空白本身就是线索——这一章最贵的一课93 秒的静默挂起就是它教会我的。一、traceId 一条线从过滤器到浏览器气泡trace/TraceFilter挂在 HIGHEST_PRECEDENCE整条线是它拉起来的生成或透传入站 X-Trace-Id写进 MDC键 traceId供 UnifiedResponse 取用并在响应头回写——traceId 由此贯穿 HTTP、异常处理器、日志logback 的 %X{traceId}全链路。生产日志是 JSON 结构化的traceId:%X{traceId}是每个事件的固定字段。这条线的价值在终点响应头x-trace-id把 traceId 交到前端最终出现在每条消息气泡的 meta 区——用户反馈这条回答不对时traceId 直接定位到服务端日志行。跨进程也不断MQ 消费用MdcTraceContextPropagator合成 W3C traceparent 注入消息属性消费线程还原回 MDC注释里预留了 prod 换 OTel 原生实现的位置。二、append-only 审计链13 种事件与单一映射源审计事件的类型全集是AuditEventType枚举的 13 个值类型语义INJECTION接入层提示注入检测命中零 LLM 短路TOOL_CALL / TOOL_FAILURE工具调用留痕 / 自纠正耗尽短路HITL转人工/超时/权限不足工单状态流转FAILOVER / MODEL_DOWN / FAILOVER_EXHAUSTED模型故障转移三态SESSION_DOWN / RAG_SKIP / OUTPUT_FALLBACK各层降级REFUSAL无依据拒答——“业务级拒答非降级”DEGRADATION通用降级留痕EXCEPTION未预期异常收口到 INTERNAL 话术三条收口纪律让这条链可信。第一append-only枚举名name()稳定事件只增不改。第二单一映射源AuditEventType.from(scenario)把 16 个降级场景映射到审计类型避免各处自行 if/else 漂移。第三per-step 自动收口StepOutcomeAuditor统一决定要不要产生审计——Proceed/Retry 无审计推进/重试非降级ShortCircuit 审计并由调用方收口话术Degrade 审计并 markDegraded。业务代码不手写审计分支忘记埋点这个失败模式在结构上不存在。// common/pipeline/StepOutcomeAuditor.java —— per-step 审计收口// Proceed/Retry 无审计推进/重试非降级// ShortCircuit → 审计 话术收口Degrade → 审计 markDegraded// detail 前缀 短路:/降级:/异常: stepName scenario/message三、SSE progress 协议给浏览器看的第三条信道事件时序发送侧SseProgressEmitter连接建立 →step_started{step}→step_finished{step, outcome, scenario?}×15 →reply_chunk{text}×N →reply_ready{ChatResponse}→ complete。序列化异常兜底为{}坏事件不炸流dead标志置位后 emit 静默 no-op——改它是因为超时后流水线继续推进时每个事件都撞 “already completed” 各刷一条 WARN 刷屏现在只记 debug。与审计信道的关系一句收口两条信道正交同一个 step 产出各自的出口——实时性给浏览器可追溯性给数据库谁也不替代谁。超时兜底补上了这个协议的最后一环ChatController注释原文此前超时后连接被容器掐断流水线『晚到的成功』全部撞 already-completed 被吞前端什么都收不到。现在 chatStream 注册 SseEmitter.onTimeout超时即向客户端补发 reply_ready负载 ChatResponse 降级话术 PIPELINE_TIMEOUT complete。另外两处小埋点都值得记。logArrival到达打点此前首条日志是查询改写 LLM 完成的时刻比请求到达晚 0.8~2.6 秒发起会话→首条日志的延迟没法区分传输段与管线段这一行把到达时刻显式落日志。FirstTokenTimingEmitter在首个 TokenChunk 到达时记首字毫秒——口径与用户体感一致打字指示消失、文字开始出现的时刻。四、93s 静默挂起空白即线索可观测性最贵的一课来自延迟攻坚期第八章有完整叙事日志里出现过两段 93s 和 64s 的空白——没有任何 LLM 出站日志请求像人间蒸发。根因RAG 的 embedding 调用用的是裸new RestTemplate()连接和读取都无超时SiliconFlow 间歇挂起时就被无限拖住。修复后每次模型调用都有出站行LLM出站 scene回答生成 modelsiliconflow-small durMs7405 oktrue attempt1/2。而且流式路径后来又漏了一次同步路径有出站日志流式不经FailoverExecutor是零日志的——第三章记录了这次补漏。纪律因此定型一句话无日志空白从此不可能再发生——空白即线索。观测覆盖不是一次性的新路径流式、旁路、批处理每次上线都要重新回答这条路径上日志空白意味着什么。五、降级语义正交性什么算系统坏了16 个降级场景各有话术但不是每个都该算进故障率——本节划清哪三条语义必须分账。16 个DegradationScenario每个都带面向用户话术INJECTION/BAD_REQUEST/PAYLOAD_TOO_LARGE/RATE_LIMITED/SESSION_DOWN/MODEL_DOWN/FAILOVER_EXHAUSTED/UNKNOWN_INTENT/TOOL_FAILURE/RAG_SKIP/HITL_TIMEOUT/WORKFLOW_APPROVAL_TIMEOUT/OUTPUT_FALLBACK/PIPELINE_TIMEOUT/USER_CANCELLED/INTERNAL。但统计口径上三条语义必须正交拒答不标 degradedRefusalGateStep“不标记 degraded与降级语义正交”——拒答是业务级正确行为混进降级统计会让故障率虚高。业务驳回不是降级AfterSaleWorkflowOutcome业务驳回≠系统失败E1——不 ShortCircuit走 presetReply 短路。退役场景保留常量WORKFLOW_APPROVAL_TIMEOUT在提交制2026-09-20后不再被工作流步产出——审批是事件无请求内等待/超时常量保留作对外指标兼容。枚举即对外契约删名字比留空转更贵。这三条合起来是同一个原则监控里的每个数字都应该能被一句业务语言解释——“系统故障率里混进用户没登录”“政策没查到”“这单不符合退款条件”数字就再也不可信了。六、管理台审批是状态的不是请求的admin/AdminHitlController四个端点GET /admin/hitl/tickets——列表PENDING 优先已决议单留痕带徽章内存DB 持久副本合并重启不丢历史/{id}/confirm/{id}/reject/{id}/resume——显式恢复接口APPROVED 单重驱。两个安全细节业务前置校验confirm 放行前经HitlBusinessGate对账订单真实状态不满足则不决议工单保持 PENDING、返回 CONFLICT(409)——第七章那次 mock↔DB 对账事故就是这条链上炸出来的。鉴权同形话术AdminAuthInterceptor逐字比对X-Admin-Token未授权不抛 5xx、不进下游控制器同形 HTTP 200 code 体内表达①话术短路——①②是本系列的两条全局收口纪律①对外只回面向用户的话术②任何依赖失败降级继续而非崩溃——闸口语义与管理台语义一致。指标侧收口在AgentMetrics门面所有指标只经此门面记录同StepOutcomeAuditor之于审计——禁止各处散落 MeterRegistry 直接打点导致命名/标签漂移。/api/obs/summary是 16 字段快照端点不缓存、不持久化空指标→0②每步降级端点恒可用不抛。七、可观测三流一次 step 产出三个出口流水线步骤产出StepOutcome① SSE 实时进度SseProgressEmitterstep_started/finished/chunk② 审计落库StepOutcomeAuditor → AuditEvent→ MQ → audit_event 表③ 指标计数AgentMetrics 门面→ /api/obs/summary 快照浏览器 PipelineSpine逐节点亮灯/转琥珀复盘与审计查询trace_id 索引可观测页 ECharts环图 降级分场景条形图三条信道语义正交的验收方式关掉任意一条其余两条照常工作——审计不依赖页面开着指标不依赖有人看进度不依赖落库成功。指标快照最终呈现为可观测页面这张是全链路遥测的真实截图八、这一章带走的五条traceId 要走到用户手里响应头 → 气泡 meta用户报障自带定位符比请描述一下什么时候出的问题快一个数量级。审计靠结构不靠自觉per-step 收口器决定谁产生审计业务代码没有忘记埋点这个失败模式。信道的正交性要显式声明实时进度、审计落库、指标计数的注释里都写了与 XX 正交——不声明后来者一定会合并它们。空白即线索每条新路径上线前回答这条路径上没有日志意味着什么。正交语义是监控可信的前提拒答、业务驳回、退役场景各归各位故障率才是一个能报警的数字。九、已知边界诚实清单无 Prometheus 后端dev 是 SimpleMeterRegistry 内存计数——application.yml 的 exposure 虽列了prometheus抓取端但未引入micrometer-registry-prometheus依赖该端点实际并未装配指标是进程累计当下值重启清零。告警通道 NO_OPAlertChanneldev 丢弃、“prod 覆盖为 Webhook/邮件/PagerDuty/IM”——未实现AlertRuleEvaluator只消费快照 Map“评估器对来源无感”。无 OTelMDC 桥是自研默认实现注释预留 prod 覆盖点无分布式 span 树。指标有滞后陷阱totalTurns 走异步落库可观测页已加口径注释防误读。密钥启动校验类不存在——与 DEPLOY.md已知缺口对账双源提示词人工同步登记在第二章与第十二章的已知边界。本文机制出处trace/TraceFilter、observability/audit/AgentMetrics/ObservabilityController/alert/、common/pipeline/StepOutcomeAuditor、common/progress/ProgressEvent、web/SseProgressEmitterChatController、admin/AdminHitlControllerAdminAuthInterceptor、logback-spring.xml。相关阅读系列目录 · 上一章前端与流式交互 · 下一章部署交付实录 · 第三章·流式出站日志补漏