Python 从多个包层次结构中记录(不使用根记录器)

Python logging from multiple package hierarchies (without using root logger)

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

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

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

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

限制

一些想法

  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_oneapp_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 文档说 Loggers 不应该直接实例化,但总是通过 module-level 函数 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")

这是我降落的地方。

概览

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

代码

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")