【问题标题】:Python: logging module - globallyPython:日志记录模块 - 全局
【发布时间】:2011-11-29 03:08:03
【问题描述】:

我想知道如何实现一个可以在任何地方使用您自己的设置的全局记录器:

我目前有一个自定义记录器类:

class customLogger(logging.Logger):
   ...

该类位于一个单独的文件中,其中包含一些格式化程序和其他内容。 记录器可以独立运行。

我在我的主 python 文件中导入这个模块并创建一个像这样的对象:

self.log = logModule.customLogger(arguments)

但显然,我无法从代码的其他部分访问此对象。 我使用了错误的方法吗?有没有更好的方法来做到这一点?

【问题讨论】:

  • 我不确定我是否了解您要克服的“日志记录”限制是什么

标签: python logging module global-variables


【解决方案1】:

使用logging.getLogger(name) 创建一个命名的全局记录器。

ma​​in.py

import log
logger = log.setup_custom_logger('root')
logger.debug('main message')

import submodule

log.py

import logging

def setup_custom_logger(name):
    formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')

    handler = logging.StreamHandler()
    handler.setFormatter(formatter)

    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    return logger

submodule.py

import logging

logger = logging.getLogger('root')
logger.debug('submodule message')

输出

2011-10-01 20:08:40,049 - DEBUG - main - main message
2011-10-01 20:08:40,050 - DEBUG - submodule - submodule message

【讨论】:

  • 每个子模块中定义的记录器不是全局参数,有没有办法使之成为一个?我希望当我在 submodule.py 中定义 logger = logging.getLogger('root') 时,它将与 submodule2.py 中的 logger = logging.getLogger('root') 中定义的记录器相同
  • 如果您在导入模块后配置“全局”记录器,您会怎么做? (例如,当使用多处理模块分叉一个进程时)
  • 由于您必须在submodule.py 中重新定义记录器,因此全局定义不是这样。同样,可以在从配置文件读取的每个子模块中重新初始化记录器(实际上需要更少的击键)。
  • 调用setup_custom_logger()时,建议使用__name__代替硬编码字符串
【解决方案2】:

由于我没有找到满意的答案,我想稍微详细说明一下这个问题的答案,以便深入了解 Python 标准附带的 logging 库的工作原理和意图图书馆。

与 OP(原始海报)的方法相比,该库清楚地将记录器的接口和记录器本身的配置分开。

处理程序的配置是使用您的库的应用程序开发人员的特权。

这意味着您应该创建自定义记录器类并通过添加任何配置或其他方式在该类中配置记录器。

logging 库引入了四个组件:loggershandlersfiltersformatters

  • 记录器公开应用程序代码直接使用的接口。
  • 处理程序将日志记录(由记录器创建)发送到适当的目的地。
  • 过滤器为确定要输出哪些日志记录提供了更细粒度的工具。
  • 格式化程序指定最终输出中日志记录的布局。

一个常见的项目结构如下所示:

Project/
|-- .../
|   |-- ...
|
|-- project/
|   |-- package/
|   |   |-- __init__.py
|   |   |-- module.py
|   |   
|   |-- __init__.py
|   |-- project.py
|
|-- ...
|-- ...

在您的代码中(例如在 module.py 中),您可以引用模块的记录器实例来记录特定级别的事件。

命名记录器时使用的一个好的约定是在每个使用记录的模块中使用模块级记录器,命名如下:

logger = logging.getLogger(__name__)

特殊变量__name__ 引用您的模块名称,根据您的应用程序的代码结构,它看起来类似于project.package.module

module.py(和任何其他类)基本上看起来像这样:

import logging
...
log = logging.getLogger(__name__)

class ModuleClass:
    def do_something(self):
        log.debug('do_something() has been called!')

每个模块中的记录器会将任何事件传播到父记录器,父记录器作为回报将信息传递给其附加的处理程序!与 python 包/模块结构类似,父记录器由使用“带点模块名称”的命名空间确定。这就是为什么用特殊的__name__ 变量初始化记录器是有意义的(在上面的示例中,name 匹配字符串 "project.package.module")。 p>

全局配置记录器有两个选项:

  • project.py 中使用名称 __package__ 实例化一个记录器,在本例中等于 "project",因此是记录器的父记录器的所有子模块。只需向 this logger 添加适当的处理程序和格式化程序。

  • 在执行脚本(如 ma​​in.py)中使用最顶层包的名称设置一个带有处理程序和格式化程序的记录器。

在开发使用日志记录的库时,您应该注意记录库如何使用日志记录 - 例如,使用的记录器的名称。

执行脚本,例如 ma​​in.py,最终可能看起来像这样:

import logging
from project import App

def setup_logger():
    # create logger
    logger = logging.getLogger('project')
    logger.setLevel(logging.DEBUG)

    # create console handler and set level to debug
    ch = logging.StreamHandler()
    ch.setLevel(level)

    # create formatter
    formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')

    # add formatter to ch
    ch.setFormatter(formatter)

    # add ch to logger
    logger.addHandler(ch)

if __name__ == '__main__' and __package__ is None:
     setup_logger()
     app = App()
     app.do_some_funny_stuff()

方法调用log.setLevel(...) 指定记录器将处理但不一定输出的最低严重性日志消息!它只是意味着只要消息的严重级别高于(或等于)设置的级别,消息就会传递给处理程序。但是handler负责处理日志消息(例如通过打印或存储)。

因此,logging 库提供了一种结构化和模块化的方法,只需要根据自己的需要加以利用。

Logging documentation

【讨论】:

【解决方案3】:

在您的日志模块中创建customLogger 的实例并将其用作单例 - 只需使用导入的实例,而不是类。

【讨论】:

    【解决方案4】:

    您可以在第一个句点之前将一个带有公共子字符串的字符串传递给它。由句点(“.”)分隔的字符串部分可以用于不同的类/模块/文件/等。就像这样(特别是logger = logging.getLogger(loggerName)部分):

    def getLogger(name, logdir=LOGDIR_DEFAULT, level=logging.DEBUG, logformat=FORMAT):
        base = os.path.basename(__file__)
        loggerName = "%s.%s" % (base, name)
        logFileName = os.path.join(logdir, "%s.log" % loggerName)
        logger = logging.getLogger(loggerName)
        logger.setLevel(level)
        i = 0
        while os.path.exists(logFileName) and not os.access(logFileName, os.R_OK | os.W_OK):
            i += 1
            logFileName = "%s.%s.log" % (logFileName.replace(".log", ""), str(i).zfill((len(str(i)) + 1)))
        try:
            #fh = logging.FileHandler(logFileName)
            fh = RotatingFileHandler(filename=logFileName, mode="a", maxBytes=1310720, backupCount=50)
        except IOError, exc:
            errOut = "Unable to create/open log file \"%s\"." % logFileName
            if exc.errno is 13: # Permission denied exception
                errOut = "ERROR ** Permission Denied ** - %s" % errOut
            elif exc.errno is 2: # No such directory
                errOut = "ERROR ** No such directory \"%s\"** - %s" % (os.path.split(logFileName)[0], errOut)
            elif exc.errno is 24: # Too many open files
                errOut = "ERROR ** Too many open files ** - Check open file descriptors in /proc/<PID>/fd/ (PID: %s)" % os.getpid()
            else:
                errOut = "Unhandled Exception ** %s ** - %s" % (str(exc), errOut)
            raise LogException(errOut)
        else:
            formatter = logging.Formatter(logformat)
            fh.setLevel(level)
            fh.setFormatter(formatter)
            logger.addHandler(fh)
        return logger
    
    class MainThread:
        def __init__(self, cfgdefaults, configdir, pidfile, logdir, test=False):
            self.logdir = logdir
            logLevel = logging.DEBUG
            logPrefix = "MainThread_TEST" if self.test else "MainThread"
            try:
                self.logger = getLogger(logPrefix, self.logdir, logLevel, FORMAT)
            except LogException, exc:
                sys.stderr.write("%s\n" % exc)
                sys.stderr.flush()
                os._exit(0)
            else:
                self.logger.debug("-------------------- MainThread created.  Starting __init__() --------------------")
    
        def run(self):
            self.logger.debug("Initializing ReportThreads..")
            for (group, cfg) in self.config.items():
                self.logger.debug(" ------------------------------ GROUP '%s' CONFIG ------------------------------     " % group)
                for k2, v2 in cfg.items():
                    self.logger.debug("%s <==> %s: %s" % (group, k2, v2))
                try:
                    rt = ReportThread(self, group, cfg, self.logdir, self.test)
                except LogException, exc:
                    sys.stderr.write("%s\n" % exc)
                    sys.stderr.flush()
                    self.logger.exception("Exception when creating ReportThread (%s)" % group)
                    logging.shutdown()
                    os._exit(1)
                else:
                    self.threads.append(rt)
            self.logger.debug("Threads initialized.. \"%s\"" % ", ".join([t.name for t in self.threads]))
            for t in self.threads:
                t.Start()
            if not self.test:
                self.loop()
    
    
    class ReportThread:
        def __init__(self, mainThread, name, config, logdir, test):
            self.mainThread = mainThread
            self.name = name
            logLevel = logging.DEBUG
            self.logger = getLogger("MainThread%s.ReportThread_%s" % ("_TEST" if self.test else "", self.name), logdir, logLevel, FORMAT)
            self.logger.info("init database...")
            self.initDB()
            # etc....
    
    if __name__ == "__main__":
        # .....
        MainThread(cfgdefaults=options.cfgdefaults, configdir=options.configdir, pidfile=options.pidfile, logdir=options.logdir, test=options.test)
    

    【讨论】:

      【解决方案5】:

      python 日志记录模块作为全局记录器已经足够好了,您可能只是在寻找这个:

      ma​​in.py

      import logging
      logging.basicConfig(level = logging.DEBUG,format = '[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s')
      

      将上面的代码放入你的执行脚本中,然后你就可以在你的项目中的任何地方使用这个具有相同配置的记录器:

      module.py

      import logging
      logger = logging.getLogger(__name__)
      logger.info('hello world!')
      

      对于更复杂的配置,您可以使用配置文件 logging.conf 和日志记录

      logging.config.fileConfig("logging.conf")
      

      【讨论】:

      • 我正在记录的答案...感谢有趣的李 #dadjokes
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-04
      • 2021-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多