## Python日志库介绍:logging模块基础

日志巡检规则编写与性能调优教程 - 第3节

3.1 为什么非要用logging?

很多人刚接触日志时,习惯用print()。我个人早年也这么干,直到有一次线上服务崩了——日志刷爆磁盘,排查时发现关键信息被print埋没了。嗯,从那以后,我再也不敢把生产日志交给print了。

Python自带的logging模块,是官方标准库。它帮你处理了:日志分级、输出到文件/控制台、日志轮转、性能开销控制。说白了,它是专门为“可靠记录”设计的。

核心原则:生产环境日志必须用logging,别用print。原因?print无法控制输出目标、无法分级、无法异步,还容易阻塞IO。

3.1.1 logging模块的核心组件

logging模块由四个基础部件组成,我画了一张图帮你理清关系:

Python logging 核心组件结构 Logger 日志记录器 Handler 日志处理器 Formatter 日志格式化器 Filter 日志过滤器 说明: Logger 是入口,程序通过它记录日志。 Handler 决定日志去往何处(文件/控制台/网络等)。 Formatter 控制日志的格式(时间/级别/消息等)。 Filter 可选,用于更精细地控制哪些日志被处理。 一条日志经过 Logger → 零或多个 Filter → Handler → Formatter 最终输出。

你想想看,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模块不复杂,但细节很多。多配、多试、多看日志输出,慢慢你就能摸清它的脾性了。