这里的“追加”通常有两个层面的含义:
- 追加到文件:这是最常见的需求,即每次程序运行时,新的日志信息都添加到日志文件的末尾,而不是覆盖旧内容。
- 日志处理器追加到日志器:在配置日志时,可以向一个日志器(Logger)对象添加多个处理器(Handler),让同一条日志信息可以同时输出到不同的地方(如文件、控制台、网络等)。
下面我将分别对这两个方面进行详细说明。
追加到文件 (FileHandler)
这是“追加”最直接的理解。logging 模块中的 FileHandler 默认就是以追加模式打开文件的。
核心要点
- 默认行为:当你创建一个
FileHandler并指定一个文件名时,它会以'a'(append) 模式打开该文件,如果文件不存在,它会自动创建。 - 如何确认:你可以查看
FileHandler的源码,或者在创建后检查其baseFilename和mode属性。
示例代码
这个例子会清晰地展示日志是如何被追加到文件中的。
import logging
import time
import os
# 定义日志文件名
LOG_FILE = 'my_app.log'
# --- 第一次运行 ---
print("--- 第一次运行,创建日志文件并写入内容 ---")
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOG_FILE, mode='a', encoding='utf-8'), # 显式指定 mode='a'
logging.StreamHandler() # 同时输出到控制台
]
)
logger = logging.getLogger(__name__)
logger.info("这是第一条日志信息 (第一次运行)")
logger.warning("这是一个警告信息 (第一次运行)")
time.sleep(1) # 模拟程序执行了一段时间
logger.error("这是一个错误信息 (第一次运行)")
print(f"请检查当前目录下的文件: {os.path.abspath(LOG_FILE)}")
print("-" * 30)
# --- 模拟第二次运行 ---
# 在实际应用中,这可能是你重新启动脚本后
print("\n--- 第二次运行,模拟追加日志 ---")
# 注意:这里我们不需要再次 basicConfig,因为如果已经配置过,它可能不会重新应用。
# 更好的做法是获取已配置的 logger 实例。
logger = logging.getLogger(__name__) # 获取同一个 logger 实例
logger.info("这是第一条日志信息 (第二次运行)")
logger.warning("这是一个警告信息 (第二次运行)")
print("再次检查日志文件,你会发现新的日志被追加到了末尾。")
运行结果分析:
-
第一次运行:
- 程序会创建一个名为
my_app.log的文件。 - 如下:
2025-10-27 10:30:00,123 - INFO - 这是第一条日志信息 (第一次运行) 2025-10-27 10:30:01,124 - WARNING - 这是一个警告信息 (第一次运行) 2025-10-27 10:30:02,125 - ERROR - 这是一个错误信息 (第一次运行)
- 程序会创建一个名为
-
第二次运行:
- 程序再次运行,
FileHandler以追加模式打开my_app.log。 - 被更新为:
2025-10-27 10:30:00,123 - INFO - 这是第一条日志信息 (第一次运行) 2025-10-27 10:30:01,124 - WARNING - 这是一个警告信息 (第一次运行) 2025-10-27 10:30:02,125 - ERROR - 这是一个错误信息 (第一次运行) 2025-10-27 10:30:05,130 - INFO - 这是第一条日志信息 (第二次运行) <-- 新内容追加在这里 2025-10-27 10:30:05,131 - WARNING - 这是一个警告信息 (第二次运行) <-- 新内容追加在这里
- 程序再次运行,
重要提醒:FileHandler 的 mode 参数
虽然 FileHandler 默认是追加模式,但如果你显式地传入 mode='w',它就会覆盖文件内容。
# 警告:这会覆盖日志文件! # logging.FileHandler(LOG_FILE, mode='w')
在绝大多数情况下,你都应该使用默认的追加模式。
日志处理器追加到日志器
这是“追加”的第二个重要含义,即配置日志的“路由”,你可以将多个处理器“追加”到一个日志器上,实现日志的多路输出。
核心要点
- Logger 对象:日志系统的入口点,它负责产生日志消息。
- Handler 对象:日志消息的“分发器”,它决定日志消息最终要去哪里(文件、控制台、网络等)。
- 关系:一个
Logger可以有多个Handler,当日志产生时,Logger会将消息传递给它所拥有的所有Handler。
示例代码
这个例子将展示如何将日志同时输出到文件和控制台。
import logging
# 1. 创建一个 Logger
# 注意:如果直接使用 logging.getLogger() 不传名字,会获取到 root logger。
# 为了更好的模块化管理,推荐使用 __name__。
logger = logging.getLogger("my_module")
logger.setLevel(logging.DEBUG) # 设置日志器级别,DEBUG 级别及以上的日志都会被处理
# 2. 创建多个 Handler
# 2.1 文件 Handler (追加模式)
file_handler = logging.FileHandler('module.log', mode='a', encoding='utf-8')
file_handler.setLevel(logging.INFO) # 文件处理器只处理 INFO 级别及以上的日志
# 2.2 控制台 Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING) # 控制台处理器只处理 WARNING 级别及以上的日志
# 3. 创建并设置 Formatter (日志格式)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 将 Formatter 添加到 Handler
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 4. 将 Handler 追加到 Logger (这是关键步骤)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# 5. 现在开始记录日志
logger.debug("这是一个调试信息,只有文件会记录。") # DEBUG 级别,低于 file_handler 的 INFO,所以文件不记录,也低于 console_handler 的 WARNING,所以控制台不记录。
logger.info("这是一个信息,只有文件会记录。") # INFO 级别,文件会记录,但低于控制台的 WARNING,所以控制台不记录。
logger.warning("这是一个警告,文件和控制台都会记录。") # INFO 级别以上,文件和控制台都会记录。
logger.error("这是一个错误,文件和控制台都会记录。") # INFO 级别以上,文件和控制台都会记录。
运行结果分析:
- 控制台输出 (只有
WARNING和ERROR级别):2025-10-27 10:35:00,500 - my_module - WARNING - 这是一个警告,文件和控制台都会记录。 2025-10-27 10:35:00,501 - my_module - ERROR - 这是一个错误,文件和控制台都会记录。 module.log文件内容 (只有INFO、WARNING、ERROR级别):2025-10-27 10:35:00,501 - my_module - INFO - 这是一个信息,只有文件会记录。 2025-10-27 10:35:00,502 - my_module - WARNING - 这是一个警告,文件和控制台都会记录。 2025-10-27 10:35:00,503 - my_module - ERROR - 这是一个错误,文件和控制台都会记录。
这个例子完美地展示了如何通过“追加”多个 Handler 到一个 Logger,实现灵活的日志输出策略。
总结与最佳实践
| 概念 | 作用 | 关键点 |
|---|---|---|
| 追加 | 确保日志历史不被覆盖,持续记录。 | 使用 FileHandler,其默认模式为 'a' (append)。 |
| 处理器追加 | 实现日志的多路输出,如同时写文件和打印到控制台。 | 使用 logger.addHandler(handler) 方法。 |
最佳实践建议
- 使用
logging.config.dictConfig:对于复杂的日志配置,使用字典配置比在代码中硬编码Handler和Formatter更清晰、更易于维护。 - 为不同模块使用不同的 Logger:使用
logging.getLogger(__name__)是一个好习惯,这样可以根据模块名来定位日志来源。 - 合理设置日志级别:
- Logger 的级别决定了哪些级别的日志消息可以被处理。
- Handler 的级别决定了哪些级别的日志消息最终会被输出。
- 消息要被最终输出,必须同时满足 Logger 和其所连接的 Handler 的级别要求。
- 考虑使用
RotatingFileHandler:对于长期运行的服务,日志文件会变得非常大。RotatingFileHandler可以在文件达到一定大小时自动创建新文件,并保留一定数量的备份文件,避免单个日志文件无限增长。
希望这个详细的解释能帮助你完全理解 Python logging 模块中的“追加”功能!
