我有一些代码使用requests
模块与日志记录API进行通信。但是,requests
本身通过urllib3
进行日志记录。当然,我需要禁用日志记录,以便对日志记录API的请求不会导致无限循环的日志。因此,在我执行日志记录调用的模块中,我执行logging.getLogger("requests").setLevel(logging.CRITICAL)
来静音例程请求日志。
但是,此代码旨在加载和运行任意用户代码。由于python logging
模块显然使用全局状态来管理给定记录器的设置,我担心用户的代码可能会重新开启记录并导致问题,例如,如果他们天真地使用请求模块他们的代码没有意识到我已经因为某种原因禁用了日志记录。
如何从我的代码上下文执行请求模块时禁用日志记录,但是从用户的角度来看,不会影响模块的记录器状态?某种上下文管理器会在管理器中静默调用记录代码,这是理想的。能够使用唯一的__name__
加载请求模块,以便记录器使用不同的名称也可以工作,尽管它有点复杂。但是,我找不到办法做这些事情。
令人遗憾的是,该解决方案需要处理多个线程,因此在程序上关闭日志记录,然后运行API调用,然后重新打开它将无法正常工作,因为全局状态发生了变异。
答案 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