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

标签 python python-2.7 logging python-requests

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

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

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

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

最佳答案

我想我已经为您找到了解决方案:

logging 模块是 built to be thread-safe :

The logging module is intended to be thread-safe without any special work needing to be done by its clients. It achieves this though using threading locks; there is one lock to serialize access to the module’s shared data, and each handler also creates a lock to serialize access to its underlying I/O.

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

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

所以思路是这样的:

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

main.py

from Queue import Queue
from threading import Thread
from usercode import fetch_url
import logging
import requests
import time


logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)


class SilencableHandler(logging.StreamHandler):

    def __init__(self, *args, **kwargs):
        self.silenced = False
        return super(SilencableHandler, self).__init__(*args, **kwargs)

    def emit(self, record):
        if not self.silenced:
            super(SilencableHandler, self).emit(record)


requests_logger = logging.getLogger('requests')
requests_logger.propagate = False
requests_handler = SilencableHandler()
requests_logger.addHandler(requests_handler)


class Silenced(object):

    def __init__(self, handler):
        self.handler = handler

    def __enter__(self):
        log.info("Silencing requests logger...")
        self.handler.acquire()
        self.handler.silenced = True
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.handler.silenced = False
        self.handler.release()
        log.info("Requests logger unsilenced.")


NUM_THREADS = 2
queue = Queue()

URLS = [
    'http://www.stackoverflow.com',
    'http://www.stackexchange.com',
    'http://www.serverfault.com',
    'http://www.superuser.com',
    'http://travel.stackexchange.com',
]


for i in range(NUM_THREADS):
    worker = Thread(target=fetch_url, args=(i, queue,))
    worker.setDaemon(True)
    worker.start()

for url in URLS:
    queue.put(url)


log.info('Starting long API request...')

with Silenced(requests_handler):
    time.sleep(5)
    requests.get('http://www.example.org/api')
    time.sleep(5)
    log.info('Done with long API request.')

queue.join()

usercode.py

import logging
import requests
import time


logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)


def fetch_url(i, q):
    while True:
        url = q.get()
        response = requests.get(url)
        logging.info("{}: {}".format(response.status_code, url))
        time.sleep(i + 2)
        q.task_done()

示例输出:

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

INFO:__main__:Starting long API request...
INFO:__main__:Silencing requests logger...
INFO:__main__:Requests logger unsilenced.
INFO:__main__:Done with long API request.
Starting new HTTP connection (1): www.stackoverflow.com
Starting new HTTP connection (1): www.stackexchange.com
Starting new HTTP connection (1): stackexchange.com
Starting new HTTP connection (1): stackoverflow.com
INFO:root:200: http://www.stackexchange.com
INFO:root:200: http://www.stackoverflow.com
Starting new HTTP connection (1): www.serverfault.com
Starting new HTTP connection (1): serverfault.com
INFO:root:200: http://www.serverfault.com
Starting new HTTP connection (1): www.superuser.com
Starting new HTTP connection (1): superuser.com
INFO:root:200: http://www.superuser.com
Starting new HTTP connection (1): travel.stackexchange.com
INFO:root:200: http://travel.stackexchange.com

线程代码基于 Doug Hellmann 在 threading 上的文章和 queues .

关于python - 在一个上下文中禁用 python 模块的日志记录,但在另一个上下文中禁用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26874749/

相关文章:

java - 在python中将字符串转换为短字符串

ruby - 在 ruby​​ 中仅记录一次特定事件

logging - 如何在XAMPP中启用错误报告

Python 2.7 混合迭代和读取方法会丢失数据

Python/正则表达式 : exclude everything except one thing

Python列表的列表操作顺序

python - 如何漂亮地打开Tkinter程序?

python - 检查字符串是否为空(或空格)

python - 使用 scikit-learn 进行文本特征提取

php - 接收串口数据 : real-time web display + logging (with downsampling)