从多个程序包层次结构中记录Python不使用root记录器

问题描述

我需要为特定的设置设置日志记录。简而言之,我想在两个不同的“父”模块中处理来自公共库代码的日志记录。

  app_one \
     app_one_main.py
  app_two \
     app_two_main.py
  lib_package \
     lib_module.py

app_one_main app_two_main 都导入 lib_module (下面的代码)。

这些模块显然不共享相同的包结构,因此,如果我使用的是getLogger(__name__)

,则认情况下,来自lib_module的日志记录消息不会传播到app_one或app_two。

限制

  • app_one app_two 都将在同一Python会话中运行,因此我无法在 lib_module 中全局操作记录器的层次结构>。
  • 由于我的代码被集成到更大的系统中,因此我无法操作全局根记录器。
  • app_one app_two 具有不同的处理程序。例如,他们将日志写入不同的文件

一些想法

  1. This answer建议将父记录器传递给库代码函数。我猜这会起作用,但是它将破坏几乎所有现有代码,并且我对通过这种方式传递记录器并不感到兴奋。
  2. 我可以继承logging.Logger的子类并覆盖Logger.parent,以便它可以在其包围范围内找到任何记录器。过去,我已经实现了类似的功能,但似乎有些过分设计,并且会破坏认日志记录系统的许多功能

代码

(此代码甚至不假装正常工作。这只是一个粗略的起点。)

# app_one_main.py

import logging
from lib_package import lib_module

logger = logging.getLogger(__name__)

stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter("APP ONE: %(message)s"))
logger.addHandler(stream_handler)

def log_app_one():
    logger.warning("hello from app_one_main")
    lib_module.do_the_thing()
# app_two_main.py

import logging
from lib_package import lib_module

logger = logging.getLogger(__name__)

stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter("APP TWO: %(message)s"))
logger.addHandler(stream_handler)

def log_app_two():
    logger.warning("hello from app_two_main")
    lib_module.do_the_thing()
# lib_module.py

import logging

logger = logging.getLogger(__name__)

def do_the_thing():
    logger.warning("hello from library code")

所需结果

app_one app_two 最终将在Maya之类的另一个平台上运行,该平台提供单个python会话。这两个模块都将导入到同一会话中。

因此,如果我运行app_one_main.log_app_one(),我会想要:

APP ONE: hello from app_one_main
APP ONE: hello from library code

还有app_two_main.log_app_two()

APP TWO: hello from app_two_main
APP TWO: hello from library code

解决方法

主要问题是您要直接实例化Logger对象,而不是使用getLogger为您获取它们。 Logger objects文档说,永远不要直接实例化Logger,而应始终通过模块级函数logging.getLogger(name)实例化。getLogger创建Logger时它还会将其插入到日志记录层次结构中,以便其他人可以对其进行配置。您只有自由的浮动Logger对象。

您的库记录器称为lib_package.lib_module。移至getLogger后,任何其他模块都可以获取包记录器lib_package,对其进行配置,然后其任何子记录器也将起作用。

app_one_main.py

import logging
from lib_package import lib_module

# setup logging output for this module
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter("APP ONE: %(message)s"))
logger = logging.getLogger(__name__)
#logger.setLevel(logging.INFO)
logger.addHandler(stream_handler)

# add handler other modules / packages
pkg_logger = logging.getLogger('lib_package')
pkg_logger.addHandler(stream_handler)
#pkg_logger.setLevel(logging.INFO)
del pkg_logger

logger.warning("hello from app_one_main")
lib_module.do_the_thing()

lib_package / lib_module.py

# lib_module.py

import logging

logger = logging.getLogger(__name__)

def do_the_thing():
    logger.warning("hello from library code")
,

这是我登陆的地方。

概述

我正在为所有要使用的工具创建一个通用记录器,并为生活在其上的处理程序创建一个新的Filter子类。过滤器确保仅处理源自与过滤器本身相同的父模块的依赖项的消息。从理论上讲,您可以在根记录器上使用此Filter类和堆栈查找机制,而不是在此“基本记录器”上使用。

代码

custom_logging模块

特殊的调味料在StackFilter中。它将当前执行堆栈中的模块与实例化时存储的模块进行比较。在某些情况下,这可能行不通,但我还没有找到它们。

import logging
import inspect

# Loggers
BASE_LOGGER_NAME = "_base_logger_"

def get_base_logger():
    return logging.getLogger(BASE_LOGGER_NAME)

def get_logger(name):
    return logging.getLogger(BASE_LOGGER_NAME + "." + name)


# Filtering
class StackFilter(logging.Filter):
    def __init__(self):
        self.stack = set(enclosing_modules())
        super(StackFilter,self).__init__()

    def filter(self,record):
        calling_stack = set(enclosing_modules())
        return self.stack.issubset(calling_stack)

def enclosing_modules():
    frame = inspect.currentframe()

    frame_count = 0
    _frame = frame
    while _frame:
        frame_count += 1
        _frame = _frame.f_back
    mods = [None] * frame_count

    i = 0
    while frame:
        try:
            mods[i] = frame.f_globals["__name__"]
        except:
            pass
        i += 1
        frame = frame.f_back

    return mods

# Logging Handlers
def add_console_output(formatter=None):
    base_logger = get_base_logger()
    handler = logging.StreamHandler()
    if formatter:
        handler.setFormatter(formatter)
    handler.addFilter(StackFilter())
    base_logger.addHandler(handler)

其余代码与原始问题非常相似,但是我添加了一个新模块来检查我的工作。

fake_maya / fake_maya_main.py

这将在今天的Maya中发挥作用,只是将导入和运行我的各种工具的地方。

from app_one import app_one_main
from app_two import app_two_main

app_one_main.log_it()
app_two_main.log_it()

app_one / app_one_main.py

from lib_package import lib_module
import logging
import custom_logging

logger = custom_logging.get_logger(__name__)
formatter = logging.Formatter("APP ONE: %(message)s")
custom_logging.add_console_output(formatter)

def log_it():
    logger.warning("hello from app_one_main")
    lib_module.do_the_thing()

app_two / app_two_main.py

from lib_package import lib_module
import logging
import custom_logging

logger = custom_logging.get_logger(__name__)
formatter = logging.Formatter("APP TWO: %(message)s")
custom_logging.add_console_output(formatter)

def log_it():
    logger.warning("hello from app_two_main")
    lib_module.do_the_thing()

lib_package.lib_module.py

import custom_logging

logger = custom_logging.get_logger(__name__)

def do_the_thing():
    logger.warning("hello from library code")