从“日志各打各的”到“链路一眼定位”:Java 端与 Agent 端如何统一错误日志和可观测性 📅 2026/7/3 10:52:04 一、背景为什么 Agent 项目更需要统一错误日志在传统 Java 后端项目里一次请求通常都在同一个技术栈内完成Controller 接收请求Service 处理业务DAO 操作数据库最后返回结果。即使中间出了问题也可以通过traceId、日志关键字、异常栈快速定位。但 Agent 项目不一样。在实际业务中Java 端往往负责业务编排、数据入库、风控同步、告警触发等生产链路Agent 端则可能由 Python 实现负责调用大模型、执行 Browser Agent、处理 RAG 检索、管理 memory、运行固定 Skill 等能力。以风险信息监控场景为例整体链路大致是Java 风控服务 - 调用 Python Agent 接口 - Python 侧执行固定 Skill 或 Browser Agent - 浏览器自动化查询外部风险信息 - 返回结构化结果 - Java 解析、去重、入库、触发告警这个链路功能上并不复杂真正麻烦的是一旦失败到底是 Java 端的问题还是 Agent 端的问题是参数校验失败还是浏览器初始化失败是验证码失败还是 RAG 检索失败是 Agent 执行慢还是 Java 入库慢如果没有统一的错误日志和可观测性设计最后就会变成Java 说我调用 Agent 超时了。 Python 说我这边好像执行失败了。 浏览器工具说某一步点不动了。 业务方说为什么这条企业风险信息没同步成功每一层都有日志但每一层都只能看到自己的一小段整条链路串不起来。这也是 Agent 工程化从“能跑”走向“可生产”时必须解决的问题。二、原来的问题不是没有日志而是日志没有统一语义很多项目早期都会有日志但这些日志往往只是“局部可读”不具备生产排障能力。原先链路主要有几个问题。1. 异常返回不统一Agent 端失败时可能直接返回{ ok: false, error: browser run failed }或者内部直接抛出RuntimeError。这种方式的问题是Java 调用方只能靠字符串猜错误原因。比如captcha failed validation failed browser timeout result empty这些字符串对人来说能看懂但对 Java 业务系统来说很难稳定处理。因为它不知道这个错误发生在哪个阶段是否可以重试是用户输入问题还是系统执行问题是固定 Skill 失败还是开放式 Agent 失败是否应该告警还是直接降级所以第一个核心问题不是“日志不够多”而是“错误没有标准语义”。2. Java 和 Agent 链路断裂Java 端本来可能已经有requestId或traceId但如果调用 Python Agent 时没有透传过去那么两边日志就是断的。Java 看到的是requestIdjava-xxx 调用 Agent 失败Python 看到的是requestIdpython-yyy browser skill failed这两个 ID 不一致排查时只能靠时间、参数、企业名称去人工匹配效率非常低。在生产环境里这种问题会被放大。尤其是高并发或者批量同步任务中同一时间可能有大量企业风险查询请求如果没有统一 requestId失败链路基本很难快速定位。3. memory、RAG 等非核心依赖会影响主流程Agent 链路里经常会接入 memory、RAG、模型、浏览器工具等组件。但并不是所有组件都应该和主链路强绑定。比如风险信息查询的核心目标是查询外部风险信息并返回结构化结果。memory 和 RAG 可以增强对话体验但如果 memory 读取失败就直接打断整个查询链路就会导致非核心依赖拖垮主业务。所以 Agent 工程化里必须区分核心依赖失败后当前任务无法继续 非核心依赖失败后可以降级不影响主流程这也是后面 fail-open 设计的基础。4. 缺少阶段耗时无法判断慢在哪原先可能只有总耗时agent elapsed 12000ms但 12 秒到底慢在哪可能是memoryLoadMs 200ms ragSearchMs 600ms browserInitMs 4000ms skillExecMs 6500ms memoryWriteMs 300ms也可能是 Java 端慢agentCallMs 8000ms parseMs 100ms dbPersistMs 3500ms totalMs 11600ms如果只看总耗时就无法判断是模型慢、浏览器慢、RAG 慢还是 Java 入库慢。没有阶段耗时后续做 P95、P99 优化也没有依据。三、核心思想统一的不是“日志格式”而是“错误语义 链路标识 阶段观测”Java 端和 Agent 端统一错误日志不应该只理解为大家都打印 JSON 日志或者大家都带上一个 requestId。更准确地说应该统一四件事1. 统一错误模型失败必须有 errorCode、errorStage、retryable 2. 统一链路标识Java 和 Agent 使用同一个 requestId / traceId 3. 统一阶段耗时能拆出 Agent 内部和 Java 业务侧耗时 4. 统一降级策略区分核心失败和非核心依赖失败这样一来日志才不是散点而是一条完整链路。四、第一步定义统一错误模型Agent 端需要先定义一个标准错误对象例如AgentExecutionError核心字段包括errorCode 错误码表示具体错误类型 errorStage 错误阶段表示失败发生在哪一层 retryable 是否可重试 message 面向开发者的错误描述 details 扩展信息返回给 Java 的失败结构可以统一成{ ok: false, requestId: req-20260702-001, errorCode: AGENT_BROWSER_EXEC_FAILED, errorStage: BROWSER_EXEC, retryable: true, message: browser tool execution timeout, details: { toolName: click, pageUrl: https://example.com, elapsedMs: 10000 } }这样 Java 端就不需要再解析字符串而是可以直接根据字段处理。例如errorCode AGENT_CREDITCHINA_CAPTCHA_FAILED 说明是验证码失败可以记录为外部站点挑战失败 errorCode AGENT_RESULT_VALIDATION_FAILED 说明结果校验失败不应该直接入库 errorStage INPUT_VALIDATION 说明请求参数本身不合法通常不应该重试 retryable true 说明可以进入重试队列或等待下次调度这一步的价值是把“人肉看日志”变成“系统可识别的错误语义”。五、第二步Java 和 Agent 贯通 requestId / traceId统一错误模型解决的是“失败是什么”requestId 解决的是“这次失败属于哪一次请求”。Java 端作为业务入口应该负责生成或透传 requestId。调用 Agent 时在请求头里带上X-Request-Id: req-20260702-001 X-Trace-Id: trace-20260702-001Python Agent 入口统一读取这两个字段并在后续所有日志、响应、Skill 返回、工具执行日志中都带上。链路变成Java request start requestIdreq-001 Java call Python Agent requestIdreq-001 Python request start requestIdreq-001 Skill lifecycle start requestIdreq-001 Browser tool start requestIdreq-001 Browser tool failed requestIdreq-001 Python response failed requestIdreq-001 Java parse Agent response requestIdreq-001这样排查时只需要搜索一个 requestId就能把 Java 业务日志、Python Agent 日志、Browser Tool 日志全部串起来。这一步看似简单但对跨语言系统非常关键。因为 Java 和 Python 是两个运行时、两套日志系统、两套异常栈如果没有统一 requestId就很难形成完整调用视角。六、第三步按阶段拆分日志而不是只打印开始和结束统一日志不代表所有地方都打印一堆内容而是要按链路关键阶段打点。可以拆成四层。1. HTTP 入口层记录请求进入和返回agent.request.start agent.request.finish agent.request.fail关键字段requestId path method elapsedMs ok errorCode errorStage2. Skill 生命周期层记录 Skill 执行阶段skill.lifecycle.start skill.lifecycle.finish skill.lifecycle.fail关键字段requestId skillName inputValid outputValid elapsedMs errorCode errorStage3. Browser Tool 层Browser Agent 最容易失控所以工具调用必须单独记录agent.tool.start agent.tool.finish agent.tool.fail关键字段requestId toolName pageUrl elapsedMs resultSummary failureReason比如一次点击失败可以记录{ event: agent.tool.fail, requestId: req-001, toolName: click, pageUrl: https://example.com/search, elapsedMs: 10000, failureReason: tool timeout }这样就能明确知道这次不是 Java 入库失败也不是结果解析失败而是 Browser Agent 某个工具步骤超时。4. Java 业务消费层Java 端也需要拆分耗时agentCallMs parseMs dbPersistMs totalMs比如{ requestId: req-001, agentCallMs: 8500, parseMs: 120, dbPersistMs: 300, totalMs: 9000 }如果agentCallMs很高说明慢在 Agent如果dbPersistMs很高说明慢在 Java 数据库持久化如果parseMs高说明结构化结果可能过大或解析逻辑有问题。七、第四步区分核心失败和非核心依赖失败Agent 链路里不是所有失败都应该中断主流程。比如memory 读取失败 RAG 检索失败 memory 写回失败这些在很多业务场景里都不是核心依赖。它们失败了可以记录日志但不一定要让主请求失败。可以采用 fail-open 策略memory 读失败 - 降级为空 memory继续执行 RAG 检索失败 - 降级为空检索结果继续执行 memory 写失败 - 记录 session error不影响主结果返回但像下面这些错误就应该直接失败输入参数非法 浏览器初始化失败 固定 Skill 查询失败 结构化结果缺失 主体一致性校验失败这种区分很重要。因为生产系统追求的不是“任何组件失败都立刻报错”而是“核心链路可靠非核心能力可降级”。八、第五步固定 Skill 的错误要比开放式 Agent 更标准开放式 Browser Agent 具有不确定性可能会因为页面变化、工具调用、模型决策等因素失败。但固定 Skill 不一样。固定 Skill 本质上是面向确定业务场景的生产能力例如信用中国查询、企业风险查询等。因此固定 Skill 的错误应该更加标准化。以creditchina_query为例可以设计这些错误码AGENT_CREDITCHINA_INPUT_INVALID AGENT_CREDITCHINA_CAPTCHA_FAILED AGENT_RESULT_VALIDATION_FAILED AGENT_BROWSER_EXEC_FAILED AGENT_CREDITCHINA_QUERY_FAILED这些错误码对应不同处理方式输入无效Java 端不重试直接记录参数问题 验证码失败可重试也可能触发外部站点异常告警 主体不一致不能入库必须拦截 浏览器执行失败可根据 retryable 判断是否重试 查询失败记录失败原因等待下次调度固定 Skill 的价值在于稳定、可控、可校验。所以固定 Skill 不应该只返回一句“查询失败”而应该把失败原因明确暴露给 Java 业务端。九、完整链路可以这样设计最终优化后的链路可以抽象成Java / 前端请求 - Java 生成或透传 requestId - Java 调用 Python Agent并透传 X-Request-Id - Python Flask 入口记录 request start - 参数解析 - 判断进入 chat / direct skill / fixed skill - 如果是 chat - memory 读取失败则 fail-open - RAG 检索失败则 fail-open - skill 分发 - 进入 SkillLifecycleRunner - input schema 校验 - browser init - runtime 构造 - 执行固定 Skill 或 browser_react - browser runtime 执行 - tool step 日志 - tool budget / timeout 控制 - 固定 skill 结果校验 - output schema 校验 - memory 写回 - Python 返回标准响应 - ok - requestId - errorCode - errorStage - retryable - result - Java 消费端解析 - 识别标准错误 - 成功则入库、去重、触发事件 - 记录 agentCallMs / parseMs / dbPersistMs / totalMs这条链路的关键不是多打印日志而是让每个阶段都具备明确边界。十、统一错误日志后的收益统一 Java 端和 Agent 端错误日志之后收益主要体现在四点。1. 失败可分类原来只知道“Agent 调用失败”现在可以知道是输入失败 是 RAG 失败 是 memory 失败 是 browser init 失败 是 tool timeout 是结果校验失败 还是 Java 入库失败2. 链路可追踪通过同一个 requestId可以从 Java 查到 Python再查到 Browser Tool最后定位到具体失败步骤。3. 慢请求可定位通过阶段耗时可以判断慢在Java 调 Agent Python memory Python RAG Browser 初始化 Skill 执行 Tool 调用 Java 解析 Java 入库这为后续 P95、P99 优化提供了基础数据。4. 系统更适合生产消费Agent 不再是一个“黑盒能力”而是变成 Java 业务系统可以理解、可以重试、可以降级、可以告警的生产组件。十一、总结Java 端和 Agent 端统一错误日志本质上不是日志格式问题而是跨语言、跨运行时、跨组件的工程化治理问题。真正应该统一的是错误语义errorCode、errorStage、retryable 链路标识requestId、traceId 阶段耗时Agent 内部耗时 Java 业务耗时 降级策略核心链路失败非核心依赖 fail-open 执行边界tool budget、tool timeout、健康检查对于 Agent 项目来说功能跑通只是第一步。真正进入生产环境后更重要的是让系统在失败时可解释、在变慢时可定位、在依赖异常时可降级、在跨语言调用时可追踪。一句话总结就是不要只把 Agent 当成一个接口调用而要把它当成一条可观测、可治理、可兜底的生产链路来设计。