logging 模块的设计非常灵活,其核心思想是分层的配置,我们将从最基础的配置开始,逐步深入到各个组件和参数。

核心组件与概念
在讲解具体参数之前,先理解 logging 的四个核心组件,这有助于你明白参数是如何协同工作的。
-
Loggers (记录器):
- 作用:这是你与日志系统交互的主要接口,你在代码中通过
logger = logging.getLogger(__name__)获取一个记录器实例。 - 核心参数:
name: 记录器的名称,通常使用模块的__name__,这样日志的来源就一目了然,记录器可以构成一个层级结构,用点号分隔('my_app.models'是'my_app'的子记录器)。level: 记录器处理的最低日志级别,如果设置为logging.INFO,INFO、WARNING、ERROR和CRITICAL级别的日志都会被处理,而DEBUG级别的会被忽略。注意: 即使日志级别设置正确,如果它上层的 Handler 也设置了级别,日志仍然可能被过滤掉。
- 作用:这是你与日志系统交互的主要接口,你在代码中通过
-
Handlers (处理器):
- 作用:将 Logger 产生的日志记录发送到指定的目的地,一个 Logger 可以附加多个 Handler,实现日志的多渠道输出(同时输出到文件和控制台)。
- 核心参数:
level: 与 Logger 类似,Handler 也有自己的级别,它只处理级别高于或等于此设置的日志记录,这提供了更精细的控制。formatter: 指定一个Formatter对象,用于格式化日志记录的输出样式。filters: 指定过滤器,用于对日志记录进行更复杂的过滤。
-
Formatters (格式化器):
(图片来源网络,侵删)- 作用:定义日志记录的最终输出格式。
- 核心参数:
fmt: 格式化字符串,它使用各种占位符来定义日志内容的结构。datefmt: 日期和时间的格式化字符串,遵循time.strftime的规范。
-
Filters (过滤器):
- 作用:提供更细粒度的日志过滤,可以基于日志的任何属性(如
name,level,message等)进行判断。 - 核心参数:
name: 只记录与指定名称匹配的 Logger 的日志。- 自定义过滤逻辑:你可以通过继承
logging.Filter类并重写filter方法来实现复杂的过滤逻辑。
- 作用:提供更细粒度的日志过滤,可以基于日志的任何属性(如
日志级别
这是最基础也是最重要的参数之一,它决定了哪些信息应该被记录。
| 级别 | 数值 | 描述 |
|---|---|---|
CRITICAL |
50 | 严重错误,表明程序本身可能无法继续运行 |
ERROR |
40 | 发生了错误,但程序仍可能继续运行 |
WARNING |
30 | 出现了意外情况,但未来可能发生问题(默认级别) |
INFO |
20 | 程序正常运行中的一些关键信息 |
DEBUG |
10 | 详细的调试信息,用于诊断问题 |
使用示例:
import logging
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG) # 设置Logger的级别为DEBUG
# 创建一个Handler,并设置其级别为INFO
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 创建一个Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
# 将Handler添加到Logger
logger.addHandler(console_handler)
# 现在开始记录日志
logger.debug("这是一个调试信息,不会被控制台打印出来") # 因为Handler级别是INFO
logger.info("这是一个信息,会被打印出来")
logger.warning("这是一个警告,会被打印出来")
输出结果:
2025-10-27 10:30:00,123 - my_app - INFO - 这是一个信息,会被打印出来
2025-10-27 10:30:00,124 - my_app - WARNING - 这是一个警告,会被打印出来
常用内置 Handler 及其参数
logging 模块提供了多种内置的 Handler,方便快捷地实现日志输出。
StreamHandler
将日志输出到类似 sys.stdout 或 sys.stderr 的流,通常就是你的控制台。
- 构造函数参数:
stream: 指定输出流,默认是sys.stderr,可以传入sys.stdout。
FileHandler
将日志输出到磁盘文件。
- 构造函数参数:
filename: 日志文件的名称。mode: 文件打开模式,默认是'a'(追加)。encoding: 文件编码,默认是locale.getpreferredencoding(False)。delay: 如果为True,则文件会在第一次写入时才打开,默认为False。
RotatingFileHandler
当日志文件达到指定大小时,它会自动进行轮转(备份),并创建一个新的日志文件。
- 构造函数参数:
filename,mode,encoding,delay: 同FileHandler。maxBytes: 单个日志文件的最大字节数,当日志文件超过这个大小时,进行轮转。backupCount: 保留的备份文件数量,设置为 5,则会保留.log.1,.log.2, ...,.log.5共 5 个备份文件。
TimedRotatingFileHandler
根据时间(如每天、每小时)来轮转日志文件。
- 构造函数参数:
filename,mode,encoding,delay: 同FileHandler。when: 轮转的时间单位。'S': 秒'M': 分钟'H': 小时'D': 天'W0-'W6': 每周 (0代表周一,6代表周日)'midnight': 每天午夜
interval: 轮转的时间间隔。when='H', interval=6表示每6小时轮转一次。backupCount: 保留的备份文件数量。atTime: 指定第一次轮转的具体时间点,通常配合when='midnight'使用。
Formatter 格式化参数
这是自定义日志内容的关键。fmt 参数是一个字符串,其中包含各种占位符。
| 占位符 | 描述 |
|---|---|
%(asctime)s |
日志事件发生的时间,默认格式为 YYYY-MM-DD HH:MM:SS,mmm |
%(name)s |
Logger 的名称 |
%(levelname)s |
日志级别名称 (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
%(message)s |
日志记录的文本内容 |
%(module)s |
生成日志记录的模块名 |
%(funcName)s |
生成日志记录的函数名 |
%(lineno)d |
生成日志记录的源代码行号 |
%(thread)d |
线程ID |
%(process)d |
进程ID |
%(processName)s |
进程名 |
relativeCreated |
日志记录被创建的相对时间(毫秒),相对于 logging 模块被加载的时间 |
示例:
formatter = logging.Formatter(
fmt='[%(asctime)s] [%(levelname)-8s] [%(filename)s:%(lineno)d] - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# %(levelname)-8s 中的 -8s 表示左对齐,并占8个字符宽度,使输出更整齐
实用配置函数
为了简化配置,logging 模块提供了几个高阶函数。
logging.basicConfig(**kwargs)
这是最简单的配置方法,适合于简单的脚本或快速原型。
常用参数:
filename: 指定日志文件名,日志将写入文件而不是控制台。filemode: 指定文件打开模式,默认为'a'。format: 指定日志格式。datefmt: 指定日期格式。level: 设置根 Logger 的级别。stream: 指定输出流。
注意:basicConfig 只能调用一次,多次调用,只有第一次的配置会生效。
import logging
# 只需一行代码即可完成基本配置
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
filename='app.log',
filemode='w' # 覆盖模式
)
logging.debug("This is a debug message")
logging.info("This is an info message")
logging.config.dictConfig(config)
这是最强大、最灵活的配置方式,特别适合于大型应用程序,你通过一个字典来定义整个日志系统的配置。
示例字典结构:
import logging.config
import os
LOGGING_CONFIG = {
'version': 1, # 必须是1
'disable_existing_loggers': False, # 是否禁用已存在的loggers
'formatters': {
'detailed': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s'
},
'simple': {
'format': '%(levelname)s: %(message)s'
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'INFO',
'formatter': 'simple',
'stream': 'ext://sys.stdout' # ext:// 表示引用标准库对象
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'level': 'DEBUG',
'formatter': 'detailed',
'filename': os.path.join('logs', 'app.log'),
'maxBytes': 1024*1024, # 1MB
'backupCount': 5,
'encoding': 'utf8'
}
},
'loggers': {
'my_app': { # 自定义logger的配置
'level': 'DEBUG',
'handlers': ['console', 'file'], # 使用两个handler
'propagate': False # 是否将日志传递给父logger,这里是根logger
}
},
'root': { # 根logger的配置
'level': 'INFO',
'handlers': ['console']
}
}
# 应用配置
logging.config.dictConfig(LOGGING_CONFIG)
# 使用
logger = logging.getLogger('my_app')
logger.debug("This debug message will only go to the file.")
logger.info("This info message will go to both console and file.")
最佳实践总结
- 使用
__name__作为 Logger 名称:logger = logging.getLogger(__name__),这能清晰地追踪日志来源。 - 在模块级别创建 Logger:在模块的顶层创建 Logger,而不是在函数内部,这样可以避免重复创建 Logger 实例。
- 分离关注点:将日志配置代码与业务逻辑代码分离,通常在应用程序的入口点(如
main.py或settings.py)进行一次性配置。 - 优先使用
dictConfig:对于任何非 trivial 的项目,使用dictConfig或fileConfig来管理日志配置,这比硬编码basicConfig更灵活、更易于维护。 - 为不同的 Logger 设置不同的级别:可以为第三方库的 Logger 设置一个较高的级别(如
WARNING),以避免它们的日志信息淹没你自己的日志。# 在配置中 'loggers': { 'some_library': { 'level': 'WARNING', 'propagate': False } } - 使用
RotatingFileHandler或TimedRotatingFileHandler:在生产环境中,避免使用普通的FileHandler,因为它会无限增长文件,使用轮转处理器来管理日志文件大小和数量。
