在禁用主日志的同时为多个子日志文件设置Python日志记录

时间:2014-08-04 21:58:34

标签: python logging

我有一些相关但独立的Python脚本,它们都使用了两个使用日志记录的内部模块。

第一个脚本使用根记录器工作正常,并从两个模块中捕获日志记录语句。但是,使用第二个脚本,我希望有一个主日志,但是当它遍历服务器列表时,将日志发送到每个机器的日志文件,同时暂停日志记录到主日志文件和控制台。我现在有一个hacky解决方案,我将在下面展示。

import logging

DEFAULT_LOG_FORMAT = "%(asctime)s [%(levelname)s]: %(message)s"
DEFAULT_LOG_LEVEL = logging.INFO

def get_log_file_handler(filename, level=None, log_format=None):
  file_handler = logging.FileHandler(filename=filename, encoding="utf-8", mode="w")
  file_handler.setLevel(level or DEFAULT_LOG_LEVEL)
  file_handler.setFormatter(logging.Formatter(log_format or DEFAULT_LOG_FORMAT))

  return file_handler

def process(server):
  server_file_handler = get_log_file_handler("%s.log" % server.name)
  root_logger = logging.getLogger()

  # This works, but is hacky
  main_handlers = list(root_logger.handlers) # copy list of root log handlers
  root_logger.handlers = [] # empty the list on the root logger

  root_logger.addHandler(server_file_handler)

  try:
    # do some stuff with the server
    logging.info("This should show up only in the server-specific log file.")
  finally:
    root_logger.removeHandler(server_file_handler)

    # Add handlers back in
    for handler in main_handlers:
      root_logger.addHandler(handler)

def main():
  logging.basicConfig(level=DEFAULT_LOG_LEVEL)

  logging.getLogger().addHandler(get_log_file_handler("main.log"))

  servers = [] # retrieved from another function, just here for iteration

  logging.info("This should show up in the console and main.log.")

  for server in servers:
    process(server)

  logging.info("This should show up in the console and main.log again.")


if __name__ == "__main__":
  main()

我正在寻找一种不太常见的方式来做到这一点。我意识到只是调用logging.info()和类似的是一个问题,并且已经改变了两个模块中的代码使用:

logger = logging.getLogger("moduleA")

logger = logging.getLogger("moduleB")

因此主脚本,无论是scriptA.py还是scriptB.py,使用根记录器,将从这两个模块传播事件并将其记录到main.log中。我尝试过的其他一些解决方案是在所有现有的处理程序上使用过滤器,忽略了来自" moduleA"和" moduleB"。

我的下一个想法是为各个服务器创建一个新的命名记录器,并将server_file_handler作为它们的唯一处理程序,并将其添加为两个模块记录器的处理程序,并在进程结束时删除这些处理程序()。然后我可以将根记录器的级别设置为WARNING,因此来自这两个模块的所有INFO / DEBUG语句只会转到特定于服务器的记录器。

我不能完全使用分层记录器命名,除非支持通配符,不知何故,因为我最终会使用:

logging.getLogger("org.company") # main logger for script
logging.getLogger("org.company.serverA") 
logging.getLogger("org.company.serverB")
logging.getLogger("org.company.moduleA")
logging.getLogger("org.company.moduleB")

从两个模块进行记录只会传播到主记录器,而不会传播到两个服务器日志。

它基本上是一个他们期待的树,我需要一个图形问题。有没有人之前做过这样的事情,以及最恐怖的做法是什么?

3 个答案:

答案 0 :(得分:2)

这是一个有趣的问题。我的第一直觉是使用logger.getChild,但默认实现不会做你想要的。假设您可以动态地将处理程序添加到单个记录器,它仍然无法执行您想要的操作,因为您必须向主文件处理程序和服务器处理程序添加过滤器以过滤不应该使用的消息。 ;进入服务器日志和副作用。

也就是说,好消息是为每个孩子创建处理程序的自定义记录器实际上非常简单,可以通过修改getChild的简单子类来完成,而不是其他。

下面的重大更改只是HandlerPerChildLoggerLogger与普通Logger不同,因为它需要两个参数,而不仅仅是一个name参数。< / p>

import logging

DEFAULT_LOG_FORMAT = "%(asctime)s [%(levelname)s]: %(message)s"
DEFAULT_LOG_LEVEL = logging.INFO

class HandlerPerChildLogger(logging.Logger):
    selector = "server"

    def __init__(self, name, handler_factory, level=logging.NOTSET):
        super(HandlerPerChildLogger, self).__init__(name, level=level)
        self.handler_factory = handler_factory

    def getChild(self, suffix):
        logger = super(HandlerPerChildLogger, self).getChild(suffix)
        if not logger.handlers:
            logger.addHandler(self.handler_factory(logger.name))
            logger.setLevel(DEFAULT_LOG_LEVEL)
        return logger

def file_handler_factory(name):
    handler = logging.FileHandler(filename="{}.log".format(name), encoding="utf-8", mode="a")
    formatter = logging.Formatter(DEFAULT_LOG_FORMAT)
    handler.setFormatter(formatter)
    return handler

logger = HandlerPerChildLogger("my.company", file_handler_factory)
logger.setLevel(DEFAULT_LOG_LEVEL)
ch = logging.StreamHandler()
fh = logging.FileHandler(filename="my.company.log", encoding="utf-8", mode="a")
ch.setLevel(DEFAULT_LOG_LEVEL)
fh.setLevel(DEFAULT_LOG_LEVEL)
formatter = logging.Formatter(DEFAULT_LOG_FORMAT)
ch.setFormatter(formatter)
fh.setFormatter(formatter)
logger.addHandler(ch)
logger.addHandler(fh)

def process(server):
    server_logger = logger.getChild(server)
    server_logger.info("This should show up only in the server-specific log file for %s", server)
    server_logger.info("another log message for %s", server)

def main():
    # servers list retrieved from another function, just here for iteration
    servers = ["server1", "server2", "server3"]

    logger.info("This should show up in the console and main.log.")

    for server in servers:
        process(server)

    logger.info("This should show up in the console and main.log again.")

if __name__ == "__main__":
    main()

答案 1 :(得分:1)

main.log的处理程序保留到位可能会稍微整洁,但只需将其级别更改为足够高的值,以防止它在{{1}之前输出任何内容(例如logging.CRITICAL + 1)然后循环,然后恢复它。

答案 2 :(得分:1)

使用记录器命名和传播

如果您的模块使用名为org.company.moduleX的记录器,那么您可以将文件处理程序添加到名为org.company的记录器中,并使用Logger.propogate阻止传播到根记录器处理程序

将其实现为上下文管理器以使其更好。

import contextlib

log = logging.getLogger("org.company.scriptB")

@contextlib.contextmanager
def block_and_divert_logging(logger, new_handler):
    logger.propagate = False
    logger.addHandler(new_handler)
    try:
        yield
    finally:
        logger.propogate = True
        logger.removeHandler(new_handler)

def process(server):
    server_file_handler = get_log_file_handler("%s.log" % server.name)
    logger_block = logging.getLogger("org.company")

    with block_and_divert_logging(logger_block, server_file_handler):
        # do some stuff with the server
        log.info("This should show up only in the server-specific log file.")

这将阻止来自org.company或以下的记录器的任何消息到达根记录器处理程序。相反,它们将由您的文件处理程序处理。

但这确实意味着任何未命名为org.company.something的记录器仍会到达根记录器。