暂停日志格式化后再恢复

2 投票
2 回答
1093 浏览
提问于 2025-04-18 03:26

我有一个日志配置,既可以把日志记录到文件里,也可以在控制台上显示:

logging.basicConfig(filename=logfile, filemode='w',
                        level=numlevel,
                        format='%(asctime)s - %(levelname)s - %(name)s:%(funcName)s - %(message)s')
    # add console messages
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    consoleformatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    console.setFormatter(consoleformatter)
    logging.getLogger('').addHandler(console)

在我脚本的某个地方,我需要和用户互动,打印一个总结并询问确认。这个总结现在是通过在一个循环中打印出来的。我想暂停当前控制台日志的格式,这样我就可以打印一大段文字,最后加一个问题,并等待用户输入。但我仍然希望所有这些内容都能记录到文件里!

实现这个功能的代码在一个模块里,我尝试了以下方法:

logger = logging.getLogger(__name__)
def summaryfunc:
    logger.info('normal logging business')
    clearformatter = logging.Formatter('%(message)s')
    logger.setFormatter(clearformatter)
    logger.info('\n##########################################')
    logger.info('Summary starts here')

结果出现了错误:AttributeError: 'Logger' object has no attribute 'setFormatter'

我明白日志记录器就是日志记录器,而不是处理器,但我不太确定怎么才能让它正常工作……

编辑:

根据大家的回答,我的问题变成了:在与用户互动时,如何暂停控制台的日志记录,同时仍然能够记录到文件。也就是说:只暂停streamHandler。因为这个操作发生在一个模块里,处理器的具体设置在别的地方定义,所以这是我实现的方法:

logger.debug('Normal logging to file and console')
root_logger = logging.getLogger()
stream_handler = root_logger.handlers[1]
root_logger.removeHandler(stream_handler)
print('User interaction')
logger.info('Logging to file only')
root_logger.addHandler(stream_handler)
logger.info('Back to logging to both file and console')

这个方法依赖于streamHandler总是位于handlers返回的列表中的第二个位置,但我相信这是正确的,因为它是我添加处理器到根日志记录器时的顺序……

2 个回答

3

日志记录不应该用来提供你程序的实际输出——也就是说,不管日志功能是否开启,程序的运行结果应该是一样的。所以我建议你还是继续之前的做法,也就是在循环中使用打印输出。

4

我同意Vinay的观点,正常的程序输出应该用print,而logging则只用于记录日志。不过,如果你想在中间切换格式,然后再切换回来,可以这样做:

import logging

def summarize():
    console_handler.setFormatter(logging.Formatter('%(message)s'))
    logger.info('Here is my report')
    console_handler.setFormatter(console_formatter)

numlevel = logging.DEBUG
logfile = 's2.log'
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

console_handler = logging.StreamHandler()
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(console_formatter)
logger.addHandler(console_handler)

file_handler = logging.FileHandler(filename=logfile, mode='w')
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s:%(funcName)s - %(message)s')
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)

logger.info('Before summary')
summarize()
logger.info('After summary')

讨论

  • 这个脚本创建了一个日志记录器对象,并为它分配了两个处理器:一个用于控制台,一个用于文件。
  • summarize()函数中,我为控制台处理器切换了一个新的格式化器,进行了一些日志记录,然后又切换回来了。
  • 再次提醒,logging不应该用来显示正常的程序输出。

更新

如果你想暂时关闭控制台日志记录,然后再打开,可以参考以下建议:

def interact():
    # Remove the console handler
    for handler in  logger.handlers:
        if not isinstance(handler, logging.FileHandler):
            saved_handler = handler
            logger.removeHandler(handler)
            break

    # Interact
    logger.info('to file only')

    # Add the console handler back
    logger.addHandler(saved_handler)

注意,我没有测试logging.StreamHandler的处理器,因为logging.FileHandler是从logging.StreamHandler派生出来的。因此,我移除了那些不是FileHandler的处理器。在移除之前,我保存了那个处理器,以便后续恢复。

更新 2: .handlers = []

在主脚本中,如果你有:

logger = logging.getLogger(__name__) # __name__ == '__main__'

那么在一个模块中,你可以这样做:

logger = logging.getLogger(__name__) # __name__ == module's name, not '__main__'

问题是,在脚本中,__name__ == '__main__',而在模块中,__name__ == <模块的名称>,而不是'__main__'。为了保持一致性,你需要想一个名字,并在两个地方都使用:

logger = logging.getLogger('MyScript')

撰写回答