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

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

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

Python logging 是 Python 标准库内置的日志模块,通过 Logger(记录器)、Handler(输出目标)、Formatter(格式)三要素的组合,提供可配置的日志能力。 它的好处是无需额外安装、广泛用于第三方库,学会它等于掌握了 Python 日志的"通用语言"。

核心要点速览

  • 脚本可直接用 logging.info();模块化项目推荐用 logging.getLogger(__name__) 创建模块级 logger;
  • 默认级别是 WARNING——DEBUG/INFO 不输出不是 bug,是级别没配;
  • JSON 可用 python-json-logger 或自定义 Formatter;文本也可用于生产,按解析需求选择;
  • 记录异常务必带 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)
logger.propagate = False  # 本示例由 myapp 自己管理输出,避免再次交给 root

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 决定"发不发",Logger 有效级别决定是否创建记录,Handler 级别再过滤输出;已创建记录传播到祖先 Handler 时,不重新经过祖先 Logger 的级别/过滤器。

dictConfig:集中式配置

项目变大后,推荐用 logging.config.dictConfig 把配置集中到一个字典(或单独模块)中,应用启动时一次性加载。下面是前面手动 Handler 配置的替代方案,不要叠加执行;由 root 管理输出时,普通模块 logger 应保留传播:

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 Formatter,但可用 json 模块自定义;也可安装 python-json-logger:

pip install python-json-logger
from pythonjsonlogger.json import JsonFormatter

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

上例使用当前版本的导入路径,并引用前面手动配置的 stdout_handler;使用 dictConfig 时应在其中配置 formatter。默认 asctime 不带时区偏移,跨主机采集前需统一时区或指定明确的时间格式。

输出:

{"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 记录;hook 本身不执行退出,也不覆盖线程、asyncio 或框架捕获的全部异常:

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 需配合重新打开文件,如 Unix 下的 WatchedFileHandler;普通 FileHandler 不会自动跟随 rename。多进程不要各自轮转同一文件,可集中交给单个写端或容器运行时。

接入观测云

应用先输出一条普通 JSON 日志与一条带 exc_info 的异常。接入观测云时,按 DataKit 日志配置采集文件或容器输出,核对实际级别字段、时间及异常堆栈;不同 formatter 的字段名可能不同,需要在 Pipeline 中映射。验证后再按日志检测文档设置错误数量与通知规则。若应用已有 Trace,应把同一请求的 trace_id 写入日志并检查关联结果。

总结

标准库 logging 的要点串起来就是:模块级 logger、dictConfig 集中配置、python-json-logger 出 JSON、exception 带堆栈、按写入方式选择轮转方案、采集交给 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 就会重复输出。可移除重复 Handler,或在确认本地 Handler 足够时设 propagate=False,避免一并丢失所需祖先输出。

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


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

[1]: 观测云文档《DataKit 日志采集综述》https://docs.guance.com/integrations/datakit-logging/
[2]: 观测云文档《日志采集》https://docs.guance.com/logs/collection/
[3]: 观测云文档《日志查看器》https://docs.guance.com/logs/explorer/
[4]: 观测云文档《监控》https://docs.guance.com/monitoring/
[5]: 观测云《APM 平台》https://www.guance.com/product/application-performance-monitoring

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

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

立即开始

选择观测云版本

代码托管平台