为什么使用 logging 而不是 print()?
在学习 logging 之前,先理解它的优势:

| 特性 | print() |
logging |
|---|---|---|
| 日志级别 | 只有一种输出方式 | 支持多种级别(DEBUG, INFO, WARNING, ERROR, CRITICAL) |
| 性能 | 每次执行都会产生 I/O 开销 | 可以通过级别过滤日志,减少不必要的字符串操作和 I/O |
| 可配置性 | 硬编码在代码中 | 可以在运行时通过配置文件或代码动态修改行为 |
| 输出目标 | 默认标准输出 | 可同时输出到控制台、文件、网络等多种目标 |
| 格式化 | 格式固定 | 可自定义日志格式,包含时间、模块名、行号等丰富信息 |
| 调试 | 需要手动删除或注释 | 可以轻松调整日志级别来控制输出信息量 |
日志的五个核心级别
logging 模块定义了五个标准的日志级别,从低到高依次是:
DEBUG: 最详细的日志信息,通常用于调试问题。INFO: 正常的信息,表明程序按预期运行。WARNING: 警告信息,表示发生了意外,但程序仍在正常工作。ERROR: 错误信息,表示程序某部分功能无法正常工作。CRITICAL: 严重错误,表示程序本身可能无法继续运行。
重要规则:只记录级别大于或等于当前设置的日志级别的消息,如果级别设置为 INFO,INFO, WARNING, ERROR, CRITICAL 的日志都会被记录,而 DEBUG 的日志会被忽略。
logging 的基本组件
要理解 logging 的工作方式,需要了解几个核心组件:
- Logger (记录器):这是你直接打交道的对象,通过
logging.getLogger(name)获取,你可以使用不同的Logger对象来组织日志(为应用的每个模块创建一个Logger)。 - Handler (处理器):将
Logger生成的日志记录发送到指定的目标。StreamHandler: 输出到控制台 (默认)。FileHandler: 输出到文件。RotatingFileHandler: 输出到文件,并在文件达到大小时进行轮转。
- Formatter (格式化器):定义日志记录的最终输出格式,可以包含时间、日志级别、消息内容等。
- Filter (过滤器):更精细地控制哪些日志记录应该被输出。
工作流程:你的代码调用 logger.info(...) -> Logger 处理 -> 如果级别匹配,则传递给所有已添加的 Handler -> Handler 使用 Formatter 格式化日志 -> 输出到目标。

基本使用方法
快速上手(不推荐用于生产环境)
最简单的方式是直接使用 logging 模块的顶层函数,这种方式配置简单,但不够灵活。
import logging
# 默认情况下,日志级别为 WARNING,只显示 WARNING 及以上级别的日志
# 并且默认输出格式简单
logging.debug("这是一个调试信息") # 不会显示
logging.info("这是一个普通信息") # 不会显示
logging.warning("这是一个警告") # 会显示
logging.error("这是一个错误") # 会显示
logging.critical("这是一个严重错误") # 会显示
输出:
WARNING:root:这是一个警告
ERROR:root:这是一个错误
CRITICAL:root:这是一个严重错误
可以看到,root 是默认的 Logger 名称,格式也比较简单。
基础配置(推荐)
使用 logging.basicConfig() 是一种简单且常用的配置方法,适用于小型脚本或快速原型开发。

import logging
# 进行基本配置
logging.basicConfig(
level=logging.DEBUG, # 设置日志级别为 DEBUG,显示所有级别的日志
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # 自定义格式
datefmt='%Y-%m-%d %H:%M:%S', # 日期时间格式
filename='app.log', # 输出到文件
filemode='w' # 文件模式 ('w'为覆盖, 'a'为追加)
)
# 现在所有级别的日志都会被记录到 app.log 文件中
logging.debug("这是一个调试信息")
logging.info("这是一个普通信息")
logging.warning("这是一个警告")
logging.error("这是一个错误")
logging.critical("这是一个严重错误")
执行后,app.log 文件内容如下:
2025-10-27 15:30:00 - root - DEBUG - 这是一个调试信息
2025-10-27 15:30:00 - root - INFO - 这是一个普通信息
2025-10-27 15:30:00 - root - WARNING - 这是一个警告
2025-10-27 15:30:00 - root - ERROR - 这是一个错误
2025-10-27 15:30:00 - root - CRITICAL - 这是一个严重错误
面向对象的配置(最灵活、最推荐)
对于复杂的应用程序,推荐使用面向对象的方式配置 Logger、Handler 和 Formatter,这种方式提供了最大的灵活性。
import logging
# 1. 创建一个 logger 实例
# 通常使用 __name__ 作为 logger 的名称,这样可以方便地追踪日志来源的模块
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # 设置 logger 的日志级别
# 2. 创建 Handler
# 2.1 创建一个用于输出到控制台的 Handler
ch = logging.StreamHandler()
ch.setLevel(logging.INFO) # 设置这个 Handler 的日志级别为 INFO
# 2.2 创建一个用于输出到文件的 Handler
fh = logging.FileHandler('app.log')
fh.setLevel(logging.DEBUG) # 设置这个 Handler 的日志级别为 DEBUG
# 3. 创建 Formatter 并绑定到 Handler
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
fh.setFormatter(formatter)
# 4. 将 Handler 添加到 logger
logger.addHandler(ch)
logger.addHandler(fh)
# 5. 使用 logger 记录日志
logger.debug('这是一个调试信息,只会出现在文件中')
logger.info('这是一个普通信息,会同时出现在控制台和文件中')
logger.warning('这是一个警告,会同时出现在控制台和文件中')
logger.error('这是一个错误,会同时出现在控制台和文件中')
工作原理分析:
logger.setLevel(logging.DEBUG):logger接收所有级别的日志。ch.setLevel(logging.INFO): 控制台处理器只处理INFO及以上的日志。fh.setLevel(logging.DEBUG): 文件处理器处理所有级别的日志。
输出结果:
- 控制台输出 (只显示
INFO及以上):2025-10-27 15:35:00 - __main__ - INFO - 这是一个普通信息,会同时出现在控制台和文件中 2025-10-27 15:35:00 - __main__ - WARNING - 这是一个警告,会同时出现在控制台和文件中 2025-10-27 15:35:00 - __main__ - ERROR - 这是一个错误,会同时出现在控制台和文件中 app.log文件内容 (显示所有级别):2025-10-27 15:35:00 - __main__ - DEBUG - 这是一个调试信息,只会出现在文件中 2025-10-27 15:35:00 - __main__ - INFO - 这是一个普通信息,会同时出现在控制台和文件中 2025-10-27 15:35:00 - __main__ - WARNING - 这是一个警告,会同时出现在控制台和文件中 2025-10-27 15:35:00 - __main__ - ERROR - 这是一个错误,会同时出现在控制台和文件中
常用格式说明
在 Formatter 中,你可以使用以下占位符来定制日志格式:
| 占位符 | 含义 |
|---|---|
%(name)s |
Logger 的名称 (__main__) |
%(levelname)s |
日志级别名称 (DEBUG, INFO) |
%(message)s |
|
%(asctime)s |
可读的时间,如 2025-10-27 15:30:00,123 |
%(filename)s |
模块文件名 |
%(funcName)s |
调用日志的函数名 |
%(lineno)d |
调用日志的代码行号 |
%(process)d |
进程ID |
%(thread)d |
线程ID |
完整示例
下面是一个将所有概念结合起来的完整示例。
import logging
import time
def setup_logger():
"""配置并返回一个 logger 实例"""
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
# 防止重复添加 Handler
if not logger.handlers:
# 1. 控制台 Handler (只显示 WARNING 及以上)
ch = logging.StreamHandler()
ch.setLevel(logging.WARNING)
c_formatter = logging.Formatter('%(levelname)s - %(message)s')
ch.setFormatter(c_formatter)
# 2. 文件 Handler (记录所有级别)
fh = logging.FileHandler('application.log', encoding='utf-8')
fh.setLevel(logging.DEBUG)
f_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s')
fh.setFormatter(f_formatter)
# 3. 添加 Handler 到 logger
logger.addHandler(ch)
logger.addHandler(fh)
return logger
# --- 主程序 ---
if __name__ == '__main__':
# 获取 logger 实例
logger = setup_logger()
logger.debug('应用启动,开始执行主逻辑...')
logger.info('用户 "Alice" 登录成功')
logger.warning('磁盘空间剩余不足 10%')
try:
result = 1 / 0
except ZeroDivisionError:
logger.error('发生除零错误!', exc_info=True) # exc_info=True 会记录完整的堆栈跟踪信息
logger.critical('数据库连接丢失!')
logger.info('应用准备关闭。')
执行后,控制台输出:
WARNING - 磁盘空间剩余不足 10%
ERROR - 发生除零错误!
Traceback (most recent call last):
File "your_script_name.py", line 29, in <module>
result = 1 / 0
ZeroDivisionError: division by zero
CRITICAL - 数据库连接丢失!
INFO - 应用准备关闭。
执行后,application.log 文件内容:
2025-10-27 16:00:00,123 - my_app - DEBUG - my_app:18 - 应用启动,开始执行主逻辑...
2025-10-27 16:00:00,124 - my_app - INFO - my_app:19 - 用户 "Alice" 登录成功
2025-10-27 16:00:00,124 - my_app - WARNING - my_app:20 - 磁盘空间剩余不足 10%
2025-10-27 16:00:00,124 - my_app - ERROR - my_app:29 - 发生除零错误!
Traceback (most recent call last):
File "your_script_name.py", line 29, in <module>
result = 1 / 0
ZeroDivisionError: division by zero
2025-10-27 16:00:00,124 - my_app - CRITICAL - my_app:35 - 数据库连接丢失!
2025-10-27 16:00:00,124 - my_app - INFO - my_app:37 - 应用准备关闭。
- 对于简单脚本:使用
logging.basicConfig()是最快的方式。 - 对于任何非 trivial 的项目:强烈推荐使用面向对象的配置方法 (
logger,handler,formatter),因为它灵活、可扩展且易于维护。 - 始终使用
logger = logging.getLogger(__name__)来获取 logger,这样可以清晰地知道日志的来源。 - 合理设置日志级别,既能获得足够的信息用于调试,又不会被过多的日志淹没。
