(五)用日志保留程序经过的步骤
1. 从临时输出到带等级的记录
导入程序需要留下“开始读取哪份文件、读到几本书、数据库写入是否完成”等信息。把所有信息都写进 print,以后导出 CSV 时就容易混进结果。标准库 logging 负责记录运行经过,print 继续输出给使用者看的结果:
# 按 INFO 等级记录导入过程;DEBUG 这次不会显示,日志默认写到标准错误输出。
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s %(message)s")
logger = logging.getLogger("reading.importer")
logger.debug("准备检查字段")
logger.info("开始导入 count=%d", 2)
logger.warning("跳过重复标签 tag=%s", "基础")
print("已导入 2 本")标准错误输出得到 INFO reading.importer 开始导入 count=2、WARNING reading.importer 跳过重复标签 tag=基础;标准输出得到 已导入 2 本。两条输出流的含义不同,终端同时显示时也不应靠它们的穿插顺序判断业务执行次序。
| 等级 | 数值 | 书籍库中的记录 |
|---|---|---|
DEBUG |
10 | 用于排查的字段值、查询参数和步骤 |
INFO |
20 | 一批导入完成、记录条数 |
WARNING |
30 | 已采取处理措施、仍需要关注的情况 |
ERROR |
40 | 本次操作失败 |
CRITICAL |
50 | 程序整体无法继续提供服务 |
level=INFO 接收数值不少于 INFO 的记录。未配置时根记录器通常从 WARNING 开始,因此调试时看不到 info,先检查等级。logger.info("count=%d", count) 把格式串与参数分别交给日志系统;消息需要输出时才完成拼接。参数表达式仍然会先求值,昂贵计算不会因为使用 %s 就自动跳过。
每个模块用 logging.getLogger(__name__) 取得自己的记录器,名字便是模块路径。导入模块只取得记录器,输出位置与格式由程序入口配置,避免一个库擅自改变整份应用的日志。logging 入门
2. 在处理异常的位置保留调用信息
# exception 在 except 中附上当前异常的 traceback;普通 error 只记录消息。
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
logger = logging.getLogger("reading.importer")
try:
int("很多")
except ValueError:
logger.exception("导入失败 record=%d field=%s", 2, "minutes")日志先显示记录编号与字段名,再显示 ValueError 和失败位置。logger.exception 相当于 ERROR 消息加上当前异常信息;在 except 中调用才有这次异常可记录。
同一错误可以沿函数继续传出,在入口统一记录一次,再向终端给出简短说明。每一层都记 traceback 再抛出,会把同一次失败写很多遍。字段日志选择定位所需的编号和计数,密码、令牌和整份私人数据不应随手写入运行日志。