从print到logging:掌握这6个关键点,让你的代码学会"自检",bug无处遁形!
目录:
- 为什么新手需要日志?
- 基础配置四步走
- 等级管理艺术
- 格式化才是灵魂
- 异常处理正确姿势
- 性能优化实战
嗨,你好呀,我是你的老朋友精通代码大仙。“程序员的三大错觉:这次肯定没bug、注释不用写、日志不需要”,这话你听着耳熟吗?我见过太多新手用print调试到崩溃,最后发现日志才是排查问题的核武器。今天咱们就手把手教你用logging模块,让你的代码学会"自检"!
1. 为什么新手需要日志?
痛点场景:凌晨两点,线上服务突然挂掉。你手忙脚乱地翻着服务器上残留的print输出,却发现关键信息都没打印…
# 典型的print调试灾难
def process_data(data):
print("开始处理数据") # 上线后这些print全被注释了
# ...业务逻辑...
print(f"处理结果:{result}") # 结果忘记取消注释
正确认知:logging模块是代码的"黑匣子",它能:
- 自动记录时间戳
- 区分不同严重级别的事件
- 动态控制输出目标(文件/控制台)
- 保留异常堆栈信息
生死案例:
import logging
logging.basicConfig(level=logging.INFO)
def process_data(data):
logging.info("开始处理数据")
try:
# ...业务逻辑...
logging.info(f"处理结果:{result}")
except Exception as e:
logging.error("数据处理异常", exc_info=True) # 自动记录堆栈
小结:日志不是可选项,而是代码的急救包。
2. 基础配置四步走
常见翻车:直接import logging就开始用,结果发现日志没输出…
import logging
logging.debug("这行不会显示") # 默认只显示WARNING以上级别
正确配置:
import logging
# 一次性基础配置(程序启动时执行)
logging.basicConfig(
level=logging.DEBUG, # 设置根日志级别
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler("app.log"), # 输出到文件
logging.StreamHandler() # 输出到控制台
]
)
参数详解:
- level:日志级别过滤阈值
- format:消息模板(后文详解)
- handlers:输出处理器(文件/邮件/HTTP等)
避坑指南:
- basicConfig只能调用一次
- 尽早配置(建议在入口文件顶部)
- 生产环境慎用DEBUG级别
3. 等级管理艺术
等级误用现场:
# 新手常见错误:把业务日志用ERROR级别
logging.error("用户张三登录成功") # 狼来了式日志
五级警戒线:
- DEBUG:挤牙膏调试信息(开发环境专用)
- INFO:业务关键流程记录(如"订单已支付")
- WARNING:预期内的异常(如"配置文件缺失,使用默认值")
- ERROR:需要干预的问题(如"数据库连接失败")
- CRITICAL:系统级故障(如"磁盘空间不足")
实战技巧:
# 不同模块设置不同级别
logger = logging.getLogger("payment")
logger.setLevel(logging.INFO) # 支付模块日志级别
# 动态调整级别
if config.DEBUG:
logger.setLevel(logging.DEBUG)
4. 格式化才是灵魂
默认格式的尴尬:
WARNING:root:连接超时
(看不出时间、模块、上下文)
高阶格式化:
format = "%(asctime)s [%(levelname).1s] %(filename)s:%(lineno)d - %(message)s"
# 输出示例:
# 2023-08-25 14:20:35 [E] main.py:89 - 数据库连接失败
魔法变量大全:
- %(name)s:日志器名称
- %(thread)d:线程ID
- %(funcName)s:函数名
- %(process)d:进程ID
- %(pathname)s:完整文件路径
结构化日志进阶:
# 适合ELK等日志系统
logging.info("订单创建", extra={
"order_id": 123,
"amount": 99.9,
"user_id": "U1001"
})
5. 异常处理正确姿势
无效日志现场:
try:
db.insert(data)
except Exception as e:
logging.error(f"插入失败:{e}") # 丢失堆栈信息!
三种正确姿势:
- 自动捕获异常栈
logging.error("插入失败", exc_info=True)
- 使用exception方法
logging.exception("插入失败")
- 完整异常对象
try:
# ...
except ValueError as e:
logging.error(f"参数错误:{e}", stack_info=True)
错误码映射技巧:
ERROR_CODE_MAP = {
1045: "数据库认证失败",
2003: "数据库连接超时"
}
try:
db.connect()
except DBError as e:
code = e.args[0]
logging.error(ERROR_CODE_MAP.get(code, "未知数据库错误"))
6. 性能优化实战
文件日志的坑:
# 直接使用FileHandler可能阻塞主线程
handler = logging.FileHandler("app.log") # 默认同步写入
三大优化策略:
- 使用RotatingFileHandler防爆盘
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
"app.log",
maxBytes=10*1024*1024, # 10MB
backupCount=5
)
- 异步日志处理
from concurrent_log_handler import ConcurrentRotatingFileHandler
- 生产环境禁用DEBUG
if not DEBUG:
logging.disable(logging.DEBUG)
内存监控技巧:
# 在日志中记录内存使用
import psutil
logging.info(
"内存使用统计",
extra={"memory": psutil.virtual_memory().percent}
)
写在最后
当你开始认真对待日志,代码就有了自我诊断的能力。记住:好的日志不是写出来的,而是设计出来的。那些你深夜埋下的logging.info,终将成为照亮bug的明灯。编程路上,愿你的每个error都有迹可循,每次排查都直击要害。保持这份细致,你离高手就不远了!