← 返回题目列表

Python logging 模块怎么用?为什么不建议直接用 print 打日志?

高频 中等 第 13 / 27 题 更新于 2026/07/31
logging日志HandlerFormatter

简化版

logging 是 Python 标准日志框架,支持日志级别、Logger 层级、Handler 输出目标、Formatter 格式化和 Filter 过滤。相比 print,它能控制级别、输出到文件或日志系统、带时间和上下文、在生产环境统一管理。

详细版

print 适合临时调试,不适合生产日志。生产日志需要区分 DEBUGINFOWARNINGERROR,需要写入文件、标准输出、日志平台,还要带请求 id、模块名、异常堆栈等上下文。logging 正是为这些需求设计的。

常见写法是每个模块创建自己的 logger:

import logging

logger = logging.getLogger(__name__)

def pay(order_id: str):
    logger.info("pay order %s", order_id)

应用入口统一配置 handler、格式和级别;库代码只获取 logger,不随意 basicConfig()。面试回答要强调:logger 负责产生日志,handler 负责输出到哪里,formatter 负责长什么样,level 负责过滤。

完整版教学

一、为什么 print 不适合生产日志

print 只能把文本写到标准输出,缺少级别、来源、时间、堆栈和输出路由。线上系统需要知道“什么时候、哪个模块、哪个请求、发生了什么、严重程度如何”。这些信息靠散落的 print 很难统一。

例如同样一条错误:

error

和结构化一点的日志:

2026-07-31 12:00:01 ERROR payment.service request_id=abc order_id=1001 timeout

后者才能支持检索、告警和排障。日志不是给当下的你看一眼,而是给未来排查问题的人留证据。

二、logging 的核心对象

logging 有四个核心概念:Logger、Handler、Formatter、Filter。Logger 是业务代码拿来调用的对象,Handler 决定输出到控制台、文件或网络,Formatter 决定格式,Filter 决定是否进一步过滤或补上下文。

组件作用例子
Logger产生日志事件logging.getLogger(__name__)
Handler输出目标控制台、文件、Syslog
Formatter输出格式时间、级别、模块名
Filter过滤/补充字段request_id、tenant

一个 logger 可以挂多个 handler。比如 ERROR 日志写文件,INFO 日志写标准输出,关键错误再发到告警系统。

三、日志级别怎么选

日志级别表达重要程度。DEBUG 给开发排查细节,INFO 记录关键业务路径,WARNING 表示异常但可恢复,ERROR 表示当前操作失败,CRITICAL 表示系统级严重问题。

DEBUG < INFO < WARNING < ERROR < CRITICAL

数字化理解:一个每秒 1000 次请求的接口,如果每次都打 5 条 INFO,一天就是 4.32 亿条日志,成本和检索压力都很高。日志级别不是越低越好,要根据排障价值和成本取舍。

四、为什么推荐 getLogger(__name__)

__name__ 通常是模块路径,比如 app.payment.service。用它创建 logger,可以形成层级结构,应用入口能统一控制某个包或模块的日志级别。

logger = logging.getLogger(__name__)
app
  app.payment
    app.payment.service

如果支付模块太吵,可以单独把 app.payment 调成 WARNING,而不影响其他模块。相比所有地方都用 root logger,这种层级更适合大型项目。

库代码只拿 logger 记录事件,配置权应该交给应用入口。

五、异常日志怎么打才有用

捕获异常时不要只写异常字符串,否则堆栈丢失。logger.exception() 会在 except 块里自动带上当前异常堆栈;或者用 logger.error(..., exc_info=True)

try:
    charge(order)
except TimeoutError:
    logger.exception("charge timeout, order_id=%s", order.id)
    raise

错误日志还要带业务上下文。只有 timeout 不够,至少要有 order_id、user_id、request_id、外部服务名等。否则排查时只能在海量日志里猜。

六、生产配置要注意什么

Web 服务通常把日志输出到标准输出,由容器或平台采集。传统部署可能写滚动文件。无论哪种方式,都要避免重复 handler、重复打印、日志文件无限增长、敏感信息泄露。

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
)

复杂项目更适合用 dictConfig 统一配置。密码、token、身份证、手机号等敏感字段要脱敏;日志既是排障工具,也是安全资产。

七、常见误区与追问

  • 误区:logging 只是高级版 print。 它是日志事件系统,支持级别、层级、输出目标、格式和过滤。
  • 误区:库代码应该自己配置 logging。 库应只创建 logger,最终格式和 handler 交给应用决定。
  • 误区:异常日志写 str(e) 就够了。 没有堆栈很难定位,应使用 logger.exceptionexc_info=True
  • 追问:为什么日志会重复打印? 常见原因是父子 logger 传播和多个 handler 重复挂载。
  • 追问:生产中日志输出到文件还是 stdout? 容器化环境通常 stdout,由平台采集;传统部署可用滚动文件。
  • 追问:日志里能不能打印请求参数? 可以但要脱敏,避免泄露 token、密码和隐私数据。
  • 追问:如何给日志加 request_id? 可用 Filter、LoggerAdapter、contextvars 或框架中间件注入上下文。

八、加强记忆

logging 记成“事件、级别、去向、格式、上下文”。业务代码用 getLogger(__name__) 产生日志事件,应用入口统一配置 handler 和 formatter;异常用 logger.exception 保留堆栈;生产日志要考虑成本、重复、脱敏和 request_id。这样回答,比简单说“print 不能控制级别”厚得多。