日志巡检规则编写与性能调优教程 - 第3节
3.1 为什么非要用logging?
很多人刚接触日志时,习惯用print()。我个人早年也这么干,直到有一次线上服务崩了——日志刷爆磁盘,排查时发现关键信息被print埋没了。嗯,从那以后,我再也不敢把生产日志交给print了。
Python自带的logging模块,是官方标准库。它帮你处理了:日志分级、输出到文件/控制台、日志轮转、性能开销控制。说白了,它是专门为“可靠记录”设计的。
核心原则:生产环境日志必须用logging,别用print。原因?print无法控制输出目标、无法分级、无法异步,还容易阻塞IO。
3.1.1 logging模块的核心组件
logging模块由四个基础部件组成,我画了一张图帮你理清关系:
你想想看,Logger是入口,Handler是出口,Formatter负责包装。Filter像看门大爷——符合条件才放行。这四个组件配合起来,就能搭出非常灵活的日志系统。
3.2 快速上手:最简单的logging例子
话不多说,先跑个最基本的:
import logging
logging.warning("小心,磁盘快满了!")
logging.info("这只是个信息") # 默认不会输出
运行后你会看到两条输出?不,只有warning那行。为什么?因为默认日志级别是WARNING,低于它的级别(INFO、DEBUG)都被无视了。
小技巧:如果你想让info也出来,可以设置logging.basicConfig(level=logging.INFO)。记得在导入logging后马上调用。
3.2.1 日志级别(Level)
日志有5个标准级别,由低到高:
| 级别 | 数值 | 典型用途 |
|---|---|---|
DEBUG |
10 | 调试细节,开发时看 |
INFO |
20 | 流程信息,如“任务开始/结束” |
WARNING |
30 | 潜在问题,不阻塞但需关注 |
ERROR |
40 | 功能异常,需人工介入 |
CRITICAL |
50 | 严重错误,系统可能崩 |
我习惯在开发环境设为DEBUG,生产环境设为WARNING或INFO。这样既不会漏掉关键告警,又不会把调试日志刷进生产文件。
3.3 配置日志:三种方式
logging配置有三种常见姿势。我逐个给你捋一遍。
3.3.1 快速配置:basicConfig
适合小脚本或临时调试:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
filename="app.log",
filemode="a" # 追加模式
)
logging.info("服务启动成功")
这段配置会把日志写入app.log,格式包含时间、级别、消息。简单粗暴,够用。
注意:basicConfig只能在第一次调用时才生效。之后再调用会忽略。如果你在别处又调了一遍,新的配置不会覆盖原有的。曾经有同事因为这个bug查了半天日志怎么没按预期输出——嗯,我也是踩过坑的人。
3.3.2 完整配置:Logger + Handler + Formatter
搞正式项目时,我一般会拆开配置:
import logging
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 文件处理器
file_handler = logging.FileHandler("app.log")
file_handler.setLevel(logging.DEBUG)
# 格式化器
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.info("开始处理请求...")
这样,日志可以同时输出到控制台和文件,且各自级别不同。控制台只看INFO及以上,文件记录全部DEBUG细节。你想想看,这种分层多方便——开发时看控制台,排查问题时翻文件。
3.3.3 字典配置:dictConfig
复杂场景(比如多logger、不同格式),我推荐用字典配置。一次配好,清晰可维护:
import logging.config
LOGGING_CONFIG = {
"version": 1,
"formatters": {
"standard": {
"format": "%(asctime)s %(levelname)s %(message)s"
},
"detailed": {
"format": "%(asctime)s %(name)s %(levelname)s %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "standard"
},
"file": {
"class": "logging.FileHandler",
"level": "DEBUG",
"formatter": "detailed",
"filename": "app.log"
}
},
"loggers": {
"my_app": {
"handlers": ["console", "file"],
"level": "DEBUG"
}
}
}
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("my_app")
这段代码把配置集中到一个字典里,通过dictConfig加载。我参与过的几个微服务项目都是这么干的——配置统一,改起来也方便。
3.4 避免常见陷阱
- 重复记录:如果你不小心调了两次
logging.basicConfig,或重复添加Handler,日志会重复打印。解决:在addHandler前判断或移除已有Handler。 - 文件权限:如果用
FileHandler,确保程序有写权限。尤其是容器化环境,挂载卷的权限经常忘设置。 - 日志轮转:生产环境别只用
FileHandler——日志会无限增长。用RotatingFileHandler或TimedRotatingFileHandler自动切分文件。
一句话总结:logging模块不复杂,但细节很多。多配、多试、多看日志输出,慢慢你就能摸清它的脾性了。