4、日志级别管理:DEBUG、INFO、WARNING、ERROR、CRITICAL级别定义与使用场景
日志级别这东西,说白了就是给日志信息贴个标签。你想想看,系统跑起来每天产生几万条日志,要是没个级别区分,排查问题就像大海捞针。我刚开始做运维那会儿,就吃过这个亏——日志全打在一个文件里,出问题的时候翻得眼都快瞎了。
Python 的 logging 模块内置了五个标准级别。嗯,这里要注意,它们是有优先级的。从低到高分别是:DEBUG、INFO、WARNING、ERROR、CRITICAL。每个级别代表的信息严重程度不一样,使用场景也完全不同。
核心原则:生产环境不要开 DEBUG,开发环境不要关 ERROR。这个原则我踩过坑才真正理解。
4.1 五个级别的定义与数值
每个级别其实对应一个数字,Python 内部就是靠这个数字来比较优先级。数值越小,级别越低。
| 级别 | 数值 | 什么时候用 |
|---|---|---|
| DEBUG | 10 | 开发调试时,打印变量值、函数入口出口 |
| INFO | 20 | 记录系统正常运行的关键节点信息 |
| WARNING | 30 | 系统还能跑,但有点不对劲,需要关注 |
| ERROR | 40 | 某个功能出错了,但系统整体没挂 |
| CRITICAL | 50 | 系统要挂了,必须立刻处理 |
我个人习惯把 DEBUG 和 INFO 归为「开发级」,WARNING 及以上归为「运维级」。这样分的好处是,线上只看 WARNING 以上的日志,开发环境才看全部。
4.2 DEBUG 级别:开发者的放大镜
DEBUG 级别的日志,说白了就是给开发者自己看的。我在项目中遇到过好几次,线上出了问题,结果发现 DEBUG 日志全开着,磁盘 IO 直接被打满。所以生产环境一定要关掉 DEBUG。
什么时候用 DEBUG?
- 函数调用时的入参和返回值
- 循环中的中间变量
- 条件分支的走向判断
- 第三方接口的原始返回数据
import logging
logger = logging.getLogger(__name__)
def calculate_discount(price, user_level):
logger.debug(f"入参: price={price}, user_level={user_level}")
if user_level == 'vip':
discount = 0.8
logger.debug(f"VIP用户,折扣={discount}")
else:
discount = 1.0
result = price * discount
logger.debug(f"计算结果: {result}")
return result
我的小技巧:DEBUG 日志里不要打印密码、token 等敏感信息。我曾经见过同事把数据库密码打到了 DEBUG 日志里,结果日志文件被误传到 GitHub...嗯,那场面很尴尬。
4.3 INFO 级别:系统的脉搏
INFO 日志记录的是「系统正常运行时,我想知道的信息」。比如服务启动成功、用户登录、订单创建成功这些。它不表示有问题,只是告诉你系统在正常工作。
我个人习惯把 INFO 日志当作系统的「关键节点标记」。比如一个支付流程,我会在「收到请求」「校验通过」「扣款成功」「返回结果」这四个节点各打一条 INFO 日志。这样出了问题,一看日志就知道卡在哪一步。
def process_payment(user_id, amount):
logger.info(f"收到支付请求: user={user_id}, amount={amount}")
if not validate_user(user_id):
logger.error(f"用户校验失败: {user_id}")
return False
logger.info(f"用户校验通过: {user_id}")
result = deduct_balance(user_id, amount)
if result:
logger.info(f"扣款成功: user={user_id}, amount={amount}")
else:
logger.error(f"扣款失败: user={user_id}, amount={amount}")
return result
注意:INFO 日志不要打得太频繁。比如循环里每处理一条记录就打一条 INFO,那日志量会爆炸。循环里的细节用 DEBUG,循环外的统计用 INFO。
4.4 WARNING 级别:预警信号
WARNING 表示「系统还能跑,但有点不对劲」。它不像 ERROR 那样需要立即处理,但也不能忽视。我建议运维同学每天花 10 分钟扫一遍 WARNING 日志,很多大问题其实是从小警告开始的。
常见的使用场景:
- 磁盘使用率超过 80%
- 接口响应时间超过阈值但还没超时
- 重试机制被触发
- 使用了即将废弃的 API
- 配置项使用了默认值
def check_disk_usage():
usage = get_disk_usage('/data')
if usage > 90:
logger.critical(f"磁盘使用率超过90%: {usage}%")
elif usage > 80:
logger.warning(f"磁盘使用率超过80%: {usage}%")
else:
logger.info(f"磁盘使用率正常: {usage}%")
4.5 ERROR 级别:出事了,但别慌
ERROR 表示某个功能出错了,但系统整体还在运行。比如用户请求超时、数据库连接失败、第三方接口返回错误码。这时候需要记录详细的错误信息,方便排查。
我曾经犯过一个错误:在 except 块里只打了 ERROR 日志,没打印异常堆栈。结果线上出了问题,日志里只有一句「数据库连接失败」,完全不知道是哪行代码、什么上下文导致的。从那以后,我所有的 ERROR 日志都会带上异常信息。
try:
result = db.query(sql)
except Exception as e:
logger.error(f"数据库查询失败: {sql}", exc_info=True)
# exc_info=True 会打印完整的异常堆栈
return None
关键点:ERROR 日志一定要包含足够的信息来复现问题。至少要有:错误描述、异常堆栈、关键上下文(如用户 ID、请求参数)。
4.6 CRITICAL 级别:系统要挂了
CRITICAL 是最高级别,表示系统已经无法正常工作了。比如数据库完全不可用、内存溢出、关键服务宕机。这时候不光要记录日志,通常还要触发告警——发短信、打电话、钉钉群艾特所有人。
我个人习惯把 CRITICAL 和告警系统绑定。一旦出现 CRITICAL 日志,自动触发电话告警。因为这种级别的问题,晚处理一分钟可能就损失惨重。
def health_check():
if not check_database():
logger.critical("数据库完全不可用,系统无法提供服务")
alert_ops_team("数据库宕机", level="critical")
return False
if not check_cache():
logger.warning("缓存服务不可用,系统降级运行")
return True
logger.info("系统健康检查通过")
return True
4.7 级别过滤与动态调整
实际项目中,我们不会把所有级别的日志都输出到同一个地方。常见的做法是:
- 文件日志:记录 INFO 及以上级别
- 控制台日志:开发时用 DEBUG,线上用 WARNING
- 告警系统:只接收 ERROR 和 CRITICAL
而且,日志级别应该是可以动态调整的。我参与过的一个项目,线上突然出现诡异问题,我们通过运维后台把某个模块的日志级别临时调成了 DEBUG,等定位到问题后再改回去。整个过程不需要重启服务,非常方便。
import logging
# 设置不同 handler 的不同级别
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.INFO)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# 告警 handler 只处理 ERROR 及以上
alert_handler = AlertHandler()
alert_handler.setLevel(logging.ERROR)
logger = logging.getLogger('my_app')
logger.addHandler(file_handler)
logger.addHandler(console_handler)
logger.addHandler(alert_handler)
logger.setLevel(logging.DEBUG) # 全局最低级别
4.8 知识体系图
下面这张图展示了日志级别的核心逻辑和流转关系。你可以看到不同级别对应不同的处理方式和输出目标。
4.9 避坑指南
最后,分享几个我踩过的坑:
- 不要用 print 代替 logging:print 没法控制级别,也没法输出到文件。我曾经接手过一个项目,全是用 print 打日志,线上排查问题的时候,日志全在控制台,早就被冲走了。
- ERROR 日志一定要带堆栈:光打一句「出错了」等于没打。加上
exc_info=True或者traceback.format_exc(),能省下你半天排查时间。 - 别把敏感信息打进去:密码、身份证、银行卡号这些,打死也别往日志里写。我见过有人把用户密码明文打到了 DEBUG 日志里,结果日志文件被运维同学拿去分析,隐私全泄露了。
- 日志级别要能动态调整:线上出了问题,能临时把某个模块的日志级别调成 DEBUG,定位完再改回去。这个能力在微服务架构下尤其重要。
我的建议:刚开始写日志的时候,多花 10 秒钟想想「这条日志该用哪个级别」。养成习惯后,你会发现排查问题的效率提升不止一倍。