如何在不修改上游模块代码的前提下,通过自有代码配置日志并实现代码分离?
不修改上游模块:如何通过自有代码配置日志并实现代码与模块解耦?
我来给你几个实用的方案,完全不需要改动上游mptools模块的代码,就能让模块的日志输出到你配置的syslog里:
方案1:利用Python日志系统的层级配置(最推荐)
如果上游模块的日志是基于Python标准logging模块实现的(比如原本用logging.log()或者模块专属的日志器),你完全可以通过配置日志字典来接管模块的日志输出,不用碰模块代码。
具体做法:
- 调整你的日志配置,要么直接配置根日志器(覆盖模块里的
logging.log()调用),要么单独配置模块对应的日志器:
# 自有代码部分 import sys import logging from logging import config LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '%(levelname)s %(module)s P%(process)d T%(thread)d %(message)s' }, }, 'handlers': { 'stdout': { 'class': 'logging.StreamHandler', 'stream': sys.stdout, 'formatter': 'verbose', }, 'sys-logger6': { 'class': 'logging.handlers.SysLogHandler', "address": ["127.0.0.1", 514], "facility": "local6", 'formatter': 'verbose', }, }, # 配置根日志器:模块里的logging.log()会自动使用这个配置 'root': { 'handlers': ['sys-logger6', 'stdout'], 'level': logging.DEBUG, }, # 如果你需要自己的独立日志器,继续保留 'loggers': { 'my-logger': { 'handlers': ['sys-logger6', 'stdout'], 'level': logging.DEBUG, 'propagate': False, # 避免日志重复输出到根日志器 }, } } config.dictConfig(LOGGING) my_logger = logging.getLogger('my-logger')
如果模块用的是自己的日志器(比如logging.getLogger(__name__)),你只需要在loggers里添加模块名对应的配置:
'loggers': { 'mptools': { # 替换成实际的模块名 'handlers': ['sys-logger6', 'stdout'], 'level': logging.DEBUG, 'propagate': True, }, 'my-logger': { # 你的配置... } }
方案2:猴子补丁替换模块的日志函数
如果模块里的日志逻辑是硬编码的(比如像你示例里的_logger函数直接调用某个日志器),可以用猴子补丁在运行时替换模块的日志函数,让它用你的配置:
具体做法:
# 自有代码部分 import sys import logging from logging import config from functools import partial import mptools # 导入上游模块 # 先配置好你的日志 LOGGING = { # 你的原配置... } config.dictConfig(LOGGING) my_logger = logging.getLogger('my-logger') # 定义我们自己的日志函数,替换模块里的_logger def patched_module_logger(level, msg, exc_info=None): my_logger.log(level, f'{msg}', exc_info=exc_info) # 替换模块的_logger函数 mptools._logger = patched_module_logger # 之后创建的ContextManager实例会自动使用我们的日志配置 def main(): with mptools.ContextManager() as ctx: ctx.log(logging.INFO, f"running in main") if __name__ == "__main__": main()
注意:这个方案只对补丁之后创建的模块实例生效,如果模块在导入时已经创建了实例,那这些旧实例还是会用原来的日志逻辑。
方案3:继承模块类并覆盖日志逻辑
如果模块的类(比如ContextManager)是可继承的,你可以创建一个子类,覆盖日志相关的方法,完全用自己的日志实现:
# 自有代码部分 import sys import logging from logging import config from functools import partial import mptools # 配置日志... config.dictConfig(LOGGING) my_logger = logging.getLogger('my-logger') # 继承模块的ContextManager,替换日志逻辑 class MyContextManager(mptools.ContextManager): def __init__(self): # 用我们自己的logger创建partial self.log = partial(my_logger.log, "init") def main(): with MyContextManager() as ctx: ctx.log(logging.INFO, f"running in main") if __name__ == "__main__": main()
这个方案最稳妥,完全隔离了模块代码和你的自定义逻辑,适合需要高度定制的场景。
内容的提问来源于stack exchange,提问作者Andreas Schuldei
相关产品推荐
相关产品推荐

