首页 > AI教程 >Agent可观测性实战:全链路追踪+JSON结构化日志

Agent可观测性实战:全链路追踪+JSON结构化日志

来源:互联网 2026-07-11 06:32:01

通过引入JSON结构化日志、全链路追踪及Micrometer指标,Agent可观测性支持日志搜索、请求关联与趋势监控,关键字段包括trace_id、prompt_hash等,便于故障排查与成本分析,提升诊断效率。

### 一、MVP 的日志:能跑通,但排不了障 MVP 阶段,你的日志大概长这样: ``` [Agent] User message: CNC-001 振动异常 [DiagnosisTool] Generating diagnosis for alarm: 振动异常 [SSE] Stream completed, total tokens used: 2341 [Agent] User message: CNC-002 温度过高 ``` 看下来是不是感觉挺热闹?但真要是拿它来排查问题,你会发现它什么都答不上: - “CNC-001 这次请求一共调了几次 LLM?每次的 Prompt 是什么?消耗了多少 Token?” - “createWorkOrder 被调用时传了什么参数?返回值是什么?” - “这条日志和上一条日志是同一个请求,还是两个不同的请求?” 上面那段日志,一个都回答不了。原因很简单:没有 Trace ID,没有结构化字段,没有关键数据记录。说白了,它只是“能跑通”的日志,不是“能排障”的日志。 --- ### 二、可观测性的三根支柱 生产级的 Agent 可观测性,靠三样东西撑起来: 三者各司其职: - 结构化日志:让你在出问题时能搜索、能分析 - 全链路追踪:让同一个请求的所有日志关联在一起 - 指标监控:让你看到趋势和异常模式 --- ### 三、结构化日志:从“人肉约定”到“JSON 事件” MVP 的日志属于“人肉约定”——`[Agent]`、`[Supervisor]`、`[SSE]` 都是随手写的前缀,没有统一规范。 改造之后,每一条日志都变成了结构化的 JSON 事件,像这样: ```json { "ts": "2026-06-30T10:32:15.234Z", "trace_id": "a1b2c3d4e5f6", "session_id": "sess-001", "tenant_id": "factory-a", "event": "tool_call", "execution_id": "e7b2a1c3...", "tool_name": "createWorkOrder", "params": "CNC-001,维修,HIGH,轴承磨损", "result_preview": "{\"workOrderId\":\"WO-20260630-001\"...}", "status": "OK", "duration_ms": 42 } ``` 实现上不需要引入新的日志框架。SLF4J + MDC 就是天然的 trace_id 传播机制: ```ja va // observability/StructuredLogger.ja va @Component public class StructuredLogger { public void attachTrace(RuntimeContext ctx) { MDC.put("trace_id", ctx.getTraceId()); MDC.put("session_id", ctx.getSessionId()); MDC.put("tenant_id", ctx.getTenantId()); } public void toolCall(RuntimeContext ctx, String executionId, String toolName, String params, String resultPreview, String status, long durationMs) { attachTrace(ctx); log.info("tool_call execution_id={} tool_name={} params={} " + "result_preview={} status={} duration_ms={}", executionId, toolName, params, resultPreview, status, durationMs); } public void llmCall(RuntimeContext ctx, String promptHash, int inputTokens, int outputTokens, long latencyMs, String model) { attachTrace(ctx); log.info("llm_call prompt_hash={} input_tokens={} " + "output_tokens={} latency_ms={} model={}", promptHash, inputTokens, outputTokens, latencyMs, model); } } ``` 关键就是那一行 `MDC.put("trace_id", ...)` —— 所有后续日志自动带上 trace_id,不需要在每个 `log.info` 里手动传。 --- ### 四、状态机的每个节点都在“发声” `AgentRuntime` 的 `execute()` 方法在每次状态变更时都会调用 `tracer`: ```ja va // AgentRuntime.ja va public T execute(RuntimeContext ctx, Supplier action) { tracer.attachTrace(ctx); // 状态迁移 → 日志事件 ctx.transition(SESSION_READY); tracer.stateTransition(ctx, RECEIVED, SESSION_READY); ctx.transition(CONTEXT_READY); tracer.stateTransition(ctx, SESSION_READY, CONTEXT_READY); // LLM 调用 → 日志事件 ctx.transition(MODEL_THINKING); tracer.stateTransition(ctx, CONTEXT_READY, MODEL_THINKING); // ... 执行 ... // 请求完成 → 日志事件 tracer.requestCompleted(ctx, resultPreview); return result; } ``` 一个完整的请求链路产生的日志序列是这样的: ``` trace=a1b2 state=RECEIVED→SESSION_READY elapsed=0ms trace=a1b2 state=SESSION_READY→CONTEXT_READY elapsed=1ms trace=a1b2 state=CONTEXT_READY→MODEL_THINKING elapsed=2ms trace=a1b2 llm_call prompt_hash=d4e2f input=1234 output=567 latency=890ms model=deepseek-chat trace=a1b2 tool_call execution_id=e7b2a tool=queryDeviceAlarms params=CNC-001 status=OK duration=12ms trace=a1b2 llm_call prompt_hash=8c3a1 input=2345 output=345 latency=1200ms model=deepseek-chat trace=a1b2 tool_call execution_id=3f1c8 tool=createWorkOrder params=CNC-001,... status=OK duration=8ms trace=a1b2 state=MODEL_THINKING→POST_PROCESSING elapsed=2156ms trace=a1b2 state=POST_PROCESSING→COMPLETED elapsed=2158ms trace=a1b2 request_completed state=COMPLETED elapsed_ms=2158 result_preview="设备状态..." ``` 所有日志共享同一个 `trace=a1b2`,你只需要 `grep trace=a1b2` 就能拉出这次请求的完整故事。 --- ### 五、Micrometer 指标:看趋势,不看单点 日志帮你定位具体问题,指标帮你发现模式和趋势: ```ja va // observability/AgentMetrics.ja va @Component public class AgentMetrics { private final Counter requestCounter; // agent.requests.total private final Counter toolCallCounter; // agent.tool.calls.total private final Counter llmCallCounter; // agent.llm.calls.total private final Counter failureCounter; // agent.requests.failed private final Timer requestTimer; // agent.request.duration public void recordRequest(AgentState finalState, long elapsedMs) { requestCounter.increment(); requestTimer.record(elapsedMs, MILLISECONDS); if (finalState == FAILED) failureCounter.increment(); } } ``` 这些指标通过 `/actuator/prometheus` 暴露,Grafana 可以直接拉取: ``` agent_requests_total 42 agent_requests_failed 3 agent_tool_calls_total 187 agent_llm_calls_total 89 agent_request_duration_seconds_sum 120.5 agent_request_duration_seconds_count 42 ``` 配合 Grafana Dashboard,你可以看到: - 失败率是否在上升(可能是 LLM API 不稳定的前兆) - P99 延迟是否在恶化(可能是某个工具查询变慢了) - Token 消耗趋势(成本预估) --- ### 六、prompt_hash:一个被低估的可观测利器 每次 LLM 调用都记录 `prompt_hash`: ```ja va tracer.llmCall(ctx, sha256(prompt).substring(0, 8), inputTokens, outputTokens, latencyMs, model); ``` 这个 8 位哈希的价值在于: - 相同 prompt_hash 的调用应该命中 LLM 缓存(后续可以加入 prompt caching) - prompt_hash 变化可以关联到 Agent 行为变化——如果某次部署后 prompt_hash 变了但行为异常,你能快速定位到是 Prompt 变更引入的 - 成本分析:在 LLM 日志中按 prompt_hash 分组,看哪些 Prompt 模板消耗了最多的 Token --- ### 七、P0 可观测性的边界 | 能力 | 状态 | |------|------| | MDC trace_id 全链路传播 | | | JSON 结构化日志(状态变迁/LLM/工具) | | | Micrometer Prometheus 指标 | | | Otel Agent 自动检测 | P1 | | Grafana Dashboard 模板 | P1 | | 分布式链路追踪(Gateway → Runtime → LLM → DB) | P1 | P0 的策略很清晰:先把日志和指标做对——日志可搜索、指标可聚合。OTel 分布式追踪留到 P1 引入,届时需要 Ja va Agent 或 SDK 集成。 --- ### 八、一句话总结

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。