4、日志级别实战:如何根据业务场景选择合适的日志级别?一个电商系统的日志级别设计案例。

聊完了理论,咱们得动真格的了。

很多新手会问:「我到底该用 INFO 还是 DEBUG?WARN 和 ERROR 又怎么分?」

说实话,我刚开始也纠结过。后来带团队做电商系统,踩了不少坑,才慢慢摸清门道。

今天我就拿一个真实的电商系统来拆解。你想想看,一个电商系统,从用户打开 APP 到收到货,中间有多少环节?每个环节该用什么级别?

4.1 电商系统的核心业务流程

我们先画个简图。一个典型的电商交易链路是这样的:

  1. 用户浏览商品(搜索、查看详情)
  2. 加购(加入购物车)
  3. 下单(创建订单)
  4. 支付(调用支付网关)
  5. 库存扣减(锁定库存)
  6. 发货(物流对接)

每个环节的「重要性」和「异常影响」完全不同。所以日志级别不能一刀切。

核心原则:日志级别 = 业务影响程度 + 排查难度。

影响越大、越难排查的环节,级别越高。

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:需要人工立即处理的异常,比如支付失败、数据库连接断开

记住一句话:日志是给未来的自己看的。你希望未来的你看到什么?是清晰的问题线索,还是一堆无用的噪音?

想清楚这个,级别自然就选对了。