Python:自定义日志跨所有模块

31
任务
我有一组脚本,希望它们能产生统一的日志消息,同时最小限度地更改执行实际日志消息记录的模块。
我编写了一个名为“custom_logger”的小模块,计划从主应用程序中调用它一次,让它返回一个记录器,然后我将继续使用它。
我将导入到应用程序中的子模块应该只(或者说我希望它们只):
- 只需“import logging as log” - 这样就不需要特定于我的站点的内容才能使它们运行,如果其他人发现它们有用的话。 - 只需使用log.info/error('message')记录消息,而不添加任何特定于站点的内容。 - 应使用已经配置好的“root”记录器及其所有格式和处理程序,并且不影响根记录器的配置。
*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 logging库,以利用在app.py中已经配置好的日志记录器。
此外,还有一个丑陋的解决方法:如果我将以下代码放在submodule.py的导入之后:
log = custom_logger.getLogger('root')

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

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

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

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

附言:我花了很多时间阅读Python日志记录howto(基本和高级)和cookbook,所以如果我错过了一些有用的东西,请指出来。

谢谢!

1个回答

20

如果您想更改根记录器,只需在任何地方使用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放在导入之后,并在方法中进行注释:

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

1
谢谢Mihai。我本以为我会有更多时间去研究它,但我需要等到周末。到目前为止,我已经能够通过在submodule.py中定义log作为getLogger对象来使我的类(附加格式化程序、处理程序和设置日志级别)工作。我希望这可以在不将事物放在类定义之外的情况下完成。我的问题更多是“为什么我所做的某些方法没有像我期望的那样起作用,我想了解背后的原因,而不是让代码交给我,但我非常感谢你的帮助 :)” - Marcin Kaminski
我更新了我的答案。除此以外,你可以查看模块的代码。你可以看到根记录器在导入期间被实例化。我也不太理解为什么 getLogger("root") 在所有情况下都不起作用(你可以在 app.py 中执行 getLogger(),在 submodule.py 中执行 getLogger("root"),你会得到你需要的结果,但反之则不行)。 - Mihai
如何使其与stdout重定向到文件一起工作,例如python ./app.py >> file.log? - Edijs Petersons
我猜你可以执行 python ./app.py &>> file.log。 - Mihai
这在 Windows 上无法工作。我可以让它写入文件,但它并没有真正地重定向。它将同时写入文件和 stdout:logging.getLogger('root').addHandler(logging.StreamHandler(sys.stdout))。 - Edijs Petersons

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