Python日志先定字段

一次线上接口抖动,报警先响的是接口超时。值班同学打开日志,看到一屏一屏的“处理失败”“调用异常”“retry error”。这些词都像线索,但真正排查时几乎用不上:没有请求 id,没有用户类型,没有接口耗时,没有下游返回码,也看不出失败发生在参数校验、数据库查询还是第三方调用。最后团队是靠临时加日志、重新发版、等问题再次出现才定位到某个下游接口偶发超时。
这类事故在 Python 项目里很常见。Python 写日志太方便了,print 一行、logger.info 一句,短期看已经“有记录”。可日志不是给当前开发自我安慰用的,它是给下一次线上排查留证据。证据如果没有结构,排查就只能靠猜;证据如果没有边界,又会把隐私、成本和噪声一起带进系统。
我更建议先把日志字段定清楚,再谈日志级别和格式。字段不是越多越好,而是要回答排查问题:这次请求是谁触发,走了哪条业务路径,失败在哪个阶段,影响了哪个对象,下游返回了什么,下一步该重试、降级还是报警。字段定对了,日志量不一定变大,排查效率却会完全不同。
先别急着多打日志,先问要排什么
很多团队遇到排查困难,第一反应是“日志太少”。于是大家开始在关键代码里到处加 logger.info。一段时间后,日志确实多了,但排查不一定更快,因为它们没有围绕同一个问题组织。有人记录用户 id,有人记录订单号,有人记录异常消息,有人只写中文说明。日志看起来热闹,真正串链路时还是断的。
比较稳的做法是先列出最常见的排查问题。比如接口超时要看请求入口、下游耗时和重试次数;数据错误要看业务对象、状态变更前后值和操作者;权限异常要看用户身份、角色、资源和判断结果;任务失败要看任务 id、批次、分片、失败阶段。不同问题需要的字段不同,但它们都需要稳定的关联字段。
Python 项目里至少应该有几个基础字段:trace_id request_id,用于串一次请求;业务对象 id,例如 order_id、user_id、task_id;阶段字段,例如 validate、query、call_downstream、write_result;结果字段,例如 success、failed、timeout;耗时字段;异常类型和关键错误码。没有这些字段,日志再长也很难形成证据链。
日志排查路线:请求入口、字段、业务阶段、异常和看板要能串起来,否则每条日志都只是孤立句子。
还有一个容易被忽略的点是上下文传播。很多 Python 服务在入口处生成了 request_id,但进入 Celery 任务、线程池、异步回调或第三方客户端以后就丢了。日志字段一旦在跨边界时丢失,排查会从一条链路断成几段。比较稳的做法是在入口、中间件、任务投递和下游调用处都显式传递上下文,至少保证 request_id、业务对象 id stage 不会断。
如果项目里有异步任务,任务日志还应该包含触发来源。比如一条补偿任务到底来自用户请求、定时扫描还是人工重试,后续处理方式完全不同。没有来源字段,失败后只能看到任务本身失败,却不知道它为什么出现。日志字段设计到这里,就已经不是格式问题,而是系统边界问题。
结构化日志不是为了时髦,是为了少猜
很多人听到结构化日志,会觉得是平台化团队才需要的东西。其实结构化日志的核心很简单:把日志里真正要检索、聚合、关联的信息变成稳定字段,而不是藏在一句自然语言里。比如“用户 123 创建订单 456 失败”这句话,人能看懂,系统很难稳定统计;如果拆成 user_id、order_id、event、status、error_code,后面就能按字段过滤。
Python 里可以用标准 logging,也可以结合 JSON formatter 或日志采集侧做结构化。工具不是重点,关键是字段含义稳定。uiduserIduser_id 混着用,平台再好也会变成麻烦。建议团队先定一份最小字段约定,别一开始追求覆盖所有场景。
一个常见取舍是字段粒度。字段太少,排查时不够;字段太多,日志体积上升,隐私风险也增加。我的判断是:能帮助定位阶段、关联对象、判断失败类型的字段优先;只为了“可能以后有用”的字段先别加。比如完整请求体通常不该直接打,尤其涉及手机号、证件、token、支付信息。需要留证据时,可以记录白名单字段或摘要。
落到代码里,最怕的是字段约定只停在文档里。Python logging 支持通过 extra 带上结构化字段,团队可以先用一个薄封装把公共字段收进去,再让业务代码只补业务字段。这样做的好处不是代码多优雅,而是减少每个开发各写一套字段名的机会。
  1. logger.info(
  2. "order callback failed",
  3. extra={
  4. "request_id": request_id,
  5. "stage": "call_downstream",
  6. "order_id": order_id,
  7. "provider": "payment",
  8. "duration_ms": duration_ms,
  9. "error_code": error_code,
  10. },
  11. )
python
这段代码里,日志正文故意写得很短,真正用于检索和聚合的信息都放在字段里。后面如果要查某个支付通道的失败率、某个订单的处理路径、某个阶段的耗时分布,就不需要再从一句字符串里拆信息。更进一步,可以用中间件或 contextvars 在请求入口保存 request_id,让业务代码少传一个参数;但跨线程、跨任务、跨进程时仍然要显式确认上下文有没有被带过去。
日志级别要表达行动,不只是严重程度
infowarningerror 很容易被随手使用。有人觉得异常都打 error,有人觉得只要失败就 warning,有人把业务失败也当 error。结果是报警噪声越来越多,真正需要处理的问题反而被淹没。
我更倾向于让日志级别表达行动。info 记录正常路径里的关键证据,例如一次任务开始和结束;warning 记录可恢复但值得观察的问题,例如下游短暂超时后重试成功;error 留给不可恢复、影响用户或需要人工关注的问题。业务可预期失败不一定是 error,比如库存不足、权限不足、参数校验失败,如果它们是正常业务路径,就不应该把报警打爆。
异常日志还要保留异常类型和栈,但不要只剩栈。栈能告诉你代码在哪里失败,却不一定告诉你当时处理的是哪个业务对象。一个好用的异常日志应该同时有阶段、对象、下游、错误码和栈。这样排查时不用先复现才能知道上下文。
日志观察看板:错误数、耗时、缺字段和脱敏率要一起看。只看 error 数量,很容易忽略字段质量和隐私风险。
脱敏和成本不是上线后再补
日志最容易踩的坑之一,是为了排查方便把原始数据全打出来。短期确实好查,长期会变成风险。手机号、邮箱、身份证、token、地址、支付凭证都不应该随意进入日志。即使日志平台有权限控制,也不能把它当成兜底理由。
脱敏不是简单把所有字段打星。字段如果完全不可用,排查又会受影响。可以按用途设计:用户 id 可以保留内部 id,手机号保留后四位,token 只记录 hash,复杂请求体只记录字段存在性和关键枚举。这样既能定位问题,也不会把敏感信息扩散到日志系统。
成本也要提前考虑。结构化日志更好查,但字段变多、日志量变大,存储和检索成本都会上升。高频接口不能把每一次正常请求的完整上下文都打出来。可以把关键成功日志做采样,失败日志保留更多字段,核心链路单独提高保留时间。日志策略不是越全越好,而是让高价值证据留下来。
给团队一份能执行的字段清单
日志治理如果只停留在“大家以后注意”,很快会回到原样。更可执行的方式是给不同场景准备字段清单。接口请求一套,异步任务一套,第三方调用一套,状态变更一套。清单不需要很长,但要让开发知道最低要求是什么。
比如接口请求可以包含:request_id、path、method、user_id、client、stage、duration_ms、status、error_code。第三方调用可以包含:request_id、provider、api_name、duration_ms、http_status、biz_code、retry_count。异步任务可以包含:task_id、batch_id、shard、stage、attempt、duration_ms、result。字段清单越具体,代码评审时越容易发现缺口。
字段取舍矩阵:定位价值、隐私风险和采集成本要一起看。不是所有看起来有用的信息都应该进入日志。
日志治理也需要验收。可以挑一条真实链路做演练:假设下游超时、参数异常、数据库写失败、用户权限不足,分别看日志能否在三分钟内回答“影响谁、卡在哪、下一步做什么”。如果回答不了,就不是值班同学不熟,而是字段没给够。
我通常会给日志加一个反向指标:字段缺失率。比如 error 日志里有多少缺 request_id,第三方调用日志里有多少缺 provider,任务失败日志里有多少缺 task_id。这个指标比“日志数量”更能说明治理效果。日志多不等于证据多,字段稳定才是证据稳定。
一次更稳的落地顺序
如果现在要改一个 Python 项目的日志,我不会先全量替换日志框架。那样动作太大,也容易让业务开发抵触。更稳的顺序是先选一条真实痛点链路,比如下单、登录、支付回调、批处理任务,把这条链路里的字段和阶段补齐。
可以按这个顺序推进:
选一类真实事故,写清楚排查时缺了哪些证据。
为这类场景定义最小字段清单,不超过十几个核心字段。
在入口层生成 request_id,并保证下游日志能带上。
给关键业务阶段加 stage 字段,不要只写自然语言描述。
区分业务失败和系统异常,避免把正常业务结果打成 error。
对敏感字段做白名单和脱敏,别依赖事后清理。
在日志平台做一个小看板,观察字段缺失率、错误分布和尾部耗时。
最后判断日志是否变好了,不要问“日志是不是更多了”,而要问下一次出问题时能不能少发一次版、少拉一个人、少猜一个阶段。如果日志能让团队更快确认影响范围、失败阶段和下一步动作,它就不是额外成本,而是线上系统的一部分。