Python logging如何线程安全传递上下文?LoggerAdapter使用疑问
问题1:重复实例化LoggerAdapter是否符合规范?
完全符合规范。LoggerAdapter本身就是设计为轻量的包装层,本身不持有日志处理器、配置这类重资源,底层复用你传入的原生Logger实例,重复实例化不会产生额外的性能开销或者资源泄漏。你现在每个Job实例初始化一个绑定对应JobId的Adapter的用法,就是LoggerAdapter的典型适用场景,完全符合设计预期。
问题2:实现线程安全、支持读取本地元数据的日志方案
你当前的LoggerAdapter实现本身已经是线程安全的:每个Job实例的Adapter持有独立的extra上下文,不同线程运行不同Job实例时,日志输出的上下文不会互相窜乱,只要你不跨线程传递Adapter实例即可。
如果你需要更灵活的上下文传递(比如上下文是在线程运行过程中动态生成,不是初始化Job时就能确定的),可以结合threading.local和logging.Filter实现,不需要修改原有日志打印的代码,示例如下:
import logging import threading import uuid # 线程本地存储,存上下文信息 local_ctx = threading.local() class ContextFilter(logging.Filter): def filter(self, record): # 把线程本地的JobId绑定到日志record,没有的话填默认值 record.job_id = getattr(local_ctx, 'job_id', '-') return True class Job: def __init__(self, logger): self.job_id = str(uuid.uuid4()) self.logger = logger def run(self): # 运行时把当前Job的ID存到线程本地变量 local_ctx.job_id = self.job_id self.logger.info("log line here") # 线程退出前清理上下文,避免线程复用时窜乱 delattr(local_ctx, 'job_id') if __name__ == '__main__': LOG_FILE_PATH = './test.log' # 初始化日志的时候加上Filter,格式化字符串里直接用job_id变量 logging.basicConfig( filename=LOG_FILE_PATH, format='%(asctime)s [%(job_id)s] %(levelname)s: %(message)s' ) logger = logging.getLogger('ContextFilterTest') logger.addFilter(ContextFilter()) # 多线程跑Job的话,每个线程的job_id独立,日志不会串 ...
两种方案的选择逻辑:如果上下文和Job实例强绑定,你现在的LoggerAdapter方案更直观,上下文跟着Adapter走,不需要依赖线程本地存储;如果上下文是线程维度的动态参数,选Filter+threadlocal的方案更灵活。
内容的提问来源于stack exchange,提问作者Pankaj Kumar
相关产品推荐
相关产品推荐

