小白学 Python
AI 编程指南GitHub
© 2026 小白学 Python · 基于 walter201230/Python 教程
课程目录关于本站联系方式隐私政策GitHub

日志 logging

完整讲解 · 5 段教学·配 4 道练习题·预计 30 分钟

本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。

教学 01 / 05

logging:把 print 调试升级成正经日志

各位写代码时,是不是几乎都干过这种事——

代码跑出问题了,怎么办?随手在可疑位置插一行 print('xxx'),再跑一次。然后再插一行 print('yyy'),再跑。最后整个文件里散落着十几个 print('111')、print('here')、print(user),bug 修好之后忘了删。

本地玩玩还能凑合,可一旦把代码部署到服务器,问题就来了:

「线上挂了,咋回事?」打开服务器一看,啥都没有。print 默认是写到标准输出的,要是程序被 systemd 或者 Docker 拉起来,输出可能根本没人接,或者全混在一起,谁也分不清是哪个时间、哪个模块、哪条业务挂的。

Python 标准库里早就备好了答案——logging。一行不长的代码,就能让输出自带「时间戳 + 级别 + 模块名」,还能同时写到控制台、文件、远程服务器,并且可以一键切换「调试时啥都打、上线后只打 ERROR」。

这一节就从最简单的 logging.info('hi') 开始。

教学 02 / 05

一、第一个 logger

最简单的写法只有两行:

python到闯关页运行这段 →
import logging

logging.info("两点水开始打卡了")

跑一下——啥都没输出。

这是新手第一次用 logging 几乎都会踩的坑。原因是:logging 默认级别是 WARNING,比它低的 INFO 和 DEBUG 直接被丢掉了。

把上面那行换成 logging.warning(...) 再跑:

python到闯关页运行这段 →
import logging
logging.warning("两点水忘了打卡")

终于看到输出:

WARNING:root:两点水忘了打卡

注意这一行的格式:级别 + logger 名字 + 内容,三段式。root 是默认 logger 的名字。

basicConfig:最简的一行配置

要让 INFO 也打出来,先调一行 basicConfig:

python到闯关页运行这段 →
import logging

logging.basicConfig(level=logging.INFO)
logging.info("两点水开始打卡了")

输出:

INFO:root:两点水开始打卡了

basicConfig(level=logging.INFO) 把 root logger 的级别调成 INFO——INFO 及以上都会被打出来。

⚠️ 「为什么默认是 WARNING?」官方解释:logging 是给「库的作者」准备的,库要是默认就打一堆 INFO 会污染最终用户的输出。所以默认只打「值得用户注意」的 WARNING 及以上。

练习 1 / 4第一条 log去闯关页做这题 →
教学 03 / 05

二、5 个日志级别

logging 一共有 5 个内置级别,从低到高:

级别数值含义啥时候用
DEBUG10调试细节开发期间打变量、看流程
INFO20关键流程服务启动、请求进来、任务完成
WARNING30警告但不影响运行配置文件用了默认值、降级了
ERROR40错误,部分功能挂了抓到异常、外部接口超时
CRITICAL50致命,整个程序得停数据库连不上、关键依赖不可用

数值是给「过滤」用的——级别 = 30 时,所有 < 30 的日志都被丢掉,只保留 30、40、50。

怎么用每个级别?一句话原则

  • DEBUG:「我自己调试时想看,别人不需要看的」
  • INFO:「程序正常运行的关键节点」
  • WARNING:「不太对劲,但还能跑」
  • ERROR:「这一次失败了」
  • CRITICAL:「整个程序要崩」

「WARNING 和 ERROR 怎么区分?」一个简单的标准——这一次操作还能完成吗?能完成就 WARNING,不能完成就 ERROR。

五个级别全打一遍

python到闯关页运行这段 →
import logging

logging.basicConfig(level=logging.DEBUG)

logging.debug("调试看的,临时变量")
logging.info("正常流程的关键节点")
logging.warning("不对劲但能继续")
logging.error("这次操作失败了")
logging.critical("整个程序要挂")

跑一下,五条全部打出来。

练习 2 / 45 个日志级别去闯关页做这题 →练习 3 / 4只打 WARNING 及以上去闯关页做这题 →
教学 04 / 05

三、自定义日志格式

basicConfig 能配的东西不止 level,还有 format 控制每条日志的样子:

python到闯关页运行这段 →
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
)

logging.info("两点水准备打卡")
logging.warning("打卡机故障")

输出:

2026-04-28 09:00:00 - INFO - 两点水准备打卡
2026-04-28 09:00:00 - WARNING - 打卡机故障

是不是比 print 看着舒服多了?时间戳、级别、消息全有了。

format 常用占位符

占位符含义
%(asctime)s时间戳(用 datefmt 控制格式)
%(name)slogger 名字(默认 root,或 getLogger(name) 指定的)
%(levelname)s级别名(DEBUG / INFO / ...)
%(message)s实际打的内容
%(module)s调用所在的模块名
%(funcName)s调用所在的函数名
%(lineno)d调用所在的行号

把它们组合起来,能拼出非常详细的日志:

python到闯关页运行这段 →
format='%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s'

basicConfig 只调一次

⚠️ basicConfig 只在「root logger 还没配过 handler」时才会生效,第二次调用不起作用。所以整个程序里只调一次,通常放在程序入口处。

如果想强制覆盖(比如调试时反复调),加 force=True:

python到闯关页运行这段 →
logging.basicConfig(level=logging.INFO, force=True)
教学 05 / 05

四、正经做法:getLogger(name)

新手最常见的反模式是直接 import logging 之后到处 logging.info(...)、logging.error(...)。这其实是用了 root logger——所有模块共用同一个 logger,分不清日志是哪儿来的。

正经做法:每个模块开一个独立的 logger:

python到闯关页运行这段 →
import logging

logger = logging.getLogger(__name__)

def punch_in(user):
    logger.info("用户 %s 开始打卡", user)

__name__ 是 Python 内置的变量。在 mymodule.py 里 __name__ == 'mymodule',在包 app/services/punch.py 里 __name__ == 'app.services.punch'。这个名字会出现在每条日志里,一眼就能看出日志是哪个模块打的。

为什么要这么干?

  1. 能看出哪个模块打的日志——出 bug 时直接定位
  2. 能给不同模块设不同级别——比如 app.services 整组打 DEBUG、app.api 只打 WARNING
  3. logger 自动按层级继承——app.services.punch 找不到配置时会去查 app.services,再查 app,再查 root

完整例子

python到闯关页运行这段 →
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(name)s - %(levelname)s - %(message)s',
)

logger = logging.getLogger('app.punch')

logger.info("用户开始打卡")
logger.warning("迟到了")

输出:

app.punch - INFO - 用户开始打卡
app.punch - WARNING - 迟到了

五、logger.exception():在 except 里自动带 traceback

写错误日志最容易踩的坑——只 logger.error('xxx') 不带异常信息:

python到闯关页运行这段 →
try:
    1 / 0
except Exception as e:
    logging.error('出错了')   # ❌ 完全看不出错在哪

正确写法是 logger.exception(),它会自动把当前 traceback 带进去:

python到闯关页运行这段 →
try:
    1 / 0
except Exception as e:
    logging.exception('出错了')   # ✓ 自动附上完整 traceback

输出:

ERROR:root:出错了
Traceback (most recent call last):
  File "main.py", line 4, in <module>
    1 / 0
ZeroDivisionError: division by zero

写日志时最有用的一条建议:在 except 里永远用 logger.exception(),不要用 logger.error(str(e))。

练习 4 / 4用 getLogger 给 logger 取名字去闯关页做这题 →

读完了?动手练一遍才算真会。

去闯关模式练习 →