4、日志级别实战:如何根据业务场景选择合适的日志级别?一个电商系统的日志级别设计案例。
聊完了理论,咱们得动真格的了。
很多新手会问:「我到底该用 INFO 还是 DEBUG?WARN 和 ERROR 又怎么分?」
说实话,我刚开始也纠结过。后来带团队做电商系统,踩了不少坑,才慢慢摸清门道。
今天我就拿一个真实的电商系统来拆解。你想想看,一个电商系统,从用户打开 APP 到收到货,中间有多少环节?每个环节该用什么级别?
4.1 电商系统的核心业务流程
我们先画个简图。一个典型的电商交易链路是这样的:
- 用户浏览商品(搜索、查看详情)
- 加购(加入购物车)
- 下单(创建订单)
- 支付(调用支付网关)
- 库存扣减(锁定库存)
- 发货(物流对接)
每个环节的「重要性」和「异常影响」完全不同。所以日志级别不能一刀切。
核心原则:日志级别 = 业务影响程度 + 排查难度。
影响越大、越难排查的环节,级别越高。
4.2 各环节的日志级别设计
下面是我个人习惯的设计方案。注意,这不是标准答案,但我在多个项目中验证过,比较稳妥。
| 业务环节 | 推荐级别 | 日志内容示例 | 理由 |
|---|---|---|---|
| 商品浏览 | DEBUG | 「用户 1001 搜索关键词 'iPhone15',返回结果 20 条」 | 高频、无状态、不影响核心交易 |
| 加购 | INFO | 「用户 1001 将商品 2001 加入购物车,数量 1」 | 有状态变更,但可恢复 |
| 下单 | INFO + WARN | INFO: 「订单 3001 创建成功」 WARN: 「订单 3001 创建耗时超过 3 秒」 |
核心链路,需要监控性能 |
| 支付 | INFO + ERROR | INFO: 「订单 3001 支付成功,金额 5999」 ERROR: 「订单 3001 支付回调签名校验失败」 |
资金相关,必须精确记录 |
| 库存扣减 | WARN + ERROR | WARN: 「商品 2001 库存不足,当前库存 0,请求扣减 1」 ERROR: 「库存扣减事务回滚,订单 3001 取消」 |
并发竞争严重,需预警 |
| 发货 | INFO | 「订单 3001 已分配物流单号 SF123456」 | 异步操作,失败有重试机制 |
我的小技巧:对于「下单」和「支付」这种核心链路,我会额外加一个 METRIC 级别的日志(自定义级别),专门记录耗时和成功率。这样 Grafana 监控面板可以直接拉取,不用从 INFO 日志里硬筛。
4.3 避坑指南:我曾经踩过的三个坑
嗯,这里要注意。光知道「该用什么级别」还不够,有些坑你得提前知道。
坑一:把 DEBUG 日志打到了生产环境
我曾经接手过一个项目,生产环境的日志文件一天涨了 50GB。查了半天,发现是商品搜索模块把每次查询的 SQL 参数都打成了 INFO 级别。
结果呢?磁盘 IO 被打满,接口响应时间从 50ms 飙到了 2s。
教训:DEBUG 级别的日志,默认只在开发环境开启。生产环境除非排查特定问题,否则别开。
坑二:ERROR 日志里塞了太多「假错误」
什么叫假错误?比如用户输入了错误的验证码,你打了个 ERROR。再比如某个非核心接口超时了,你也打了个 ERROR。
后果是什么?监控系统天天报警,运维同学直接麻木了。等到真正的订单支付失败时,反而没人看了。
教训:ERROR 只留给「需要人工立即介入」的异常。像用户输入错误、非核心服务超时,用 WARN 就够了。
坑三:日志里打了敏感信息
这个我印象特别深。有一次排查支付问题,发现日志里居然打印了用户的银行卡号后四位和手机号。虽然脱敏了,但合规部门还是找上门了。
教训:任何涉及用户隐私、密码、Token 的信息,一律不要打。如果非要打,用 *** 替换掉中间部分。
4.4 代码示例:一个简单的日志级别配置
说了这么多,咱们看看代码。下面是一个 Python 电商系统的日志配置示例:
import logging
import json
# 自定义日志级别
METRIC = 25 # 介于 INFO(20) 和 WARN(30) 之间
logging.addLevelName(METRIC, "METRIC")
class EcommerceLogger:
def __init__(self, name):
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.DEBUG)
# 生产环境只输出 INFO 及以上
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)
formatter = logging.Formatter(
'%(asctime)s [%(levelname)s] %(message)s'
)
handler.setFormatter(formatter)
self.logger.addHandler(handler)
def log_order_created(self, order_id, user_id, amount):
# 下单成功:INFO
self.logger.info(
f"订单创建成功 | order_id={order_id} | user_id={user_id} | amount={amount}"
)
def log_payment_failed(self, order_id, reason):
# 支付失败:ERROR
self.logger.error(
f"支付失败 | order_id={order_id} | reason={reason}",
exc_info=True # 打印堆栈
)
def log_inventory_warning(self, sku_id, current_stock):
# 库存预警:WARN
self.logger.warning(
f"库存不足预警 | sku_id={sku_id} | current_stock={current_stock}"
)
def log_search_debug(self, keyword, results_count):
# 搜索调试:DEBUG(生产环境默认不输出)
self.logger.debug(
f"搜索查询 | keyword={keyword} | results_count={results_count}"
)
# 使用示例
logger = EcommerceLogger("order_service")
logger.log_order_created("3001", "1001", 5999)
logger.log_payment_failed("3001", "签名校验失败")
logger.log_inventory_warning("2001", 0)
注意:上面的 exc_info=True 只在 ERROR 级别使用。INFO 和 WARN 不要加,否则会额外生成堆栈信息,影响性能。
4.5 总结一下
说白了,日志级别的选择没有银弹。你得根据业务场景自己权衡。
我个人习惯是:
- DEBUG:开发调试用,生产环境关掉
- INFO:记录关键业务节点,方便追溯
- WARN:非致命但需要关注的情况,比如超时、库存预警
- ERROR:需要人工立即处理的异常,比如支付失败、数据库连接断开
记住一句话:日志是给未来的自己看的。你希望未来的你看到什么?是清晰的问题线索,还是一堆无用的噪音?
想清楚这个,级别自然就选对了。