在一个上下文中禁用python模块的日志记录而不是另一个上下文

时间:2014-11-11 21:17:33

标签: python python-2.7 logging python-requests

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

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

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

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

1 个答案:

答案 0 :(得分:4)

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

logging模块是built to be thread-safe

  

日志记录模块旨在线程安全,没有任何特殊之处   需要由客户完成的工作。它虽然使用它实现了这一点   穿线锁;有一个锁可以序列化对模块的访问   共享数据,每个处理程序还创建一个锁以序列化访问   它的基础I / O.

幸运的是,它暴露了通过公共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关于threadingqueues的文章。