网站首页 > 技术文章 正文
Python logging 的“最佳实践”可以概括为一句话:
让日志既能在开发时帮你排错,也能在生产里帮你定位问题,同时不给运维埋坑。
下面给出一份可直接落地的 checklist,分场景逐条说明。
1. 永远用 logging 而不是 print
print 只能到 stdout,无法控制级别、格式、去向。
开发阶段可临时加 -u 或 PYTHONUNBUFFERED=1 看 stdout,但上线前必须全部换成 logging。
2. 一条 logger 初始化语句,放到模块顶层
import logging
logger = logging.getLogger(__name__)
- __name__ 让层级与包结构一致,后续可按模块名过滤。
- 不要给 logger 起硬编码的名字,否则重构包名后配置失效。
3. 配置只写一次,写在程序入口
不要每个模块都 logging.basicConfig();
用 dictConfig 或 fileConfig,在 if __name__ == "__main__": 里或专门的 logging.yml 中加载。
import yaml, logging.config
with open("logging.yml") as f:
logging.config.dictConfig(yaml.safe_load(f))
4. 日志级别语义要统一
- DEBUG:排查问题时才需要看的细粒度信息。
- INFO:关键业务里程碑,上线后也要保留。
- WARNING:还能自愈的问题,磁盘快满、重试一次成功等。
- ERROR:需要人工介入,但程序还能跑。
- CRITICAL:程序已死或数据已坏,立即报警。
5. 日志格式 = 时间 + 级别 + 模块 + 行号 + 消息
一行就能定位到代码,运维最爱。
formatters:
standard:
format: "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d | %(message)s"
datefmt: "%Y-%m-%d %H:%M:%S"
6. 用占位符而不是 f-string
# 好
logger.info("create user %s with id=%d", username, uid)
# 不好
logger.info(f"create user {username} with id={uid}")
- 占位符只有在真正需要输出时才拼接,DEBUG 关闭时节省 CPU。
- 避免在 f-string 里提前触发耗时运算或异常。
7. 敏感信息脱敏
密码、token、手机号统一用 *** 或 hash 前 4 位。
8. 结构化日志(可选但推荐)
生产环境直接输出 JSON,方便 ELK / Loki / ClickHouse 解析:
formatters:
json:
(): pythonjsonlogger.jsonlogger.JsonFormatter
format: "%(asctime)s %(name)s %(levelname)s %(message)s"
9. 区分控制台与文件 Handler
- console:INFO 以上,人眼看。
- file:DEBUG 以上,自动 rotate。
- error_file:ERROR 以上,单独报警。
handlers:
console:
class: logging.StreamHandler
level: INFO
formatter: standard
stream: ext://sys.stdout
file:
class: logging.handlers.RotatingFileHandler
level: DEBUG
formatter: json
filename: logs/app.log
maxBytes: 50_000_000 # 50 MB
backupCount: 5
10. 第三方库噪音治理
# 调低不关心的库
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("matplotlib").setLevel(logging.WARNING)
11. 异常记录用 logger.exception
try:
risky()
except Exception:
logger.exception("handle order failed, order_id=%s", order_id)
- 自动附带 traceback,省得手动 logger.error(e, exc_info=True)。
12. 在 async / 多进程场景
- asyncio:用 asyncio.run() 时 logging 会沿用主线程配置,无需特殊处理。
- multiprocessing:子进程需重新 dictConfig,否则 handler 会重复写同一文件导致错乱。可用 logging.handlers.QueueHandler + QueueListener 实现集中日志。
13. 单元测试时捕获日志
pytest 自带 caplog fixture,断言日志内容:
def test_invalid_user(caplog):
create_user("bad#name")
assert "invalid username" in caplog.text
14. 开发期小技巧
- 临时把全局级别调到 DEBUG:
python -m mypkg.main --log-level=DEBUG
用 argparse 解析后 logging.getLogger().setLevel(args.log_level.upper())。 - 对某个模块临时打开 DEBUG:
logging.getLogger("mypkg.engine").setLevel(logging.DEBUG)
15. 一个完整示例目录
mypkg/
├─ __init__.py
├─ main.py
├─ engine.py
└─ logging.yml
logging.yml:
version: 1
disable_existing_loggers: false
formatters:
standard:
format: "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d | %(message)s"
datefmt: "%Y-%m-%d %H:%M:%S"
json:
(): pythonjsonlogger.jsonlogger.JsonFormatter
handlers:
console:
class: logging.StreamHandler
level: INFO
formatter: standard
stream: ext://sys.stdout
file:
class: logging.handlers.RotatingFileHandler
level: DEBUG
formatter: json
filename: logs/app.log
maxBytes: 50_000_000
backupCount: 5
loggers:
"":
level: DEBUG
handlers: [console, file]
main.py
import logging.config, yaml
from engine import process
def setup_logging():
with open("logging.yml") as f:
logging.config.dictConfig(yaml.safe_load(f))
if __name__ == "__main__":
setup_logging()
process()
engine.py
import logging
logger = logging.getLogger(__name__)
def process():
logger.info("start processing")
try:
1/0
except ZeroDivisionError:
logger.exception("unexpected zero")
总结
把日志当 API 设计:格式稳定、级别清晰、配置集中、敏感脱敏、可观测、可测试。
- 上一篇: Python并发数据结构实现原理
- 下一篇: Python中的多进程
猜你喜欢
- 2025-08-06 生产环境中使用的十大 Python 设计模式
- 2025-08-06 面试必备:Python内存管理机制(建议收藏)
- 2025-08-06 服务端开发面试必背——消息队列及它的主要用途和优点。附代码
- 2025-08-06 Python 栈:深度解析与应用
- 2025-08-06 Python中的多进程
- 2025-08-06 Python并发数据结构实现原理
- 2025-08-06 用SendGrid和Redis队列用Python调度国际空间站的电子邮件
- 2025-08-06 Python教程(三十五):数据库操作进阶
- 2025-08-06 Python倒车请注意!负步长range的10个高能用法,让代码效率翻倍
- 2025-08-06 python collections 的超赞功能
- 08-06生产环境中使用的十大 Python 设计模式
- 08-06面试必备:Python内存管理机制(建议收藏)
- 08-06服务端开发面试必背——消息队列及它的主要用途和优点。附代码
- 08-06Python 栈:深度解析与应用
- 08-06Python中的多进程
- 08-06Python Logging 最佳实践
- 08-06Python并发数据结构实现原理
- 08-06用SendGrid和Redis队列用Python调度国际空间站的电子邮件
- 最近发表
- 标签列表
-
- python中类 (31)
- python 迭代 (34)
- python 小写 (35)
- python怎么输出 (33)
- python 日志 (35)
- python语音 (31)
- python 工程师 (34)
- python3 安装 (31)
- python音乐 (31)
- 安卓 python (32)
- python 小游戏 (32)
- python 安卓 (31)
- python聚类 (34)
- python向量 (31)
- python大全 (31)
- python次方 (33)
- python桌面 (32)
- python总结 (34)
- python浏览器 (32)
- python 请求 (32)
- python 前端 (32)
- python验证码 (33)
- python 题目 (32)
- python 文件写 (33)
- python中的用法 (32)