《别再用 print 打日志了:深入理解 Python logging 的底层原理与最佳实践》

2025博客之星年度评选已开启 10w+人浏览 2.9k人参与

《别再用 print 打日志了:深入理解 Python logging 的底层原理与最佳实践》

在我这些年教授 Python、带团队做项目、审查代码的过程中,“日志”是我最常看到被忽视、却又最容易暴露开发者水平的部分。尤其是初学者,几乎都会经历一个阶段:用 print() 打天下

调试用 print,排查问题用 print,甚至线上环境也用 print。

但随着项目规模扩大、团队协作加深、系统复杂度提升,你会发现:

print 是玩具,logging 才是生产力工具。

这篇文章,我会从基础到高级,从原理到实践,带你彻底理解:

  • 为什么 print 不适合作为日志工具
  • logging 模块到底做了哪些 print 做不到的事
  • logging 的底层架构与运行机制
  • 如何在真实项目中构建专业、可扩展、可维护的日志体系
  • 常见坑与最佳实践

无论你是刚入门 Python 的新手,还是正在带团队的资深开发者,这篇文章都能帮你建立起对“日志系统”的正确认知。


一、为什么 print 不适合打日志?

很多人第一次听到“不要用 print 打日志”时,第一反应是:

print 不就是输出信息吗?我调试的时候用得好好的,为什么不能用?

下面我们从工程角度逐条拆解。


1. print 没有日志等级(Log Level)

在真实项目中,你需要区分:

  • DEBUG:调试信息
  • INFO:正常运行信息
  • WARNING:潜在问题
  • ERROR:错误但程序还能继续
  • CRITICAL:致命错误

print 只有一种输出方式,无法表达语义。

而 logging:

logging.debug("调试信息")
logging.info("正常信息")
logging.warning("警告")
logging.error("错误")
logging.critical("严重错误")

日志等级不仅是“分类”,更是“过滤器”。
你可以在开发环境输出 DEBUG,在生产环境只输出 WARNING 以上。

print 做不到。


2. print 无法控制输出位置

print 只能输出到 stdout。

但真实项目需要:

  • 输出到控制台
  • 输出到文件
  • 输出到多个文件
  • 输出到远程日志服务器
  • 输出到 ELK / Loki / Splunk
  • 输出到邮件、钉钉、企业微信

logging 可以通过 Handler 灵活配置:

logging.FileHandler("app.log")
logging.StreamHandler()
logging.handlers.RotatingFileHandler()
logging.handlers.SMTPHandler()

print 做不到。


3. print 无法格式化日志结构

生产级日志必须包含:

  • 时间戳
  • 日志等级
  • 模块名
  • 行号
  • 线程/进程 ID
  • 消息内容

logging 可以通过 Formatter 完成:

"%(asctime)s - %(levelname)s - %(name)s - %(message)s"

print 做不到。


4. print 无法进行日志轮转(Log Rotation)

线上日志不能无限增长,否则:

  • 占满磁盘
  • 导致服务崩溃
  • 无法归档分析

logging 提供:

  • RotatingFileHandler(按大小轮转)
  • TimedRotatingFileHandler(按时间轮转)

print 做不到。


5. print 无法做到线程安全 / 进程安全

多线程、多进程环境下,print 输出会互相覆盖、交错。

logging 内部使用锁机制,保证线程安全。

print 做不到。


6. print 无法统一管理、无法动态调整

你无法做到:

  • 某个模块单独调高日志等级
  • 某个模块单独输出到文件
  • 某个模块关闭日志

logging 可以通过 Logger 层级结构实现。

print 做不到。


总结一句话:

print 是调试工具,logging 才是日志系统。


二、logging 模块到底做了什么?(底层架构解析)

很多人用 logging 只是停留在:

import logging
logging.basicConfig(...)
logging.info("hello")

但 logging 的底层架构其实非常优雅,甚至可以说是“工程美学”的典范。


logging 的四大核心组件

Python logging 模块由四个核心对象组成:

组件作用
Logger日志入口,负责接收日志请求
Handler决定日志输出到哪里
Formatter决定日志长什么样
Filter决定哪些日志能通过

它们的关系如下:

Logger → Handler → Formatter
           ↑
         Filter

1. Logger:日志入口

每个模块通常会创建自己的 logger:

logger = logging.getLogger(__name__)

Logger 有层级结构:

root
 ├── app
 │    ├── app.db
 │    ├── app.api
 │    └── app.utils

子 logger 会继承父 logger 的 Handler。

这意味着:

  • 你可以为某个模块单独配置日志
  • 也可以统一管理整个项目的日志

print 完全没有这种能力。


2. Handler:日志输出位置

常见 Handler:

  • StreamHandler(控制台)
  • FileHandler(文件)
  • RotatingFileHandler(按大小轮转)
  • TimedRotatingFileHandler(按时间轮转)
  • SMTPHandler(邮件)
  • HTTPHandler(发送到 HTTP 服务)
  • SocketHandler(发送到远程日志服务器)

你可以给一个 Logger 配置多个 Handler:

logger.addHandler(console_handler)
logger.addHandler(file_handler)

print 只能输出到 stdout。


3. Formatter:日志格式

你可以自定义日志格式:

formatter = logging.Formatter(
    "%(asctime)s - %(levelname)s - %(name)s - %(message)s"
)

甚至可以输出 JSON:

formatter = logging.Formatter(
    '{"time": "%(asctime)s", "level": "%(levelname)s", "msg": "%(message)s"}'
)

这对接 ELK、Loki 非常重要。


4. Filter:日志过滤器

你可以过滤:

  • 某个模块的日志
  • 某个等级的日志
  • 某个关键字的日志

例如:

class KeywordFilter(logging.Filter):
    def filter(self, record):
        return "支付" in record.msg

print 完全做不到。


三、logging 的底层实现机制(你可能第一次看到)

下面我们深入一点,看看 logging 是如何工作的。


1. Logger.log() 的执行流程

当你调用:

logger.info("hello")

logging 内部会执行:

Logger.info()
    ↓
Logger._log()
    ↓
创建 LogRecord 对象
    ↓
Logger.handle()
    ↓
Logger.callHandlers()
    ↓
Handler.handle()
    ↓
Handler.emit()

其中最关键的是:

LogRecord 对象

它包含:

  • 消息内容
  • 日志等级
  • 文件名
  • 行号
  • 函数名
  • 线程 ID
  • 进程 ID
  • 时间戳

print 只有字符串。


2. Handler.emit() 是真正输出日志的地方

不同 Handler 的 emit() 不同:

  • StreamHandler.emit() → 输出到 stdout
  • FileHandler.emit() → 写入文件
  • RotatingFileHandler.emit() → 判断文件大小并轮转
  • TimedRotatingFileHandler.emit() → 判断时间并轮转

这就是 logging 能扩展的原因。


3. logging 是线程安全的

Handler 内部使用了锁:

self.lock.acquire()
try:
    stream.write(msg)
finally:
    self.lock.release()

print 没有锁。


四、如何构建一个专业的日志系统(实战案例)

下面我给你一个真实项目中常用的日志配置方案。


1. 日志配置文件(logging.conf)

[loggers]
keys=root,app

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=WARNING
handlers=consoleHandler

[logger_app]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=app
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=logging.handlers.TimedRotatingFileHandler
level=INFO
formatter=simpleFormatter
args=("logs/app.log", "midnight", 1, 7)

[formatter_simpleFormatter]
format=%(asctime)s - %(levelname)s - %(name)s - %(message)s

2. 加载配置

import logging.config

logging.config.fileConfig("logging.conf")
logger = logging.getLogger("app")

3. 使用日志

logger.debug("调试信息")
logger.info("系统启动")
logger.warning("磁盘空间不足")
logger.error("数据库连接失败")
logger.critical("系统崩溃")

五、常见坑与最佳实践


1. 不要在库中使用 basicConfig()

它会污染全局配置。


2. 不要在库中使用 root logger

库应该使用:

logger = logging.getLogger(__name__)

3. 不要在日志中拼接字符串

错误写法:

logger.info("用户 %s 登录" % username)

正确写法:

logger.info("用户 %s 登录", username)

原因:

  • logging 会延迟格式化,提高性能
  • 避免无意义的字符串拼接

4. 不要重复添加 Handler

否则会出现重复日志。


5. 异步日志(高级技巧)

如果日志量很大,可以使用:

  • QueueHandler
  • QueueListener

实现异步日志,提高性能。


六、前沿视角:日志系统的未来趋势

随着云原生、微服务、分布式系统的发展,日志系统也在演进:

  • JSON 结构化日志
  • OpenTelemetry 统一追踪标准
  • 日志、指标、链路追踪三位一体
  • 日志采集与可观测性平台(Loki、ELK、Datadog)

Python logging 依然是核心基础设施。


七、总结与互动

总结

  • print 适合调试,不适合生产
  • logging 是一个完整的日志框架
  • logging 的底层架构优雅且可扩展
  • 通过 Logger / Handler / Formatter / Filter 可以构建专业日志系统
  • 了解 logging 的底层机制能帮助你写出更高质量的代码

互动

我很想听听你的经验:

  • 你在项目中遇到过哪些“日志相关的坑”?
  • 你是否踩过 print 的坑?
  • 你现在的日志系统是如何设计的?

欢迎在评论区分享你的故事,我们一起交流、一起成长。


如果你愿意,我可以继续为你写:

  • logging 高级技巧(异步日志、JSON 日志、上下文日志)
  • logging 与 FastAPI / Django 的最佳实践
  • 如何构建企业级日志系统(ELK / Loki)

告诉我你想继续深入哪个方向,我可以马上展开。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铭渊老黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值