Python在交互式会话中登录

时间:2011-02-19 14:36:49

标签: python logging interactive

我正在尝试在我的Python 2.7应用程序中实现日志记录,并发现它非常有用。但是,我注意到,当以交互方式运行Python时,每个日志消息都会打印多次。打印消息的次数与我之前运行脚本的次数相同,因此在脚本结束时似乎没有正确清理记录器(我猜)。请考虑以下示例:

import sys
import logging

def main(argv=None):

    log = logging.getLogger('test')
    log.setLevel(logging.DEBUG)

    console_handler = logging.StreamHandler()
    console_handler.setFormatter(logging.Formatter("%(message)s"))
    log.addHandler(console_handler)

    log.info('Starting something...')
    log.info('Doing something...')
    log.info('Finished something.')

    logging.shutdown()

if __name__=='__main__':
    sys.exit(main(sys.argv[1:]))

输入

>>> import file.py
>>> file.main()

产生以下结果:

Starting something...
Doing something...
Finished something.

然后再次输入file.main()会产生:

Starting something...
Starting something...
Doing something...
Doing something...
Finished something.
Finished something.

重复第三次会发出三条或每条消息,依此类推。有谁知道为什么会这样 - 这是日志记录模块的预期行为,如果是这样,我该如何更改?如果以预期的方式作为脚本(python file.py)运行,上述脚本仅打印每条消息中的一条。

4 个答案:

答案 0 :(得分:5)

是的,您正在创建并重复使用记录器的单个实例。添加到该记录器的每个处理程序也记录一条消息。

您可能希望日志记录设置位于模块级别或单独的函数中,因此您只需运行一次。

也许是这样的:

import atexit
import sys
import logging

log = logging.getLogger('test')
log.setLevel(logging.DEBUG)

console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter("%(message)s"))
log.addHandler(console_handler)

def shutdown_logging():
    logging.shutdown()    

atexit.register(shutdown_logging)

def main(argv=None):
    log.info('Starting something...')
    log.info('Doing something...')
    log.info('Finished something.')


if __name__=='__main__':
    sys.exit(main(sys.argv[1:]))

答案 1 :(得分:2)

尝试将其作为解决方法:

if len(logging.root.handlers) == 0:
     log.add_handler(console_handler)

日志记录模块使用全局静态记录器对象,当您在解释器中时,该对象会在您的会话中持续存在。因此,每次拨打add_handler时,您都会添加一个全新的流处理程序,但不会删除旧的处理程序。记录只是遍历其处理程序并将输出发送到每个处理程序,因此每次运行时都会有相同内容的新副本进入控制台。

答案 2 :(得分:1)

您可以在每次重新加载配置时删除所有处理程序,具体情况视您的情况而定,然后调用file.main()

file.logging.getLogger('test').handlers = []

注意(主观):

正如@stderr所写,鼓励在模块级别定义记录器。但是,我认为在应用程序入口点设置它们也是一个好习惯,因此在这里,我只会在if __name__=='__main__'之后添加处理程序,或者在您的情况下,在您的(I)Python控制台中添加处理程序。这样,导入模块不会创建所有类型的处理程序,而是仅由选择执行模块某些功能的人员创建的。

答案 3 :(得分:0)

这是@ beer_monk解决方案的替代方案,即使你已经触及其他地方的根处理程序,它也能正常工作。我在这里使用了__name__而不是'test',这样就可以更容易地在其他模块中重用代码,也许可以将它变成一个以__name__为参数的函数。

if not __name__ in logging.Logger.manager.loggerDict:
    new_logger = logging.getLogger(__name__)
    new_logger.setLevel(logging.DEBUG)

    console_handler = logging.StreamHandler()
    console_handler.setFormatter(logging.Formatter("%(message)s"))
    new_logger.addHandler(console_handler)
log = logging.getLogger(__name__)