在一个上下文中禁用 python 模块的日志记录,但在另一个上下文中禁用

And*_*ter 4 python logging python-2.7 python-requests

我有一些代码使用该requests模块与日志记录 API 进行通信。然而,requests它本身通过 进行urllib3日志记录。当然,我需要禁用日志记录,以便对日志记录 API 的请求不会导致日志无限循环。因此,在我进行日志记录调用的模块中,我会logging.getLogger("requests").setLevel(logging.CRITICAL)静音例行请求日志。

但是,此代码旨在加载和运行任意用户代码。由于 pythonlogging模块显然使用全局状态来管理给定记录器的设置,我担心用户的代码可能会重新打开日志记录并导致问题,例如,如果他们天真地在代码中使用 requests 模块,而没有意识到我已禁用日志记录这是有原因的。

当从我的代码上下文中执行请求模块时,如何禁用该模块的日志记录,但从用户的角度来看,不影响该模块的记录器的状态?某种类型的上下文管理器可以静默对管理器内代码的日志记录的调用,这将是理想的选择。能够使用唯一的名称加载请求模块,__name__以便记录器使用不同的名称也可以工作,尽管这有点复杂。不过,我找不到办法做这两件事。

遗憾的是,该解决方案需要处理多个线程,因此按程序关闭日志记录,然后运行 ​​API 调用,然后重新打开它将不起作用,因为全局状态已发生变化。

Luk*_*raf 5

我想我已经为你找到了一个解决方案:

\n\n

logging模块被构建为线程安全的

\n\n
\n

日志记录模块旨在是线程安全的,不需要其客户端完成任何特殊的工作。它通过使用线程锁来实现这一点;有一个锁用于序列化对模块\xe2\x80\x99s\n 共享数据的访问,并且每个处理程序还创建一个锁来序列化\n 对其底层 I/O 的访问。

\n
\n\n

幸运的是,它通过公共 API 公开了提到的第二个锁:Handler.acquire()允许您获取特定日志处理程序的锁(并Handler.release()再次释放它)。获取该锁将阻止所有其他尝试记录将由该处理程序处理的记录的线程,直到锁被释放。

\n\n

这允许您以线程安全的方式操纵处理程序的状态。需要注意的是:因为它的目的是作为处理程序 I/O 操作的锁,所以该锁只能在emit(). 因此,只有当一条记录通过过滤器和日志级别并由特定处理程序发出时,才会获取锁。这就是为什么我必须子类化处理程序并创建SilencableHandler.

\n\n

所以想法是这样的:

\n\n
    \n
  • 获取模块最顶层的记录器requests并停止其传播
  • \n
  • 创建您的自定义SilencableHandler并将其添加到请求记录器
  • \n
  • 使用Silenced上下文管理器有选择地静音SilencableHandler
  • \n
\n\n

main.py

\n\n
from Queue import Queue\nfrom threading import Thread\nfrom usercode import fetch_url\nimport logging\nimport requests\nimport time\n\n\nlogging.basicConfig(level=logging.INFO)\nlog = logging.getLogger(__name__)\n\n\nclass SilencableHandler(logging.StreamHandler):\n\n    def __init__(self, *args, **kwargs):\n        self.silenced = False\n        return super(SilencableHandler, self).__init__(*args, **kwargs)\n\n    def emit(self, record):\n        if not self.silenced:\n            super(SilencableHandler, self).emit(record)\n\n\nrequests_logger = logging.getLogger(\'requests\')\nrequests_logger.propagate = False\nrequests_handler = SilencableHandler()\nrequests_logger.addHandler(requests_handler)\n\n\nclass Silenced(object):\n\n    def __init__(self, handler):\n        self.handler = handler\n\n    def __enter__(self):\n        log.info("Silencing requests logger...")\n        self.handler.acquire()\n        self.handler.silenced = True\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        self.handler.silenced = False\n        self.handler.release()\n        log.info("Requests logger unsilenced.")\n\n\nNUM_THREADS = 2\nqueue = Queue()\n\nURLS = [\n    \'http://www.stackoverflow.com\',\n    \'http://www.stackexchange.com\',\n    \'http://www.serverfault.com\',\n    \'http://www.superuser.com\',\n    \'http://travel.stackexchange.com\',\n]\n\n\nfor i in range(NUM_THREADS):\n    worker = Thread(target=fetch_url, args=(i, queue,))\n    worker.setDaemon(True)\n    worker.start()\n\nfor url in URLS:\n    queue.put(url)\n\n\nlog.info(\'Starting long API request...\')\n\nwith Silenced(requests_handler):\n    time.sleep(5)\n    requests.get(\'http://www.example.org/api\')\n    time.sleep(5)\n    log.info(\'Done with long API request.\')\n\nqueue.join()\n
Run Code Online (Sandbox Code Playgroud)\n\n

usercode.py

\n\n
import logging\nimport requests\nimport time\n\n\nlogging.basicConfig(level=logging.INFO)\nlog = logging.getLogger(__name__)\n\n\ndef fetch_url(i, q):\n    while True:\n        url = q.get()\n        response = requests.get(url)\n        logging.info("{}: {}".format(response.status_code, url))\n        time.sleep(i + 2)\n        q.task_done()\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出示例:

\n\n

(请注意如何http://www.example.org/api不记录对的调用,并且尝试记录请求的所有线程在前 10 秒内都会被阻止)。

\n\n
INFO:__main__:Starting long API request...\nINFO:__main__:Silencing requests logger...\nINFO:__main__:Requests logger unsilenced.\nINFO:__main__:Done with long API request.\nStarting new HTTP connection (1): www.stackoverflow.com\nStarting new HTTP connection (1): www.stackexchange.com\nStarting new HTTP connection (1): stackexchange.com\nStarting new HTTP connection (1): stackoverflow.com\nINFO:root:200: http://www.stackexchange.com\nINFO:root:200: http://www.stackoverflow.com\nStarting new HTTP connection (1): www.serverfault.com\nStarting new HTTP connection (1): serverfault.com\nINFO:root:200: http://www.serverfault.com\nStarting new HTTP connection (1): www.superuser.com\nStarting new HTTP connection (1): superuser.com\nINFO:root:200: http://www.superuser.com\nStarting new HTTP connection (1): travel.stackexchange.com\nINFO:root:200: http://travel.stackexchange.com\n
Run Code Online (Sandbox Code Playgroud)\n\n

线程代码基于 Doug Hellmann 关于线程队列的文章。

\n