3、日志文件输出:FileHandler的使用、日志文件命名规则、日志文件编码问题
日志光在控制台打印可不行,生产环境里我们得把日志写到文件里。这样出了问题才能翻历史记录,才能做审计。这一节我重点聊聊 FileHandler 怎么用,以及文件命名和编码那些容易踩坑的地方。
3.1 FileHandler 的基本使用
说白了,FileHandler 就是 Python logging 模块里负责把日志写到磁盘文件的组件。你配置好它,日志就会按你的规则往文件里写。
我个人习惯直接这样配:
import logging
# 创建一个 logger
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
# 创建一个 FileHandler,指定日志文件路径
file_handler = logging.FileHandler('app.log', mode='a', encoding='utf-8')
file_handler.setLevel(logging.INFO)
# 定义格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# 把 handler 加到 logger 上
logger.addHandler(file_handler)
# 测试一下
logger.info('系统启动成功')
logger.error('数据库连接失败,重试中...')
这里有个细节:mode='a' 表示追加模式,每次启动不会覆盖旧日志。如果你用 mode='w',那每次重启服务,之前的日志就全没了。我在项目中遇到过有人图省事用了 w 模式,结果线上排查问题发现日志只剩最后启动的那一段,气得够呛。
3.2 日志文件命名规则
日志文件怎么命名?这看起来是个小事,但命名不规范,排查问题时你找文件都能找半天。
我总结了一套比较通用的命名规则,你可以参考:
| 场景 | 命名示例 | 说明 |
|---|---|---|
| 单文件 | app.log |
开发环境或临时测试用 |
| 按日期轮转 | app-2025-01-15.log |
每天一个文件,便于按天归档 |
| 按大小轮转 | app.log.1, app.log.2 |
超过大小后自动编号,类似 logrotate 风格 |
| 按模块拆分 | api-2025-01-15.log, worker-2025-01-15.log |
不同模块写不同文件,方便定位问题 |
| 带主机名 | app-web01-2025-01-15.log |
多机部署时区分来源 |
我个人比较推荐「模块名 + 日期 + 序号」这种组合。举个例子:order-service-2025-01-15-001.log。这样一看就知道是哪个模块、哪天的日志、第几个轮转文件。
- 或下划线 _ 分隔,这样在 Linux 命令行里 grep、awk 处理起来不会出幺蛾子。
3.3 日志文件编码问题
编码问题,嗯,这里要注意。很多新手在这个上面吃过亏。
默认情况下,Python 的 FileHandler 在 Windows 上可能用 GBK 编码,在 Linux 上可能用 UTF-8。如果你的日志里包含中文,跨平台查看时就会出现乱码。
我曾经在一个项目里遇到过:开发在 Windows 上写代码,日志里打印了中文订单号,结果部署到 Linux 服务器上,用 cat 查看全是乱码。排查了半天才发现是编码不一致。
解决方案很简单:显式指定编码为 UTF-8。
# 正确的做法
file_handler = logging.FileHandler('app.log', encoding='utf-8')
# 错误的做法(不指定编码,依赖系统默认)
file_handler = logging.FileHandler('app.log')
如果你用的是 RotatingFileHandler 或 TimedRotatingFileHandler,同样要指定编码:
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
'app.log',
maxBytes=10*1024*1024, # 10MB
backupCount=5,
encoding='utf-8' # 别忘了这个
)
3.4 实战中的避坑指南
最后分享几个我踩过的坑,你遇到了可以少走弯路:
- 文件权限问题:日志文件是由应用进程创建的。如果应用以普通用户运行,而日志目录是 root 所有的,那就会写失败。我曾经部署时忘了改目录权限,结果服务跑了一周都没写日志,排查问题时才发现。
- 日志文件被删除:如果你手动删除了正在被写入的日志文件,FileHandler 不会自动重建。它继续往那个已删除的 inode 里写,但你看不到文件了。解决办法是用
WatchedFileHandler,它会检测文件是否被删除或轮转,然后自动重新打开。 - 编码声明位置:在 Python 2 时代,文件头部要加
# -*- coding: utf-8 -*-。Python 3 默认就是 UTF-8,但如果你在日志消息里拼接了非 UTF-8 的字节串,还是会报错。所以日志里尽量只输出字符串,别直接输出 bytes。
好了,FileHandler 的使用、命名规则和编码问题就聊到这儿。下一节我们讲日志轮转的具体策略,到时候会结合 TimedRotatingFileHandler 和 RotatingFileHandler 做详细对比。