Python logging 模块详解:从入门配置到生产级 JSON 输出

Python 内置 logging 模块怎么用?本文系统讲解 Logger/Handler/Formatter 三要素、级别体系、dictConfig 集中配置、python-json-logger 结构化输出、异常堆栈与未捕获异常处理,并给出接入观测云集中管理的路径。

最佳实践
Python logging 模块详解:从入门配置到生产级 JSON 输出技术指南封面

Python logging 是 Python 标准库内置的日志模块,通过 Logger(记录器)、Handler(输出目标)、Formatter(格式)三要素的组合,提供不输任何第三方框架的完整日志能力。 它最大的好处是零依赖、生态默认——绝大多数第三方库都基于它打日志,学会它等于掌握了 Python 日志的"通用语言"。

核心要点速览

  • 用法红线:别用 logging.info() 操作 root logger,用 logging.getLogger(__name__) 创建模块级 logger;
  • 默认级别是 WARNING——DEBUG/INFO 不输出不是 bug,是级别没配;
  • 生产输出 JSON 用 python-json-logger,文本格式只适合开发;
  • 记录异常务必带 exc_info=True 或用 logger.exception(),否则堆栈丢失;
  • 集中化推荐"应用写 stdout/文件 + DataKit 采集上报观测云"。

快速上手与默认行为

import logging

logging.debug("调试信息")
logging.info("一般信息")
logging.warning("警告信息")

输出只有 WARNING:root:警告信息——默认 logger 名为 root,默认级别 WARNING,低于它的 DEBUG/INFO 被丢弃。正确姿势是创建模块级 logger:

logger = logging.getLogger(__name__)

__name__ 命名会自动形成"包.模块"的层级,便于按模块控制级别。

三要素:Logger、Handler、Formatter

import sys
import logging

logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)

stdout_handler = logging.StreamHandler(stream=sys.stdout)
stdout_handler.setLevel(logging.DEBUG)
err_handler = logging.FileHandler("error.log")
err_handler.setLevel(logging.ERROR)   # 只收 ERROR 及以上

fmt = logging.Formatter("%(asctime)s | %(levelname)s | %(name)s | %(filename)s:%(lineno)s | %(message)s")
stdout_handler.setFormatter(fmt)
err_handler.setFormatter(fmt)

logger.addHandler(stdout_handler)
logger.addHandler(err_handler)

要点:级别可以分别设在 logger 和 handler 上——logger 决定"收不收",handler 决定"发不发",两个阀门串联。

dictConfig:集中式配置

项目变大后,推荐用 logging.config.dictConfig 把配置集中到一个字典(或单独模块)中,应用启动时一次性加载:

from logging.config import dictConfig

dictConfig({
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "default": {"format": "%(asctime)s | %(levelname)s | %(name)s | %(message)s"},
    },
    "handlers": {
        "console": {"class": "logging.StreamHandler", "stream": "ext://sys.stdout", "formatter": "default"},
    },
    "root": {"level": "INFO", "handlers": ["console"]},
})

生产化:JSON 结构化输出

标准库本身不支持 JSON,装 python-json-logger 即可:

pip install python-json-logger
from pythonjsonlogger import jsonlogger

json_fmt = jsonlogger.JsonFormatter(
    "%(name)s %(asctime)s %(levelname)s %(filename)s %(lineno)d %(message)s",
    rename_fields={"levelname": "level", "asctime": "time"},
)
stdout_handler.setFormatter(json_fmt)

输出:

{"name": "myapp", "time": "2026-08-25 14:39:03,265", "level": "INFO", "filename": "main.py", "lineno": 31, "message": "服务启动于 8080 端口"}

附加上下文字段用 extra 参数(注意字段名不能与 LogRecord 内置属性冲突,否则抛 KeyError):

logger.info("订单创建成功", extra={"order_id": "ORD-123", "user_id": "USR-9"})

异常与未捕获异常

try:
    1 / 0
except ZeroDivisionError:
    logger.exception("计算失败")   # 等价于 error(..., exc_info=True),自动带堆栈

未捕获异常用 sys.excepthook 兜底,以 CRITICAL 记录后退出:

import sys

def handle_exception(exc_type, exc_value, exc_tb):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_tb)
        return
    logger.critical("未捕获异常", exc_info=(exc_type, exc_value, exc_tb))

sys.excepthook = handle_exception

文件轮转

FileHandler 不带轮转,用 RotatingFileHandler(按大小)或 TimedRotatingFileHandler(按时间):

from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler("app.log", maxBytes=10_000_000, backupCount=5)

生产环境更推荐交给 logrotate(见本系列《Logrotate 日志轮转实战》),Python 内置轮转在高并发多进程写同一文件时并不可靠。

接入观测云

观测云落地:应用以 JSON 格式输出到 stdout(容器)或文件(虚拟机),DataKit 负责采集上报——容器 stdout 采集或磁盘文件采集(logging.confsource/service)。JSON 自动解析出字段,Pipeline 中把 level 映射为标准 status、确认 time 提取 。之后在日志查看器按字段检索、聚类分析报错模式 ;**监控器(日志检测)**配置"CRITICAL/ERROR 出现即告警",经告警策略推送钉钉/企业微信/飞书 。Python 应用还可用观测云 APM(OpenTelemetry/ddtrace 接入)并把 trace_id 注入日志,实现链路-日志双向跳转 。

总结

标准库 logging 的要点串起来就是:模块级 logger、dictConfig 集中配置、python-json-logger 出 JSON、exception 带堆栈、轮转交给 logrotate、采集交给 DataKit。掌握这套,再看 Loguru/Structlog 只是"换一种更省事的写法"。

常见问题(FAQ)

Q:为什么我的 INFO 日志不输出?
logging 默认级别是 WARNING。需要 logger.setLevel(logging.DEBUG/INFO) 或在 dictConfig 中显式配置级别;注意 logger 和 handler 两级都要放行。

Q:getLogger(name) 和 getLogger("myapp") 有什么区别?
__name__ 自动取模块全名形成层级(如 myapp.services.order),可继承父 logger 配置,适合中大型项目;固定字符串适合小项目或想统一命名的场景。

Q:同一个 logger 为什么打印了两遍?
logger 默认向 root logger 传播(propagate=True),root 又配了 handler 就会重复输出。给自定义 logger 设 logger.propagate = False 即可。

Q:标准库、Loguru、Structlog 怎么选?
写库/需要最大兼容性用标准库;求省心好用选 Loguru;要强结构化与上下文绑定选 Structlog。详细对比见本系列《Python 日志库六款对比》。


系列阅读:Loguru 实战指南Python 日志最佳实践十条

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台