You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 11:51:32