多Logger日志记录及处理器移除问题排查与优化咨询
问题排查
1. 日志重复记录的根源
看C.py的setup_logger函数,你连续两次调用了l.addHandler(fileHandler),这会给同一个logger添加两个完全一样的文件处理器,导致每条日志都会被写入文件两次。另外原代码里的路径用了单反斜杠,f"C:\{time.time()}_main.log"会把\t解析成制表符,导致日志文件路径错误,这也是隐藏的问题。
2. 无法删除日志文件的原因
- 你在
release_logs里先调用logging.shutdown()再去关闭移除handler,顺序搞反了:shutdown已经尝试关闭所有handler,后续操作相当于无效重复; - 重复添加的handler可能导致部分句柄没被正确关闭,Windows系统下只要进程还占用文件句柄,就无法删除文件;
- 另外
main.py里的detail开关控制不彻底:你在main.py里重定义了detail_log,但B.py直接导入的是C.py的原始函数,所以B模块里的日志不会被开关控制。
修复后的代码示例
C.py(核心问题修正)
import logging import time def setup_logger(logger_name, log_file, level=logging.INFO): l = logging.getLogger(logger_name) # 避免模块被多次导入时重复创建handler if l.handlers: return l formatter = logging.Formatter('%(asctime)s | %(message)s') # 用双反斜杠避免路径转义,指定utf-8防止中文乱码 fileHandler = logging.FileHandler(log_file, mode='w', encoding='utf-8') fileHandler.setFormatter(formatter) streamHandler = logging.StreamHandler() streamHandler.setFormatter(formatter) l.setLevel(level) # 只添加一次处理器,解决重复日志问题 l.addHandler(fileHandler) l.addHandler(streamHandler) return l def main_log(x): log_main.info(x) def detail_log(y): log_detail.info(y) def release_logs(a, b): # 先关闭移除handler,再调用shutdown def remove_handlers(logger): # 转成list避免遍历过程中修改列表报错 for h in list(logger.handlers): h.close() logger.removeHandler(h) remove_handlers(a) remove_handlers(b) logging.shutdown() # 初始化日志器,路径用双反斜杠 log_main = setup_logger('log_main', f"C:\\{time.time()}_main.log") log_detail = setup_logger('log_detail', f"C:\\log2_{time.time()}_detail.log")
main.py(修正detail开关控制)
import time import logging import B from C import main_log, detail_log as original_detail_log, release_logs, log_main, log_detail detail = True # 重新定义全局的detail_log,让B模块也能继承开关逻辑 def detail_log(x): if detail: original_detail_log(x) # 替换B模块里的detail_log引用,确保B的日志也受控制 B.detail_log = detail_log def main(): main_log('main - 01') sub1('detail - 01') sub2('detail - 02') release_logs(log_main, log_detail) def sub1(x): detail_log(x) def sub2(y): detail_log(y) B.do_something() if '__main__' == __name__: main()
更优的实现方案
用logging的字典配置来封装日志逻辑,更清晰易维护,还能直接在初始化阶段控制detail日志的启停:
# C.py 优化版 import logging import logging.config import time import os # 提前创建日志目录,避免路径不存在报错 os.makedirs('C:\\logs', exist_ok=True) def setup_loggers(detail_enabled=True): log_config = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': '%(asctime)s | %(message)s' } }, 'handlers': { 'main_file': { 'class': 'logging.FileHandler', 'filename': f"C:\\logs\\{time.time()}_main.log", 'mode': 'w', 'encoding': 'utf-8', 'formatter': 'standard' }, 'console': { 'class': 'logging.StreamHandler', 'formatter': 'standard' } }, 'loggers': { 'main_logger': { 'handlers': ['main_file', 'console'], 'level': 'INFO', 'propagate': False } } } # 根据开关决定是否创建detail日志器 if detail_enabled: log_config['handlers']['detail_file'] = { 'class': 'logging.FileHandler', 'filename': f"C:\\logs\\log2_{time.time()}_detail.log", 'mode': 'w', 'encoding': 'utf-8', 'formatter': 'standard' } log_config['loggers']['detail_logger'] = { 'handlers': ['detail_file', 'console'], 'level': 'INFO', 'propagate': False } else: # 关闭时返回空日志器,调用info不会做任何操作 class NullLogger: def info(self, *args, **kwargs): pass return logging.getLogger('main_logger'), NullLogger() logging.config.dictConfig(log_config) return logging.getLogger('main_logger'), logging.getLogger('detail_logger') def release_logs(logger1, logger2): for logger in [logger1, logger2]: for h in list(logger.handlers): h.close() logger.removeHandler(h) logging.shutdown()
对应的main.py使用方式:
import time import logging import B from C import setup_loggers, release_logs detail = True log_main, log_detail = setup_loggers(detail) def main_log(x): log_main.info(x) def detail_log(x): log_detail.info(x) # 替换B模块的日志函数 B.main_log = main_log B.detail_log = detail_log def main(): main_log('main - 01') sub1('detail - 01') sub2('detail - 02') release_logs(log_main, log_detail) def sub1(x): detail_log(x) def sub2(y): detail_log(y) B.do_something() if '__main__' == __name__: main()
这个方案的优势:配置集中清晰,便于扩展不同日志级别、格式;直接在初始化阶段控制日志器创建,不需要后续修改函数引用;统一管理日志目录,避免路径错误。
内容的提问来源于stack exchange,提问作者aeiou
相关产品推荐
相关产品推荐

