刚入行的时候,我跟大多数人一样,调试代码全靠 print。哪里不对就打印一下变量,满屏输出一坨,自己爽了,等别人或者生产环境出问题就傻眼了。日志文件里全是'这里进来了'“那个值不对”,连时间都没有,根本没法查。
后来我老老实实把项目里的 print 全换成了 logging。刚开始觉得麻烦,多了几行配置代码。用了一周后,线上问题排查速度快了不止一倍。生产环境出 bug,直接看日志文件,错误堆栈、时间戳、函数调用链一目了然,不用再远程上去加 print 了。
很多人觉得 Python 的 logging 库复杂,其实配置一次就能反复用。核心就三样东西:日志器(Logger)、处理器(Handler)、格式化器(Formatter)。日志器就是写日志的对象,处理器决定日志去哪(文件、控制台、网络),格式化器控制日志长什么样。
给大家看一个最实用的配置模板。直接在代码开头贴进去,后面所有模块都能用。
```python
import logging
import sys
def setup_logger(name=__name__, log_file='app.log', level=logging.INFO):
logger = logging.getLogger(name)
logger.setLevel(level)
控制台输出,调试时候用
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(level)
console_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(console_format)
文件输出,持久化记录
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(logging.WARNING) 文件里只记录 WARNING 及以上的
file_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s')
file_handler.setFormatter(file_format)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
return logger
直接模块级别调用,后面 import 这个就能用
logger = setup_logger('myapp')
```
这个配置有几个隐藏技巧。控制台输出用 StreamHandler,默认是 stderr,我改成 stdout,日志不会和错误信息混在一起。文件处理器单独设置级别为 WARNING,平时开发用 logger.info 不影响文件大小,只有真正的问题才会落盘。
用的时候简单得很。在任何 Python 文件里直接 import 这个 setup_logger 模块,然后这样写:
```python
logger.info('用户登录成功,用户ID: %s', user_id)
logger.warning('数据库连接超时,第3次重试')
logger.error('订单支付失败,订单号: %s', order_id, exc_info=True)
```
注意 exc_info=True 这个参数。它会自动把异常堆栈打出来。比 traceback.print_exc() 干净很多。
还有一个坑。很多人用 logging.getLogger() 的时候不传参数,默认拿根日志器。根日志器默认只处理 WARNING 以上的消息。你辛辛苦苦配了格式,结果 logger.info 根本没效果。一定是搞错了。要么给 getLogger 传一个名字,要么给处理器单独设置级别。
如果项目是 Django 或者 Flask,配置方法也差不多。Django 在 settings.py 里写 LOGGING 字典,Flask 用 app.logger 配合 logging.config.dictConfig。核心思路一模一样,就是把日志分类,给不同等级的消息安排不同的去处。
还有一个好用但容易忽略的东西:日志轮转。单文件日志无限往大里写,磁盘满了服务就挂了。用 RotatingFileHandler 可以按文件大小自动切割。
```python
from logging.handlers import RotatingFileHandler
rotating_handler = RotatingFileHandler(
'app.log', maxBytes=1010241024, backupCount=5 10MB 大小,保留5个备份
)
```
或者按时间切割,用 TimedRotatingFileHandler。生产环境必须加上,这是血的教训。
有人觉得日志配置麻烦,写个字典配置就能一劳永逸。Python 自带的 logging.config.dictConfig 方法可以直接读取字典格式的配置,复制到新项目里改改路径就行。网上搜一下'Python logging dictConfig 模板'就能找到成熟的方案。
再说一个真实案例。之前接手一个老项目,所有日志都是 print 输出到控制台的。生产环境偶发崩溃,要查原因只能重现。我把所有 print 换成 logger,加上了时间戳和模块名。第二天线上出了同样的错误,拿着日志文件去排查,5分钟就定位到一个并发写入导致的文件损坏。换成以前,至少要花一整天反复测试。
现场的人最懂这种痛。上了 logging 之后该干嘛干嘛,日志自动写,错误自动分级。遇到线上紧急问题,一句 grep ERROR app.log | head -50 就锁定了原因。比对着满屏的 print 输出猜谜语强一万倍。
不用一口吃成胖子。先把当前项目里 print 最多的模块改成 logging。跑两三天就会发现,排查问题时总忍不住去翻那个模块的日志。这时候再改其他模块,心里就有底了。