有次运营报"生图很慢",我去查日志,看到的是一堆这样的东西:
生成中...
生成完成
生成失败
没有 traceId,没有耗时,没有参数。我连"哪一次慢"都定位不了,只能凭感觉去猜。
日志里至少要有什么
后来我重新定了一版最小字段集,每次调用记两条------请求前一条、响应后一条:
| 字段 | 为什么要 |
|---|---|
traceId |
把请求和响应串起来,也能和上游业务日志对上 |
model |
排查"是不是某个模型特别慢" |
promptLen |
提示词长度和耗时有相关性 |
aspect / size |
尺寸直接影响耗时 |
costMs |
核心指标 |
ok / errCode |
成败和错误码 |
retryCount |
这次是第几次尝试 |
实现
const { randomUUID } = require('crypto');
async function genImage(params, ctx = {}) {
const traceId = ctx.traceId || randomUUID();
const t0 = Date.now();
const base = {
traceId,
model: params.model,
promptLen: (params.prompt || '').length,
aspect: params.aspect_ratio,
size: params.image_size,
};
log.info({ ...base, event: 'gen_start' });
try {
const r = await callGenApi(params);
log.info({ ...base, event: 'gen_done', ok: true, costMs: Date.now() - t0 });
return r;
} catch (e) {
log.error({
...base, event: 'gen_done', ok: false,
costMs: Date.now() - t0,
errCode: e.code || 'unknown',
errMsg: String(e.message).slice(0, 200),
});
throw e;
}
}
traceId 要从上游传下来,别在这一层新生成。用户点一次按钮可能触发多次生成(多图、重试),只有共用同一个 traceId 才能把它们归到一起看。
结构化,别拼字符串
// ✗ 后面没法查
console.log(`生成完成,耗时 ${cost}ms`);
// ✓ 能按字段过滤和聚合
log.info({ event: 'gen_done', costMs: cost, model, traceId });
拼成一句话的日志,人能看,机器不能。改成 JSON 之后,"过去一小时 nano-banana-pro 的 p95 耗时"这种问题,一条查询就出来了。
三个真正有用的聚合
日志有了之后,我固定看这三个:
一、按模型分组的耗时分布。 看 p50 和 p95,别看平均值------生图的耗时分布很长尾,平均值会把偶发的超长请求抹平。
二、按错误码分组的失败数。 参数错误、鉴权失败、限流,是完全不同的处理方式。混成一个"失败率"没有指导意义。
三、重试率。 这个指标最容易被忽略。重试率突然上升往往是最早的信号,比失败率更灵敏------因为重试成功的那些,在失败率上根本看不出来。
一条经验
排查生图问题时,提示词长度是个常被忽略的变量。
我们有次整体变慢,查了半天网络和并发,最后发现是运营换了一版提示词模板,长度翻了三倍。日志里有 promptLen 之后,这个关联一眼就能看出来。
该服务本身的响应挺稳定,我们遇到的问题绝大多数出在自己这边------参数传错、并发没控、提示词失控。有日志才知道该怪谁,没日志就只能互相猜。
接口服务:甜甜圈API(dashengfenshen.cn)