工程化06:可观测性

从Prompt、上下文、模型、耗时、成本到失败原因,全面记录和分析AI应用的运行状态。

字数 1334 阅读时长 ≈ 4 分钟 2026-7-19 2026-7-27
工程化06:可观测性

AI 应用的可观测性比传统应用更复杂,需要记录 Prompt、上下文、模型响应、Token 消耗等特殊信息。有效的可观测性能帮助快速定位问题、优化性能、控制成本。

可观测性三要素

日志、指标、链路追踪

要素说明用途
日志记录详细事件问题排查、审计
指标聚合统计数据性能监控、告警
链路追踪追踪请求路径性能分析、依赖分析

AI 特有可观测数据

数据类型说明用途
Prompt用户输入问题复现、Prompt优化
Context上下文信息问题定位、上下文分析
Model Response模型输出质量分析、效果评估
Token UsageToken消耗成本监控、优化
Model Parameters模型参数版本追踪、效果对比

日志记录

日志内容设计

字段说明示例
request_id请求唯一标识UUID
timestamp请求时间ISO 8601
user_id用户标识用户ID
prompt用户输入原始输入文本
context上下文信息历史对话、参考文档
model使用的模型gpt-4o
model_params模型参数temperature: 0.7
response模型响应生成的文本
input_tokens输入Token数1000
output_tokens输出Token数500
latency响应延迟2500ms
error错误信息null或错误描述
status请求状态success/failed/timeout

日志实现示例

@Aspect
@Component
public class ModelLoggingAspect {
    private final Logger log = LoggerFactory.getLogger(ModelLoggingAspect.class);
    
    @Around("execution(* com.example.service.ModelService.generate(..))")
    public Object logModelRequest(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        String requestId = UUID.randomUUID().toString();
        
        Object[] args = joinPoint.getArgs();
        String prompt = args.length > 0 ? args[0].toString() : "";
        ModelConfig config = args.length > 1 ? (ModelConfig) args[1] : null;
        
        try {
            Object result = joinPoint.proceed();
            
            long latency = System.currentTimeMillis() - startTime;
            
            log.info("Model request completed", Map.of(
                "request_id", requestId,
                "prompt", truncate(prompt, 500),
                "model", config != null ? config.model() : "unknown",
                "response", truncate(result.toString(), 500),
                "latency_ms", latency,
                "status", "success"
            ));
            
            return result;
        } catch (Exception e) {
            long latency = System.currentTimeMillis() - startTime;
            
            log.error("Model request failed", Map.of(
                "request_id", requestId,
                "prompt", truncate(prompt, 500),
                "model", config != null ? config.model() : "unknown",
                "latency_ms", latency,
                "error", e.getMessage(),
                "status", "failed"
            ));
            
            throw e;
        }
    }
    
    private String truncate(String text, int maxLength) {
        return text != null && text.length() > maxLength 
            ? text.substring(0, maxLength) + "..." 
            : text;
    }
}

日志分级策略

级别说明使用场景
DEBUG详细调试信息开发调试
INFO正常运行信息请求完成、状态变更
WARN警告信息缓存失效、降级触发
ERROR错误信息请求失败、异常抛出
FATAL致命错误系统崩溃、严重故障

指标监控

核心监控指标

指标说明计算方式
请求量每秒处理请求数QPS
成功率请求成功比例success / total
延迟请求响应时间P50/P90/P99
错误率请求错误比例error / total
超时率请求超时比例timeout / total
输入Token每秒输入Token数sum(input_tokens)
输出Token每秒输出Token数sum(output_tokens)
成本每秒成本sum(cost)

指标实现示例

@Component
public class ModelMetricsService {
    private final Counter requestCounter = Counter.builder("model.request.count")
        .description("Total number of model requests")
        .register();
    
    private final Timer latencyTimer = Timer.builder("model.request.latency")
        .description("Request latency in milliseconds")
        .register();
    
    private final Counter tokenCounter = Counter.builder("model.token.consumption")
        .description("Token consumption by type")
        .tag("type", "input")
        .register();
    
    public void recordRequest(long latencyMs, boolean success, 
                              long inputTokens, long outputTokens) {
        requestCounter.increment();
        latencyTimer.record(latencyMs, TimeUnit.MILLISECONDS);
        
        if (success) {
            tokenCounter.tag("type", "input").increment(inputTokens);
            tokenCounter.tag("type", "output").increment(outputTokens);
        }
    }
    
    public void recordError() {
        Counter.builder("model.request.errors")
            .description("Number of failed requests")
            .register()
            .increment();
    }
}

告警规则

规则条件级别通知方式
错误率过高错误率 > 5% 持续 5 分钟严重即时通知
延迟过高P99 > 5s 持续 10 分钟警告延迟通知
成本超支日成本 > 预算的 80%警告延迟通知
Token 耗尽Token 余额 < 1000严重即时通知
请求量突增QPS > 基线 2 倍警告延迟通知

链路追踪

链路追踪设计

步骤说明示例
Trace ID请求唯一标识贯穿整个请求
Span单个操作模型调用、缓存查询
Parent Span父操作业务服务调用
Tags操作标签model=GPT-4, prompt_length=100
Logs操作日志开始时间、结束时间

链路追踪实现

@Component
public class ModelTracingService {
    private final Tracer tracer;
    
    public String generateWithTrace(String prompt, ModelConfig config) {
        Span span = tracer.spanBuilder("Model.generate")
            .setAttribute("prompt.length", prompt.length())
            .setAttribute("model", config.model())
            .setAttribute("temperature", config.temperature())
            .startSpan();
        
        try (Scope scope = span.makeCurrent()) {
            String response = modelService.generate(prompt, config);
            
            span.setAttribute("response.length", response.length());
            span.setAttribute("status", "success");
            
            return response;
        } catch (Exception e) {
            span.setAttribute("status", "failed");
            span.setAttribute("error", e.getMessage());
            throw e;
        } finally {
            span.end();
        }
    }
}

链路分析

分析维度说明用途
延迟分析各步骤延迟分布定位性能瓶颈
依赖分析调用关系分析优化依赖结构
错误分析错误分布分析定位错误原因
并发分析并发调用分析优化资源使用

成本监控

成本追踪指标

指标说明计算方式
总Token消耗输入+输出Tokensum(input + output)
日均Token消耗每日平均消耗total / days
单次请求成本每次请求平均成本total_cost / requests
模型成本分布各模型成本比例model_cost / total_cost
用户成本分布各用户成本比例user_cost / total_cost

成本监控实现

@Service
public class CostMonitoringService {
    private final AtomicLong dailyInputTokens = new AtomicLong(0);
    private final AtomicLong dailyOutputTokens = new AtomicLong(0);
    private final Map<String, Long> modelTokenUsage = new ConcurrentHashMap<>();
    
    public void recordUsage(String model, long inputTokens, long outputTokens) {
        dailyInputTokens.addAndGet(inputTokens);
        dailyOutputTokens.addAndGet(outputTokens);
        modelTokenUsage.merge(model, inputTokens + outputTokens, Long::sum);
    }
    
    public CostReport generateReport() {
        long totalInput = dailyInputTokens.get();
        long totalOutput = dailyOutputTokens.get();
        double cost = calculateCost(totalInput, totalOutput);
        
        return new CostReport(totalInput, totalOutput, cost, 
            new HashMap<>(modelTokenUsage));
    }
    
    private double calculateCost(long inputTokens, long outputTokens) {
        double inputCost = inputTokens * 0.0005 / 1000;
        double outputCost = outputTokens * 0.0015 / 1000;
        return inputCost + outputCost;
    }
}

record CostReport(long inputTokens, long outputTokens, 
                  double totalCost, Map<String, Long> modelUsage) {}

可观测性最佳实践

数据存储策略

数据类型存储方式保留时间
日志Elasticsearch30天
指标Prometheus15天
链路追踪Jaeger7天
成本数据数据库永久

监控仪表盘设计

面板内容说明
概览面板请求量、成功率、延迟、成本整体状态
性能面板P50/P90/P99延迟、吞吐量性能指标
错误面板错误率、错误类型分布错误分析
成本面板Token消耗、成本趋势、模型分布成本监控
链路面板请求链路、各步骤耗时链路分析

故障排查流程

1. 发现告警 → 2. 查询日志 → 3. 分析链路 → 4. 定位问题 → 5. 修复验证

常见问题与解决方案

问题1:日志过多

表现:日志量过大,存储成本高

解决方案

  • 分级记录日志
  • 压缩敏感信息
  • 设置日志保留时间
  • 采样记录

问题2:指标不全

表现:缺少关键监控指标

解决方案

  • 识别关键业务指标
  • 添加AI特有指标
  • 设置合理的告警规则

问题3:链路追踪缺失

表现:无法追踪请求完整路径

解决方案

  • 添加Trace ID
  • 记录关键Span
  • 整合分布式追踪

问题4:成本监控缺失

表现:无法准确了解成本分布

解决方案

  • 记录Token消耗
  • 计算成本
  • 设置成本预算和告警

项目判断清单

  • 需要问题排查 → 完善日志记录
  • 需要性能监控 → 设置核心指标
  • 需要追踪请求路径 → 实现链路追踪
  • 需要控制成本 → 建立成本监控
  • 需要快速定位问题 → 设置告警规则
  • 需要全面分析 → 设计监控仪表盘
  • 需要历史分析 → 合理存储数据
  • 需要持续改进 → 建立可观测性体系