【问题标题】:Python: custom logging across all modulesPython:跨所有模块的自定义日志记录
【发布时间】:2013-03-30 00:29:06
【问题描述】:

任务

我有一组脚本,我希望它们生成统一的日志消息,同时对记录实际消息的模块进行最少的改动。

我已经编写了一个小模块“custom_logger”,我打算从主应用程序调用一次,让它返回一个记录器,然后我会继续使用它。

我要导入应用程序的子模块应该只(或者更确切地说我希望它们)

  • 应该只“将日志记录作为日志导入” - 这样,如果其他人发现它们有用,我的网站不需要任何特定的东西就可以让它们运行。
  • 应该只使用 log.info/error('message') 记录消息,而不添加任何特定于站点的内容
  • 应使用已配置的“根”记录器及其所有格式和处理程序,并且不影响根记录器的配置

*custom_logger.py*

import logging
import logging.handlers
import os
import sys


def getLogger(name='root', loglevel='INFO'):
  logger = logging.getLogger(name)

  # if logger 'name' already exists, return it to avoid logging duplicate
  # messages by attaching multiple handlers of the same type
  if logger.handlers:
    return logger
  # if logger 'name' does not already exist, create it and attach handlers
  else:
    # set logLevel to loglevel or to INFO if requested level is incorrect
    loglevel = getattr(logging, loglevel.upper(), logging.INFO)
    logger.setLevel(loglevel)
    fmt = '%(asctime)s %(filename)-18s %(levelname)-8s: %(message)s'
    fmt_date = '%Y-%m-%dT%T%Z'
    formatter = logging.Formatter(fmt, fmt_date)
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    if logger.name == 'root':
      logger.warning('Running: %s %s',
                     os.path.basename(sys.argv[0]),
                     ' '.join(sys.argv[1:]))
    return logger

然后是子模块,其中包含一些测试消息以及哪些有效和哪些无效的示例。

submodule.py

import sys
import custom_logger
import logging


class SubClass(object):

  def __init__(self):
    # NOK (no idea why since by default (no name parameter), it should return the root logger)
    #log = logging.getLogger()
    #log.info('message from SubClass / __init__')

    # OK (works as expected)
    #log = logging.getLogger('root')
    #log.info('message from SubClass / __init__')

    # OK (works as expected)
    log = custom_logger.getLogger('root')
    log.info('message from SubClass / __init__')


  def SomeMethod(self):
    # OK but I'd have to define `log` for every method, which is unacceptable
    # Please see question below all code snippets
    log = custom_logger.getLogger('root')
    log.info('message from SubClass / SomeMethod')

还有主应用:app.py这里没什么特别的:

#!/usr/bin/python

import custom_logger
import submodule

log = custom_logger.getLogger('root', loglevel='DEBUG')

log.debug('debug message')
log.info('info message')
log.warning('warning message')
log.error('error message')

a = submodule.SubClass() # this should produce a log message
a.SomeMethod()           # so should this

我追求和得到的输出,只是以一种极其丑陋的方式:

% ./app.py 
2013-04-08T03:07:46BST custom_logger.py   WARNING : Running: app.py 
2013-04-08T03:07:46BST app.py             DEBUG   : debug message
2013-04-08T03:07:46BST app.py             INFO    : info message
2013-04-08T03:07:46BST app.py             WARNING : warning message
2013-04-08T03:07:46BST app.py             ERROR   : error message
2013-04-08T03:07:46BST submodule.py       INFO    : message from SubClass / __init__
2013-04-08T03:07:46BST submodule.py       INFO    : message from SubClass / SomeMethod

我希望能够在 app.py 中定义一个记录器,然后在子模块中仅使用标准 Python 日志库来利用 app.py 中已配置的记录器。

另外,一个丑陋的解决方法:如果我将下面的代码放在 submodule.py 中的导入之后:

log = custom_logger.getLogger('root')

它将在我的记录器在 app.py 中配置之前执行,有效地使子模块,而不是我的应用程序配置日志记录。

我考虑的另一种解决方法:在 SubClass 类的构造函数中,我可以定义

self.log = custom_logger.getLogger('root')

然后使用 self.log.error('some error')。一定有更好的方法 - 如果您能提出一些有用的建议或指出我误解了文档的地方,我将非常感激!

PS。我花了很多时间阅读 Python 日志记录方法(基本和高级)和说明书,所以如果我错过了那里有用的东西,请指出。

谢谢!

【问题讨论】:

    标签: python logging centralized


    【解决方案1】:

    如果您想更改根记录器,您可以在任何地方使用getLogger(),不带任何参数。

    关于仅在主模块中设置的实例,您可以实例化您的记录器,添加您自己的处理程序,并在所有其他子模块中使用它(就像我在下面所做的那样)。

    我在custom_logger.py中创建了一个继承StreamHandler的类:

    class MyHandler(logging.StreamHandler):
    
        def __init__(self):
            logging.StreamHandler.__init__(self)
            fmt = '%(asctime)s %(filename)-18s %(levelname)-8s: %(message)s'
            fmt_date = '%Y-%m-%dT%T%Z'
            formatter = logging.Formatter(fmt, fmt_date)
            self.setFormatter(formatter)
    

    然后,在submodule.py 中,我将getLogger 放在imports 之后并在方法中对其进行注释:

    import sys
    import logging
    
    log = logging.getLogger('root')
    
    class SubClass(object):
    
        def __init__(self):
             log.info('message from SubClass / __init__')
    
        def SomeMethod(self):
            log.info('message from SubClass / SomeMethod')
    

    然后,我在 app.py 中创建了一个 Logger 实例(在所有模块中都是相同的)并添加了我的处理程序,它格式化了输出:

    #!/usr/bin/python
    
    import logging
    import custom_logger
    import submodule
    
    log = logging.getLogger('root')
    log.setLevel('DEBUG')
    log.addHandler(custom_logger.MyHandler())
    
    log.debug('debug message') 
    log.info('info message')
    log.warning('warning message')
    log.error('error message')
    
    a = submodule.SubClass() # this should produce a log message
    a.SomeMethod()           # so should this
    

    输出:

    ./app.py 
    2013-04-08T15:20:05EEST app.py             DEBUG   : debug message
    2013-04-08T15:20:05EEST app.py             INFO    : info message
    2013-04-08T15:20:05EEST app.py             WARNING : warning message
    2013-04-08T15:20:05EEST app.py             ERROR   : error message
    2013-04-08T15:20:05EEST submodule.py       INFO    : message from SubClass / __init__
    2013-04-08T15:20:05EEST submodule.py       INFO    : message from SubClass / SomeMethod
    

    【讨论】:

    • 谢谢米海。我以为我会有更多时间来研究它,但我需要等待周末。到目前为止,通过将 log 定义为 submodule.py 中的 getLogger 对象,我已经能够使我的类能够完成所有工作(附加格式化程序、处理程序和设置 loglevel)。我希望这可以在不把东西放在类定义之外的情况下完成。我的问题更多是“为什么我所做的某些方法没有按预期工作,因为我想了解其背后的原因,而不是把代码交给我,但我非常感谢你的帮助 :)
    • 我更新了我的答案。除此之外,您可以查看模块的代码。您可以看到在导入期间实例化了根记录器。我也不太明白为什么 getLogger("root") 在所有情况下都无法正常工作(您可以在 app.py 中执行 getLogger() 并在 submodule.py 中执行 getLogger("root") ,然后您就得到了需要,但反之则不然)。
    • 如何使其与标准输出重定向到文件一起工作,例如python ./app.py >> file.log ?
    • 我猜你可以做 python ./app.py &>> file.log
    • 这在 Windows 上不起作用。我可以让它写入文件,但它并不是真正的重定向。它将同时写入文件和标准输出:logging.getLogger('root').addHandler(logging.StreamHandler(sys.stdout))
    猜你喜欢
    • 1970-01-01
    • 2017-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-04
    • 1970-01-01
    • 2019-12-27
    • 1970-01-01
    相关资源
    最近更新 更多