如果你的 Python 项目一出问题第一反应还是在代码里到处加print()这篇文章应该能省你不少时间。不是说 print 没用而是当项目稍微变大、多人协作、需要线上排查时它很快会变成一堆噪音不知道来自哪个模块没时间没等级异常堆栈也不完整。本文不讲复杂的日志平台也不要求你一上来就接 ELK、OpenTelemetry。目标很简单用 Python 标准库logging搭一套“够用、可维护、以后还能扩展”的日志习惯。一、print 调试最大的问题不是低级而是不可控很多人讨厌 logging是因为一开始看到配置就头大。但反过来看print 的问题也很明显没有等级调试信息、普通流程、警告、错误混在一起。没有上下文输出来自哪个文件、哪个模块经常要靠猜。不好关闭临时 print 忘记删线上日志就变脏。异常信息不完整只打印错误字符串真正的堆栈丢了。不方便落盘后面想保存文件、按天切分、统一格式又要重新改。所以我的建议不是“永远不用 print”而是临时实验可以 print进入项目代码就尽量 logging。二、先记住 5 个日志等级Python logging 常用等级就这几个DEBUG开发调试时看的细节比如变量、分支、请求参数。INFO正常流程信息比如服务启动、任务完成。WARNING程序还能继续但出现了值得注意的情况。ERROR某个操作失败需要排查。CRITICAL非常严重的问题通常意味着服务不可用或数据风险。一个简单判断如果这条信息只给开发时自己看用 DEBUG如果线上排查也可能需要用 INFO 或 WARNING如果发生了异常用 ERROR。三、最小可用配置先让日志像样起来在小项目里可以先用下面这段配置import logging logging.basicConfig( levellogging.INFO, format%(asctime)s | %(levelname)s | %(name)s | %(message)s, datefmt%Y-%m-%d %H:%M:%S, ) logger logging.getLogger(__name__) logger.info(任务开始) logger.warning(配置项 timeout 未设置使用默认值)这段代码做了三件事设置日志等级为INFO统一输出时间、等级、模块名和消息用__name__标记日志来源。有了模块名后续排查时你就能知道日志来自service.user、crawler.fetcher还是jobs.cleaner不用在每条 print 前手动写前缀。四、每个模块都用自己的 logger不要在所有文件里复用一个全局 logger。更推荐每个模块这样写# user_service.py import logging logger logging.getLogger(__name__) def create_user(username: str): logger.info(开始创建用户: %s, username) # 业务逻辑...注意这里用了%s占位而不是 f-string。原因是 logging 可以延迟格式化当某个等级的日志不会输出时字符串拼接成本也能少一点。小项目差别不明显但这是个好习惯。五、异常日志别只打印 e要带堆栈很多代码会这样写try: risky_call() except Exception as e: logger.error(调用失败: %s, e)这样只能看到错误信息看不到完整调用链。更推荐try: risky_call() except Exception: logger.exception(调用 risky_call 失败)logger.exception()只能在except块里使用它会自动带上异常堆栈。线上排查时这通常比一行错误字符串有用得多。六、把日志写入文件适合脚本和定时任务如果你写的是爬虫、数据清洗、定时任务日志最好落盘。标准库里可以直接加一个文件处理器import logging from logging.handlers import RotatingFileHandler logger logging.getLogger(app) logger.setLevel(logging.INFO) formatter logging.Formatter( %(asctime)s | %(levelname)s | %(name)s | %(message)s ) console_handler logging.StreamHandler() console_handler.setFormatter(formatter) file_handler RotatingFileHandler( app.log, maxBytes5 * 1024 * 1024, backupCount3, encodingutf-8, ) file_handler.setFormatter(formatter) logger.addHandler(console_handler) logger.addHandler(file_handler) logger.info(日志会同时输出到控制台和文件)RotatingFileHandler的作用是限制单个日志文件大小并保留一定数量的备份避免日志无限长把磁盘写满。七、开发环境想更舒服可以加 Rich如果只是本地开发想让日志更清楚可以考虑用rich做彩色输出。它不是必需品但对阅读体验很友好。pip install richimport logging from rich.logging import RichHandler logging.basicConfig( levellogging.DEBUG, format%(message)s, handlers[RichHandler(rich_tracebacksTrue)], ) logger logging.getLogger(__name__) logger.debug(这是一条调试日志) logger.info(本地开发时更容易看清层级和异常)我的习惯是本地开发可以追求好看线上日志优先追求稳定、结构清晰、方便机器处理。所以 Rich 更适合开发环境不建议盲目把所有生产日志都变成花哨格式。八、一个实用的日志分层建议项目里可以按环境区分日志策略本地开发DEBUG等级控制台输出可以加 Rich。测试环境INFO等级保留关键业务流程。生产环境INFO或WARNING起步异常必须带堆栈文件或日志系统统一收集。不要把所有东西都打成 ERROR。ERROR 多了以后真正的问题反而会被淹没。也不要把每个循环变量都打进 INFO否则日志量会迅速膨胀。九、我自己常用的日志检查清单启动时有没有打印关键配置比如环境、端口、任务参数。重要外部调用有没有记录开始和失败原因异常日志有没有堆栈日志里有没有密码、Token、手机号等敏感信息定时任务有没有开始、结束、耗时和结果数量日志等级是否合理能不能通过配置关闭 DEBUG尤其要注意敏感信息。日志一旦落盘或进入平台传播范围往往比你想象的大。调试接口时顺手把 Authorization、Cookie、数据库连接串打出来是非常危险的习惯。十、最后给一个落地路线如果你现在项目里还是大量 print可以按这个顺序改不用一次性重构完先在入口文件加logging.basicConfig。每个模块引入logger logging.getLogger(__name__)。把关键流程的 print 改成logger.info()。把异常处理里的 print 改成logger.exception()。脚本类项目加文件日志和滚动策略。最后再考虑 Rich、JSON 日志、集中式日志平台。日志不是为了显得“工程化”而是为了让未来的你少熬夜。代码能跑只是第一步出问题时能快速定位才是真正对项目负责。你在 Python 项目里更常用 print、logging还是直接上结构化日志欢迎分享你踩过的日志坑。