从多个包层次结构进行Python日志记录(不使用根记录器)

从多个包层次结构进行Python日志记录(不使用根记录器),python,logging,packaging,Python,Logging,Packaging,我需要为一个相当具体的设置设置日志记录。简而言之,我想在两个不同的“父”模块中处理来自一段公共库代码的日志记录 app_one_main和app_two_main都导入lib_模块(代码如下) 这些模块显然不共享相同的包结构,因此如果我使用getLogger(\uu name\uuu) 限制 app_one和app_two都将在同一个Python会话中运行,因此我无法全局操作lib_模块中记录器的层次结构 我无法操作全局根记录器,因为我的代码被集成到一个更大的系统中 应用程序1和应用程序2具有

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

app_one_main和app_two_main都导入lib_模块(代码如下)

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

限制

  • app_one和app_two都将在同一个Python会话中运行,因此我无法全局操作lib_模块中记录器的层次结构
  • 我无法操作全局根记录器,因为我的代码被集成到一个更大的系统中
  • 应用程序1和应用程序2具有不同的处理程序。例如,他们将日志写入不同的文件
一些想法
  • 建议将父记录器传递到库代码的函数中。我想这是可行的,但它会破坏我现有的几乎所有代码,我对这种方式传递给伐木者并不感到兴奋
  • 我可以子类化
    logging.Logger
    并重写
    Logger.parent
    ,这样它就可以在其封闭范围内找到任何记录器。我在过去也实现过类似的功能,但它似乎有点设计过度,会破坏默认日志系统的许多功能
  • 代码 (这段代码甚至没有假装起作用。它只是一个粗略的起点。)

    期望结果 app_one和app_two最终将在另一个平台(如Maya)上运行,该平台提供一个python会话。这两个模块都将导入到同一个会话中

    因此,如果我运行
    app\u one\u main.log\u app\u one()
    ,我希望:

    APP ONE: hello from app_one_main
    APP ONE: hello from library code
    
    app\u two\u main.log\u app\u two()


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

    您的库记录器称为
    lib\u package.lib\u模块
    。移动到
    getLogger
    后,任何其他模块都可以获取包记录器
    lib_-package
    ,对其进行配置,然后其任何子记录器也将工作

    app\u one\u 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_module.py
    
    import logging
    
    logger = logging.getLogger(__name__)
    
    def do_the_thing():
        logger.warning("hello from library code")
    
    lib_包/lib_模块.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_module.py
    
    import logging
    
    logger = logging.getLogger(__name__)
    
    def do_the_thing():
        logger.warning("hello from library code")
    

    这就是我降落的地方

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

    代码 自定义日志记录模块 特殊酱汁在
    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\u one/app\u one\u main.py app_two/app_two_main.py lib_package.lib_module.py
    “应用程序1和应用程序2都将在同一个Python会话中运行”你是什么意思?您是通过
    import
    运行它们,还是通过
    python appx.py
    调用独立脚本?如果您通过导入运行,则需要重新构造文件,导入模块不应产生副作用,即,您应该将逻辑包装在方法中,在脚本情况下,仅当
    \uuuuu name\uuuu==“\uuuu main\uuuuu”
    您能回答@flakes问题吗?如果这些是脚本而不是导入的模块,那么设置根记录器就是一种方法。通常,导入的模块应该登录到一个命名的记录器,但脚本的任务是设置日志记录。您可以
    getLogger(“lib_module”)
    并在那里配置日志记录-但是如果有很多模块,那就不是非常可伸缩的。@它们将通过导入到更大的上下文中来运行-即,作为在Maya、3DS Max中运行的插件,或者其他内容创建平台。@tdelaney这也是我不能真正使用根记录器的原因。我们使用的一些工具已经“拥有”了根,使用它可能非常棘手。我不知道这是否可能,但我希望能够导入一个模块并让其日志记录通过封闭范围内的处理程序,可能是多个(不同)封闭范围。注意-最初的问题使用了
    logging.Logger()
    直接创建日志记录程序,我本想使用logging.getLogger,但没有找到。我已经更新了代码来反映这一点。哎呀,这实际上是一个疏忽,我想从一开始就使用getLogger。我将编辑原文以反映这一点。这个解决方案的一个问题是——当我在app_two_main.py中执行完全相同的操作时会发生什么?那么lib_module.logger会有两个流处理程序吗?是的。您可以使用
    logging.getLogger('lib_package.lib_module').handlers
    验证这一点。每件事你都会得到两份日志。这不是我真正想要的行为,但这是一个好的开始。我只希望在任何给定时间运行一个app_x模块的处理程序。。。我想知道处理器上是否有一个额外的过滤器可以做到这一点。我将再次更新我的原始代码,使用例更加清晰too@EdW-是的,我想知道。过滤器可以向下查看堆栈
    # lib_module.py
    
    import logging
    
    logger = logging.getLogger(__name__)
    
    def do_the_thing():
        logger.warning("hello from library code")
    
    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)
    
    from app_one import app_one_main
    from app_two import app_two_main
    
    app_one_main.log_it()
    app_two_main.log_it()
    
    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()
    
    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()
    
    import custom_logging
    
    logger = custom_logging.get_logger(__name__)
    
    def do_the_thing():
        logger.warning("hello from library code")