在多个类中使用模块名称记录日志

22

我想使用logging模块代替打印信息和文档调试。目标是在控制台上以DEBUG级别打印并记录到文件中以INFO级别。

我阅读了很多文档,食谱和其他有关logging模块的教程,但无法弄清楚如何按照我想要的方式使用它。(我使用的是python25)

我希望在日志文件中写入日志的模块名称。

文档说我应该使用logger = logging.getLogger(__name__),但是我如何声明在其他模块/包中使用的类中使用的记录器,以便它们使用与主记录器相同的处理程序?为了识别“父母”,我可以使用logger = logging.getLogger(parent.child),但我怎么知道是谁调用了类/方法?

下面的示例显示了我的问题,如果我运行这个,输出将只有__main__中的日志,并忽略Class中的日志

这是我的主文件:

# main.py

import logging
from module import Class

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# create file handler which logs info messages
fh = logging.FileHandler('foo.log', 'w', 'utf-8')
fh.setLevel(logging.INFO)

# create console handler with a debug log level
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# creating a formatter
formatter = logging.Formatter('- %(name)s - %(levelname)-8s: %(message)s')

# setting handler format
fh.setFormatter(formatter)
ch.setFormatter(formatter)

# add the handlers to the logger
logger.addHandler(fh)
logger.addHandler(ch)

if __name__ == '__main__':
    logger.info('Script starts')
    logger.info('calling class Class')
    c = Class()
    logger.info('calling c.do_something()')
    c.do_something()
    logger.info('calling c.try_something()')
    c.try_something()

模块:

# module.py

imnport logging

class Class:
    def __init__(self):
        self.logger = logging.getLogger(__name__) # What do I have to enter here?
        self.logger.info('creating an instance of Class')
        self.dict = {'a':'A'}
    def do_something(self):
        self.logger.debug('doing something')
        a = 1 + 1
        self.logger.debug('done doing something')
    def try_something(self):
        try:
            logging.debug(self.dict['b'])
        except KeyError, e:
            logging.exception(e)

控制台中输出:

- __main__ - INFO    : Script starts
- __main__ - INFO    : calling class Class
- __main__ - INFO    : calling c.do_something()
- __main__ - INFO    : calling c.try_something()
No handlers could be found for logger "module"
此外:是否有一种方法可以在不像上面每个类都要声明一个新记录器的情况下,获取日志文件中发生日志的模块名称?另外,像这样,每次想要记录日志时都必须使用self.logger.info()。我更喜欢在我的整个代码中使用logging.info()logger.info()
也许全局记录器是正确的答案吗?但是我将无法在日志中获取错误发生的模块...
我的最后一个问题:这是否符合Pythonic?还是有更好的建议来正确执行这些操作。
2个回答

21
在你的主模块中,你正在配置名称为'__main__'的记录器(或者在你的情况下等同于__name__),而在module.py中,你正在使用不同的记录器。你可以为每个模块配置记录器,或者你可以在主模块中配置根记录器(通过配置logging.getLogger()),其默认适用于项目中的所有记录器。
我建议使用配置文件来配置记录器。此链接应该能让你了解好的实践方法:http://victorlin.me/posts/2012/08/26/good-logging-practice-in-python 编辑:在你的格式化程序中使用%(module)将模块名称包含在日志消息中。

2
只配置根记录器是可以接受的,只要您希望将相同的设置应用于项目中的所有记录器。但是,如果您需要基于模块添加不同的处理程序,则最好定义一个清晰的配置文件,并指定包级别的记录器(例如,路径为path.to.package1的记录器配置将应用于此包层次结构的所有子级)。 - sirfz
有没有可能将包名添加到模块中,甚至是类名?如果你有一个非常大的项目,仅有模块名并不是很有帮助... - uloco
2
如果你使用 logger.getLogger(__name__),那么你将会在日志信息中得到完整的模块名称(包括包的层次结构),前提是你已经设置好了 %(name) 格式化元素。请记住,如果根日志器被配置,那么它将应用于所有子日志器(也就是所有包和模块)。 - sirfz
那么,如果我想使用 logging.getLogger(__name__) 变量,我该如何编写代码?在我的类中的哪一行需要输入什么? - uloco
2
如果你在 main.py 模块中的 getLogger 调用中仅删除 __name__,则一切都应该按你想要的方式工作,无需进行任何其他修改(因为您将配置适用于所有子级包括 module.py 的根记录器)。 试一下,你就会明白我的意思了。 - sirfz
显示剩余3条评论

11

一般推荐的日志设置是每个模块最多只有一个日志记录器。

如果你的项目被正确打包,则__name__将具有"mypackage.mymodule"的值,除了在主文件中,它具有"__main__"的值。

如果你想要更多关于记录消息的代码背景信息,请注意可以使用格式化字符串设置你的格式化程序,例如%(funcName)s,这将向所有消息添加函数名。

如果你真的非常需要基于类的记录器,可以尝试以下方法:

class MyClass:
    def __init__(self):
        self.logger = logging.getLogger(__name__+"."+self.__class__.__name__)

2
嗯...每次都要明确地在每个类中插入名称,这看起来太糟糕了...一定有更好的方法。 - uloco
我发现,只需在我的主文件中使用'',并在模块中使用__name__,就可以摆脱getLogger(__name__)的问题,这样一切都能正常显示! - uloco
2
最好避免使用getLogger('') - 你使用的库中的其他模块可能也会使用根记录器,它们可以更改你的处理程序和格式化程序配置。更安全的做法是简单地使用项目范围的前缀。 - loopbackbee
但现在它不再起作用了...所以我真的必须每次都使用你的版本,即'myapp.'+__name__... - uloco
@drrtyrokka 日志记录器如果没有分配处理程序,则会使用它们的父级。如果您不使用 'parentloggername+'.'+'childloggername'logging 将认为它们是独立的(即:没有父/子关系)。 - loopbackbee
2
@uloco 你可以使用装饰器为类添加日志记录器:https://xmedeko.blogspot.cz/2017/10/python-logger-by-class.html - xmedeko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接