Python logging 入门:别再用 print 调试了

举报
yd_250975324 发表于 2026/10/30 10:38:07 2026/10/30
【摘要】 print 调试在小程序里够用,一旦程序跑在后台、跑在别人的机器上就会失灵。本文讲清 Python logging 的五个级别、basicConfig 上手方法、控制台与文件双输出,以及 Logger、Handler、Formatter 三件套,并列出重复输出、级别设错等四个常见坑,附一个可直接复用的日志模板。

程序刚写出来时,print 够用;一旦程序跑到别人的机器上、跑在后台、跑了好几天,print 就不够用了:输出混在一起、没有时间、找不到来源,出了问题只能靠猜。本文用最短的路径讲清 Python 内置 logging 的用法,读完就能替换掉大部分 print。

一、为什么 print 不够用

print 有三个硬伤。第一,它没有级别,调试信息和真正的错误混在一起,删也不是、留也不是。第二,它没有时间、模块、行号等上下文,出问题后很难定位。第三,它不好分流:想把错误写文件、把正常信息打到控制台,print 要自己写很多分支。logging 天生就是为这三件事设计的:分级、分流、带上下文。

二、五个级别,先记顺序

logging 的级别从低到高是 DEBUG、INFO、WARNING、ERROR、CRITICAL。DEBUG 写开发期的细节,INFO 写程序正常走到了哪一步,WARNING 写“还能跑,但有点不对劲”,ERROR 写“这一步失败了”,CRITICAL 写“程序快撑不住了”。默认级别是 WARNING,所以只调 logging.warning 及以上的调用才会显示,info 和 debug 默认会被过滤掉,这也是新手最常问“为什么我的 info 没打印”的原因。

三、最快上手:basicConfig

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
logging.info("服务启动")
logging.warning("磁盘剩余空间不足")

format 里的占位符很实用:asctime 是时间,levelname 是级别,name 是记录器名字,message 是正文。先把格式定下来,后面查日志就不用再猜“这条是谁、在什么时候打的”。注意 basicConfig 只在第一次调用时生效,重复调用不会覆盖已有配置,调试时如果改了格式却没变化,多半是这个原因。

四、同时输出到控制台和文件

开发时看控制台,上线后要留档,两者可以兼得:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    handlers=[
        logging.FileHandler("app.log", encoding="utf-8"),
        logging.StreamHandler(),
    ],
)

FileHandler 写文件时记得指定 encoding=“utf-8”,否则中文在 Windows 上很容易乱码。文件会越写越大,正式项目建议换成 RotatingFileHandler,按大小自动切分,或者按天切分的 TimedRotatingFileHandler,避免一个日志文件撑满磁盘。

五、Logger、Handler、Formatter 三件套

把 logging 理解成一条流水线就清楚了。Logger 是“谁在记”,一般用 getLogger(name) 按模块取名,这样日志里能直接看出是哪个模块出的。Handler 是“记到哪”,控制台、文件、网络都可以是 Handler。Formatter 是“记成什么样”,决定时间、级别、模块名怎么排。多个 Handler 可以挂在同一个 Logger 上,各自用不同的级别和格式,例如控制台只看 WARNING 以上,文件保留全部 INFO。

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

用模块级 Logger 而不是根 Logger,还有一个好处:不同模块可以单独调级别,排查某个模块时只把它的级别降到 DEBUG,不用把整个程序的日志都打开。

六、新手最容易踩的四个坑

第一是重复输出。同一个 Logger 上挂了多个 Handler,或者父子 Logger 都向上传播,同一条日志就会出现两遍。排查时先看 Handler 数量,必要时把子 Logger 的 propagate 设为 False。第二是级别设错地方:Logger 和 Handler 各自都有级别,只有两者都放行的日志才会输出,调了 Logger 级别却没效果时,检查 Handler 的级别。第三是在库代码里调 basicConfig,库应该只记日志、把配置权留给使用者,否则会打乱主程序的日志配置。第四是把密码、Token、身份证号等敏感信息写进日志,日志文件一旦外泄就是安全事故,记录前先脱敏。

七、一个可以直接用的模板

import logging

def get_logger(name: str) -> logging.Logger:
    logger = logging.getLogger(name)
    if logger.handlers:
        return logger
    logger.setLevel(logging.INFO)
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter(
        "%(asctime)s %(levelname)s %(name)s: %(message)s"
    ))
    logger.addHandler(handler)
    return logger

这个模板先判断 handlers 是否已存在,避免重复挂载导致重复输出;入口统一,之后要加文件输出时只改一处。配合异常处理使用效果更好:except 里用 logger.exception 记录,它会自动带上堆栈信息,比只记一句话好查得多。

总结

记住三句话就够了:用级别区分轻重缓急,用 Handler 决定日志去向,用 Formatter 固定日志格式。从今天起,新项目先配好 logging,再写业务代码;旧项目遇到 print 调试时,顺手换成 logger,查问题的时间会明显变少。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。