如何通过.ini配置文件实现Python日志的QueueHandler与QueueListener?
结合现有logging.ini配置实现非阻塞日志(QueueHandler + QueueListener)
问题背景
现有基于logging.ini配置的Python日志脚本,通过logging.config.fileConfig()加载配置为各模块创建日志实例。需通过QueueHandler和QueueListener实现非阻塞日志调用,但不清楚如何结合现有配置改造。
原logging.ini配置内容:
[loggers] keys=root,rcs_logger [handlers] keys=console,file [formatters] keys=file,console [logger_root] level=DEBUG handlers=console [logger_rcs_logger] level=DEBUG handlers=file,console qualname=rcs_logger propagate=0 [handler_console] class=StreamHandler level=DEBUG formatter=console args=(sys.stdout,) [handler_file] class=logging.handlers.TimedRotatingFileHandler level=DEBUG formatter=file args=('%(logfilename)s', 's', 5, 0, 'utf8') [formatter_file] format={"observed_timestamp": %(observedTime)d, "observed_timestamp_rfc3339": "%(observedTimeRfc3339)s", "intrumentation_scope": "%(filename)s", "hostname": "%(hostname)s", "severity_text": "%(levelname)s","severity_number": %(levelno)s, "event_timestamp": %(eventTime)s, "body": "%(message)s", "resource": "%(node)s", "span_id": "%(spanId)s", "trace_id": "%(traceId)s"} datefmt=%s [formatter_console] format=[%(levelname)s][%(levelno)s] %(message)s datefmt=
改造思路
核心逻辑:让业务线程仅负责将日志事件发送到队列(非阻塞),由独立的Listener线程从队列取出事件,交给原有配置好的handler(console、file)完成实际输出。具体步骤:
- 加载原有
logging.ini配置,保留已配置的handler、formatter和logger级别; - 创建日志队列,将原有handler绑定到QueueListener;
- 替换目标logger的原有handler为QueueHandler,实现日志事件转发;
- 启动QueueListener,程序结束时停止Listener确保所有日志被处理。
完整代码实现
import logging import logging.config import queue from logging.handlers import QueueHandler, QueueListener # 配置日志文件名(对应ini中的%(logfilename)s占位符) LOG_FILENAME = "app.log" # 加载原有ini配置,传入日志文件名参数 logging.config.fileConfig( "logging.ini", defaults={"logfilename": LOG_FILENAME}, disable_existing_loggers=False ) # 创建无限制大小的日志队列 log_queue = queue.Queue(-1) # 获取配置好的handler(Python 3.10+支持getHandlerByName,旧版本见下方说明) console_handler = logging.getHandlerByName("console") file_handler = logging.getHandlerByName("file") # 初始化QueueListener,传入队列和要使用的handler listener = QueueListener(log_queue, console_handler, file_handler) # 获取需要改造的logger实例 root_logger = logging.getLogger() rcs_logger = logging.getLogger("rcs_logger") # 替换logger的handler:移除原有阻塞handler,添加QueueHandler for logger in [root_logger, rcs_logger]: # 遍历并移除所有原有handler for handler in list(logger.handlers): logger.removeHandler(handler) # 添加QueueHandler,将日志事件转发到队列 logger.addHandler(QueueHandler(log_queue)) # 启动QueueListener线程 listener.start() # ------------------------------ # 业务代码中的日志使用示例 # ------------------------------ try: root_logger.debug("Root logger 调试信息") # 针对rcs_logger的自定义字段日志 rcs_logger.info("RCS logger 业务日志", extra={ "observedTime": 1718000000, "observedTimeRfc3339": "2024-06-10T12:00:00Z", "hostname": "prod-server-01", "eventTime": 1718000000, "node": "service-node-01", "spanId": "a1b2c3d4", "traceId": "e5f6g7h8" }) finally: # 程序结束前停止Listener,确保队列中所有日志都被处理 listener.stop()
关键细节说明
- Python版本兼容:若使用Python 3.10以下版本,
logging.getHandlerByName()不可用,可通过logging._handlers字典获取handler:console_handler = logging._handlers.get("console") file_handler = logging._handlers.get("file") - 避免重复日志:必须移除logger原有handler,否则会同时触发阻塞和非阻塞两种日志输出逻辑。
- 线程安全:
TimedRotatingFileHandler和StreamHandler均为线程安全,适合在QueueListener线程中使用。 - 队列配置:示例中队列大小设为
-1(无限制),若需限制容量可设置具体数值,此时QueueHandler的emit()会在队列满时阻塞;若想避免阻塞,可修改QueueHandler实现为queue.put_nowait(),但可能丢失日志。 - 自定义字段:
extra参数必须包含file formatter中定义的所有自定义字段(如observedTime、traceId等),否则会抛出KeyError。
内容的提问来源于stack exchange,提问作者Poorvi Sidhapura
相关产品推荐
相关产品推荐

