
当 AI 代码生成工具越来越普及“Vibe Coding”已经从一个小众词汇变成了开发者日常的一部分用自然语言描述需求让模型生成代码快速验证、快速迭代。问题也随之而来——AI 生成的代码“看起来能跑”但一旦进入生产环境遇到看不到的异常、查不到的调用链路、说不清的性能瓶颈整个系统就会变成一个黑盒。本文围绕 Observability可观测性在 AI 工程化中的关键作用结合 Java/Spring Boot 实战讲清楚如何把“凭感觉写代码”变成“可度量、可追踪、可治理”的工程能力。1. Vibe Coding 与 AI Engineering开发者的角色正在变化1.1 什么是 Vibe CodingVibe Coding 这个说法最早出现在 AI 辅助编程社区核心含义是开发者通过自然语言向大模型描述需求让模型直接生成代码开发者主要负责理解需求、拼装模块、快速验证和调整方向。整个过程非常依赖“感觉”——代码跑通了就继续报错了就拷给模型让模型改很少去逐行推敲内部的实现细节。这种模式之所以流行根源在于 AI 编程工具的能力已经足够强。市面上常见的 GitHub Copilot、Trae、通义灵码等工具已经能在很多场景中生成可运行的代码尤其是在 CRUD 接口、常见算法、脚本工具、简单配置类代码上生成质量相当可观。对于原型验证、个人项目、内部工具“能跑”往往就够了。但 Vibe Coding 也有明显的边界问题。如果只依赖“看结果”来判断代码质量很多隐患会被掩盖AI 生成的代码没有日志或者只在出错时输出一个笼统的异常信息。异常处理逻辑缺失一旦请求参数不符合预期直接返回 500。没有埋点、没有指标系统性能下降时根本不知道卡在哪个环节。多模块、多服务之间的调用关系模糊出现问题无法追溯源头。换句话说Vibe Coding 解决的是“从零到一快速产出代码”的问题但没有解决“代码上线后如何证明自己可靠”的问题。1.2 AI Engineering 需要哪些能力AI Engineering 并不是指“用 AI 开发工程”而是指把 AI 辅助开发的方式纳入一套工程规范有设计、有测试、有监控、有复盘、有迭代。它和传统软件工程的差别在于AI 生成代码具备一定的随机性和不确定性因此工程治理的约束必须更强。一个成熟的 AI Engineering 流程通常包含以下几个维度能力维度说明需求建模把自然语言需求拆解为功能点、边界条件和验收标准代码审查对 AI 生成代码进行人工审查重点检查安全性和异常路径自动化测试用单元测试、集成测试覆盖核心逻辑可观测性日志、指标、链路追踪三者结合让代码运行状态可度量持续集成构建、测试、部署流程自动化避免“本地能跑上下线就挂”复盘机制线上问题发生后能快速定位并反哺到后续开发中其中可观测性往往是决定系统能否长期稳定运行的关键。没有可观测性的代码即使测试全部通过进入生产环境后仍然是一台“无法读取内部的机器”。1.3 两者的关键差异从“生成代码”到“保障系统”Vibe Coding 的核心是“让模型替你写代码”AI Engineering 的核心是“让系统在运行时可验证、可诊断、可治理”。这中间的关键桥梁就是 Observability。举个例子Vibe Coding 模式下你用自然语言让 AI 生成一个订单创建接口接口跑通了你很高兴。AI Engineering 模式下你不仅要求接口跑通还要求它记录请求 ID、统计耗时、上报指标、追踪数据库调用链路出现异常时能快速判断是参数问题、业务逻辑问题还是基础设施问题。两种模式并不对立而是进阶关系。Vibe Coding 适合前期快速探索AI Engineering 负责让这些探索结果变成可以长期维护的生产代码。而生产代码的底线就是“出了问题可以查”。2. 为什么 Observability 是 AI 代码落地的关键2.1 可运行不等于可上线很多开发者对 AI 生成代码有一个误解代码能编译、能跑、接口能返回数据就可以上线。实际上“可运行”只证明代码在某个输入条件下没有崩溃并不代表它在真实流量、异常参数、弱网环境、并发压力下依然可靠。我在实际项目中见过不少 AI 生成的模块主要问题集中在几个地方缺少参数校验异常输入直接导致空指针或数据库异常。没有设置超时时间上游服务变慢时整个线程池被拖垮。打印日志不规范要么完全没有日志要么用System.out.println输出大量无结构信息。对第三方依赖异常没有兜底策略一个外部接口抖动就导致核心链路失败。这些问题在开发阶段很难察觉因为开发阶段的数据量小、调用方固定、网络环境正常。但一旦进入生产环境流量放大、场景增多任何边界问题都会被放大。此时如果没有可观测性排查问题就像在黑屋子里找一把丢失的钥匙。2.2 AI 代码的不确定性需要观测兜底传统手工编写的代码开发者对每一行的作用都有清晰的认知。AI 生成的代码则不同它可能保留了训练数据中的某些模式而这些模式并不一定适合当前项目。例如AI 可能生成一段类似下面这样的代码// 伪代码展示 AI 生成代码中常见的“隐藏逻辑” public int calculateTotal(ListItem items) { int sum 0; for (Item item : items) { sum item.getPrice() * item.getCount(); } return sum; }这段代码看起来没问题但实际可能隐藏几个问题item.getPrice()可能为 null导致空指针。item.getCount()可能为 0 或负数业务上是否允许总金额超过int上限怎么办中间计算是否需要记录日志和指标只有把这些观测点补上才能在运行时发现“价格为空”“数量异常”“溢出风险”等潜在问题。可观测性在这里的作用并不仅仅是监控系统健康更是为 AI 生成代码提供“运行时校验反馈”把模型不知道的业务约束和边界条件暴露出来。2.3 可观测性把“无法解释”变成“可追溯”AI 生成代码后经常出现一种情况系统一旦出问题开发者很难解释“代码为什么这样写”。这种“无法解释”在生产环境是致命的。Observability 解决的就是这个问题。通过日志、指标和链路追踪系统运行时产生的每一个关键行为都有记录每一个异常都有上下文每一次调用都有完整链路。当问题发生时开发者不再需要凭借直觉猜测而是可以直接从观测数据中还原完整的事故现场。3. 可观测性三大支柱在 AI 工程中的落地3.1 Logs给 AI 生成代码装上动作记录仪日志是最基础、也是最重要的观测手段。对于 AI 生成代码日志的主要价值不只是“记录发生了什么”而是让开发者能够回溯 AI 生成代码的具体行为路径。在设计日志时要注意几个原则结构化使用 JSON 或键值对格式输出而不是纯文本拼接。带上下文日志中要包含请求 ID、用户 ID、订单 ID 等业务标识。分级别debug记录详细过程info记录关键业务动作warn记录潜在风险error记录异常。避免敏感信息不能将密码、Token、身份证号等敏感信息直接写入日志。对于 AI 生成代码尤其要检查它是否“只在出错时打印日志”。正确的方式是关键业务流程的入口和出口都要有日志而不是只有异常分支。3.2 Metrics用指标衡量 AI 代码的健康度指标是另一个关键维度。和日志不同指标是聚合的、数值化的适合用来发现趋势和异常。常见指标包括接口调用次数QPS接口响应时间P50、P95、P99错误率业务指标如订单创建成功数、支付失败数资源指标如线程池活跃线程数、数据库连接池使用率对于 AI 生成代码指标的作用尤其明显。因为 AI 生成的代码往往没有遵循项目的埋点规范可能在业务逻辑上完全是正确的但因为性能瓶颈比如在循环中查询数据库导致接口响应越来越慢。没有指标这种性能劣化很难被及时发现。实际项目中我会用 Micrometer 或 OpenTelemetry Metric API 为关键业务方法埋点然后接入 Prometheus Grafana 做可视化展示。这样每次发布 AI 生成代码后只需要对比发布前后的指标曲线就能快速判断是否存在性能回退。3.3 Traces追踪一次 AI Agent 调用的完整链路在现代微服务架构中一个用户请求往往会经过多个服务。AI 生成的代码如果真的嵌入核心链路就必须考虑链路追踪的问题——否则定位问题就像大海捞针。链路追踪的核心是 Trace ID 和 Span。一个 Trace 代表一次完整的请求Span 代表链路中的一个步骤。每个服务在接收入口请求时生成或传递 Trace ID在处理过程中创建 Span记录当前步骤的耗时和状态处理完成后把数据上报到链路系统。对于 AI 场景不只是微服务调用需要追踪。如果我们构建了一个 AI Agent智能体一次用户提问可能会触发多次模型调用、多次工具调用、多次外部 API 请求这些调用的先后顺序、耗时、结果都需要通过 Trace 串联起来否则很难解释“为什么这次回答这么慢”或者“为什么答案不正确”。4. 实战场景为一个 AI 辅助生成的订单服务补全可观测性4.1 案例背景与初始代码假设我们通过 AI 工具快速生成了一个订单创建服务代码如下这是简化后的演示示例// 文件路径src/main/java/com/example/order/OrderController.java package com.example.order; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/order) public class OrderController { private final OrderService orderService; public OrderController(OrderService orderService) { this.orderService orderService; } PostMapping public Order createOrder(RequestBody OrderRequest request) { return orderService.createOrder(request); } }// 文件路径src/main/java/com/example/order/OrderService.java package com.example.order; import org.springframework.stereotype.Service; Service public class OrderService { private final OrderRepository orderRepository; public OrderService(OrderRepository orderRepository) { this.orderRepository orderRepository; } public Order createOrder(OrderRequest request) { int total request.getItems().stream() .mapToInt(item - item.getPrice() * item.getQuantity()) .sum(); Order order new Order(); order.setUserId(request.getUserId()); order.setTotal(total); order.setStatus(CREATED); return orderRepository.save(order); } }这段代码是典型的 AI 生成风格主流程清晰、能运行但缺少观测能力。下面我们逐步补全。4.2 第一步补全结构化日志在补全日志之前先引入日志门面。推荐使用 SLF4J LogbackSpring Boot 默认已经包含了这部分依赖。改进后的代码会注意几个细节使用log.info/log.error替代System.out.println。日志中包含请求 ID方便后续串起整个调用流程。在方法入口和出口都打日志记录关键参数和结果。// 文件路径src/main/java/com/example/order/OrderService.java package com.example.order; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; Service public class OrderService { private static final Logger log LoggerFactory.getLogger(OrderService.class); private final OrderRepository orderRepository; public OrderService(OrderRepository orderRepository) { this.orderRepository orderRepository; } public Order createOrder(OrderRequest request) { String requestId generateRequestId(); log.info(开始创建订单, requestId{}, userId{}, itemCount{}, requestId, request.getUserId(), request.getItems().size()); try { int total request.getItems().stream() .mapToInt(item - item.getPrice() * item.getQuantity()) .sum(); log.info(订单金额计算完成, requestId{}, total{}, requestId, total); Order order new Order(); order.setUserId(request.getUserId()); order.setTotal(total); order.setStatus(CREATED); Order saved orderRepository.save(order); log.info(订单创建成功, requestId{}, orderId{}, total{}, requestId, saved.getId(), total); return saved; } catch (Exception ex) { log.error(订单创建失败, requestId{}, userId{}, errorMessage{}, requestId, request.getUserId(), ex.getMessage(), ex); throw ex; } } private String generateRequestId() { // 实际上可以由网关生成并透传这里做最简单的演示 return java.util.UUID.randomUUID().toString().replace(-, ); } }这里的关键不是“打印了几行日志”而是让日志具备可检索的维度。有了requestId和userId线上查问题时可以用一个请求 ID 串起所有相关日志快速还原整个调用过程。4.3 第二步接入 Prometheus 指标接下来为订单服务添加指标。这里使用 Micrometer 作为指标门面Prometheus 作为后端存储Grafana 做可视化。首先在pom.xml中加入依赖版本号请根据项目实际情况选择dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency然后在 Spring Boot 配置文件中启用 Prometheus 端点management.endpoints.web.exposure.includehealth,info,prometheus management.health.livenessstate.enabledtrue management.health.readinessstate.enabledtrue在代码中注入MeterRegistry并为订单创建接口添加计数器、耗时统计// 文件路径src/main/java/com/example/order/OrderService.java package com.example.order; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.time.Duration; import java.util.concurrent.TimeUnit; Service public class OrderService { private static final Logger log LoggerFactory.getLogger(OrderService.class); private final OrderRepository orderRepository; private final Counter orderCreatedCounter; private final Counter orderFailedCounter; private final Timer orderCreateTimer; public OrderService(OrderRepository orderRepository, MeterRegistry meterRegistry) { this.orderRepository orderRepository; this.orderCreatedCounter meterRegistry.counter(order.created.total, result, success); this.orderFailedCounter meterRegistry.counter(order.created.total, result, failure); this.orderCreateTimer meterRegistry.timer(order.create.duration); } public Order createOrder(OrderRequest request) { String requestId generateRequestId(); long start System.nanoTime(); log.info(开始创建订单, requestId{}, userId{}, itemCount{}, requestId, request.getUserId(), request.getItems().size()); try { int total request.getItems().stream() .mapToInt(item - item.getPrice() * item.getQuantity()) .sum(); log.info(订单金额计算完成, requestId{}, total{}, requestId, total); Order order new Order(); order.setUserId(request.getUserId()); order.setTotal(total); order.setStatus(CREATED); Order saved orderRepository.save(order); orderCreatedCounter.increment(); log.info(订单创建成功, requestId{}, orderId{}, total{}, requestId, saved.getId(), total); return saved; } catch (Exception ex) { orderFailedCounter.increment(); log.error(订单创建失败, requestId{}, userId{}, errorMessage{}, requestId, request.getUserId(), ex.getMessage(), ex); throw ex; } finally { long cost System.nanoTime() - start; orderCreateTimer.record(Duration.ofNanos(cost)); } } private String generateRequestId() { return java.util.UUID.randomUUID().toString().replace(-, ); } }启动服务后访问/actuator/prometheus就能看到类似下面的指标数据order_created_total{resultsuccess,} 1.0 order_create_duration_seconds_count 1.0 order_create_duration_seconds_max 0.123在 Grafana 中配置 Prometheus 数据源后可以绘制订单创建量、接口耗时的趋势图。这些指标的价值在于即使代码是 AI 生成的只要埋点规范统一线上表现和代码改动之间的因果关系仍然可以被量化。4.4 第三步接入 OpenTelemetry 链路追踪链路追踪的核心是让整个调用链被打上统一的 Trace ID。在 Spring Boot 项目中可以使用 OpenTelemetry 提供的自动埋点能力然后在业务代码中通过WithSpan注解标记关键方法。先添加依赖dependency groupIdio.opentelemetry.instrumentation/groupId artifactIdopentelemetry-spring-boot-starter/artifactId /dependency在application.properties中配置导出器地址这里以本地 Jaeger 或 Collector 为例otel.exporter.otlp.endpointhttp://localhost:4317 otel.metrics.exporternone otel.logs.exporternone otel.traces.exporterotlp然后在核心方法上添加WithSpan注解并为 Span 添加属性// 文件路径src/main/java/com/example/order/OrderService.java package com.example.order; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import io.opentelemetry.instrumentation.annotations.SpanAttribute; import io.opentelemetry.instrumentation.annotations.WithSpan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.time.Duration; Service public class OrderService { private static final Logger log LoggerFactory.getLogger(OrderService.class); private final OrderRepository orderRepository; private final Counter orderCreatedCounter; private final Counter orderFailedCounter; private final Timer orderCreateTimer; public OrderService(OrderRepository orderRepository, MeterRegistry meterRegistry) { this.orderRepository orderRepository; this.orderCreatedCounter meterRegistry.counter(order.created.total, result, success); this.orderFailedCounter meterRegistry.counter(order.created.total, result, failure); this.orderCreateTimer meterRegistry.timer(order.create.duration); } WithSpan(createOrder) public Order createOrder(SpanAttribute(order.requestId) String requestId, SpanAttribute(order.userId) String userId, SpanAttribute(order.itemCount) int itemCount) { // 实际业务逻辑与前面的示例类似这里省略重复代码 return null; } }如果一次请求经过了多个服务OpenTelemetry 会自动传递 Trace ID在 Jaeger 或 SkyWalking 中可以查看到完整的调用拓扑。对于 AI 生成代码的模块这类链路数据更是评估其真实运行表现的重要依据。需要在配置中注意OpenTelemetry 的结构化日志和链路 ID 关联需要额外配置。常见的做法是通过日志框架的 MDCMapped Diagnostic Context自动注入traceId和spanId这样日志和链路追踪可以相互交叉查询。4.5 运行与验证按照上面的步骤改造完成后我们可以进行如下验证启动 Spring Boot 应用。调用/api/order接口构造一个正常请求和一个异常请求。查看控制台日志确认请求 ID 是否串联、异常信息是否完整。访问/actuator/prometheus确认order_created_total和order_create_duration指标有数据。打开 Jaeger 或其他链路追踪平台查看createOrderSpan 的耗时和属性。完成这些验证后原本“黑盒”的 AI 生成代码就变成了“白盒”日志可查、指标可见、链路可追踪。后续即使这个模块再次被 AI 重构只要观测能力没有被破坏系统仍然处于可控状态。5. 更进一步Agent 与 LLM 调用的可观测性5.1 传统 APM 不够用了吗在 LLM 应用和 AI Agent 场景中传统 APM应用性能监控虽然能看到服务层面的调用关系却看不到模型层面的关键信息。例如一次对话调用了哪个模型、哪个版本输入了多少 Token、输出了多少 Token、消耗了多少成本模型响应耗时多少、是否有重试是否触发了某些工具调用工具调用是否成功用户的反馈是否被记录能否作为后续评估的数据这些信息已经超出了普通 APM 的范畴需要一个面向 LLM 场景的可观测性体系。5.2 记录模型调用的关键字段在业务代码中我通常会对模型调用增加以下观测字段字段说明model_name模型名称如 claude-3.5-sonnetmodel_version模型版本便于回滚对比prompt_tokens输入 Token 数completion_tokens输出 Token 数total_tokens总 Token 数latency_ms调用耗时retry_count重试次数tool_name触发的工具名称response_code业务返回码在这些字段的基础上可以进一步统计每日 Token 消耗成本、各模型调用量、Prompt 长度分布、超时才分布等指标。这些指标不仅是运维依据更是产品优化的数据基础。5.3 示例Token 用量与成本观测下面是一个使用 OpenTelemetry 为模型调用添加观测记录的示例思路// 文件路径src/main/java/com/example/llm/LlmClient.java package com.example.llm; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; import org.springframework.stereotype.Component; Component public class LlmClient { private final Tracer tracer; public LlmClient(Tracer tracer) { this.tracer tracer; } public String callModel(String prompt) { Span span tracer.spanBuilder(llm.call).startSpan(); span.setAttribute(llm.model_name, demo-model); span.setAttribute(llm.prompt_tokens, estimateTokens(prompt)); long start System.currentTimeMillis(); try (var ignored span.makeCurrent()) { String result doActualCall(prompt); span.setAttribute(llm.completion_tokens, estimateTokens(result)); span.setAttribute(llm.latency_ms, System.currentTimeMillis() - start); return result; } catch (Exception ex) { span.recordException(ex); span.setAttribute(llm.error, ex.getMessage()); throw ex; } finally { span.end(); } } private int estimateTokens(String text) { // 这里只是示例实际项目中建议结合 tokenizer 计算 return text.length() / 2; } private String doActualCall(String prompt) { // 实际调用大模型接口的逻辑 return demo response; } }这段代码的核心思路是把模型调用作为一个独立 Span 记录包含模型名、Token 量、耗时和错误信息。后续通过链路追踪和指标系统可以清楚看到哪种 Prompt 模式消耗最大、哪个模型出错率最高。6. 常见问题与排查思路问题现象常见原因解决思路AI 生成代码没有日志线上问题无从排查生成时未要求日志或开发者未检查建立代码审查清单要求关键链路必须有结构化日志日志乱码或无结构难以检索使用System.out.println或纯文本拼接统一使用 SLF4J日志输出设置为 JSON 格式指标全部为 0 或无数据缺少 Micrometer/Prometheus 依赖或配置错误检查依赖是否引入、端点是否开放、配置是否正确链路追踪看不到完整调用链Trace ID 没有在服务间传递检查 OpenTelemetry 自动配置是否正确确认请求头传递规则AI 生成的代码存在空指针但不清楚具体输入缺少参数校验和日志上下文在入口封装参数校验日志中记录关键入参模型调用耗时较长但无法定位没有为模型调用独立建 Span为 LLM 调用增加独立 Span记录耗时和相关属性发布 AI 生成代码后接口变慢可能存在循环查询、N1 问题对比发布前后指标曲线定位慢在哪个 Span日志中出现大量相同异常AI 生成代码可能捕获异常后未正确抛出审查异常处理逻辑避免吞异常在实际项目中最常见的坑并不是“不会配置可观测性”而是“AI 生成代码后开发者跳过了可观测性补全这一步”。很多开发者觉得“代码已经能跑再加日志和指标太麻烦”而这一步恰恰是保证代码能长期稳定运行的关键。7. 最佳实践与工程建议7.1 从生成代码一开始就加入观测要求在使用 AI 工具生成代码时可以在 Prompt 中直接提出要求。例如“请为这段代码添加结构化日志包含请求 ID 和业务标识。”“请为这个方法添加 Micrometer 指标统计调用次数和耗时。”“请在异常处理中记录原始异常堆栈不要吞异常。”让 AI 在生成阶段就输出带观测能力的代码远优于生成后再手工补全。这样不仅能提高效率也能减少“代码与观测逻辑不一致”的问题。7.2 建立日志、指标、链路三者的统一规范团队内部应形成一套统一的观测规范日志格式统一为 JSON包含timestamp、level、traceId、spanId、service、message。指标命名遵循统一规范例如模块.功能.动作避免命名混乱。每个外部依赖调用都必须有独立 Span便于查看依赖对整体耗时的影响。关键业务动作的日志和指标要能互相引用避免“日志能查但指标查不到”的情况。7.3 对 AI 生成代码做“观测审查”AI 生成代码后建议在 Code Review 阶段增加一个特殊的审查项检查这段代码是否具备基本的可观测性。主要看三点是否有日志日志是否包含能定位问题的业务上下文是否有指标关键路径是否埋点是否有链路追踪跨服务调用是否传递 Trace ID如果 AI 生成的代码三项基础能力都具备通常可以认为它已经达到可上线的工程底线。7.4 用指标驱动 AI 代码的迭代可观测性不仅用于排查问题还可以用来评估 AI 生成代码的表现。通过对比指标可以回答很多问题AI 重构后的模块响应时间是否下降新的异常处理策略错误率是否下降哪个 AI 生成的模块线上问题最多是否需要人工重写这种“用数据评估代码质量”的方式正是从 Vibe Coding 走向 AI Engineering 的核心体现。8. 总结与学习路线Vibe Coding 提高了代码产出的速度但速度必须建立在可控的基础上。Observability 正是让 AI 生成代码从“能跑”走向“可上线”的关键能力通过日志记录行为通过指标量化健康通过链路追踪还原调用关系。把这三件事做扎实AI 生成的代码才能真正融入业务系统而不是成为一个随时可能出问题的黑盒。如果你想继续深入这个方向下一步可以重点关注OpenTelemetry 的自动埋点和手动埋点机制。Prometheus Grafana 的指标设计和告警规则配置。结构化日志与日志采集平台的对接。LLM 场景下的 Token 成本观测与模型质量评估。在实际项目中优先把“日志、指标、链路”三件套补齐再逐步加入告警、审计、成本分析等高阶能力。无论是自己练习还是团队落地这套方法都值得认真试一试。