You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过.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)完成实际输出。具体步骤:

  1. 加载原有logging.ini配置,保留已配置的handler、formatter和logger级别;
  2. 创建日志队列,将原有handler绑定到QueueListener;
  3. 替换目标logger的原有handler为QueueHandler,实现日志事件转发;
  4. 启动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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 18:35:26