blob: 73cbdbd7d57a585a5027139fc6c7e1ae032cf34e [file] [log] [blame]
from __future__ import print_function
import logging
import collections
import pprint
from threading import Lock
try:
from time import monotonic
except ImportError: # pragma: no cover
from monotonic import monotonic
class CollectionLogger(logging.Logger, object):
"""This logger appends log messages onto a collection.
Implemented as a logger rather than a handler because it's the simplest
way to integrate with monotonic() - built-in loggers do not support
setting "created" on log records by a custom clock."""
def __init__(self, collection):
self.collection = collection
self.lock = Lock()
super(CollectionLogger, self).__init__(collection)
def callHandlers(self, record):
now = monotonic()
record_msg = record.msg % record.args
msg = "%s: %s" % (now, record_msg)
with self.lock:
self.collection.append(msg)
def add_debug_logging(ex):
"""Hack the logger on an executor (if any) so that it records messages
with timestamps on the executor itself.
This is intended to integrate with the dump_executor function, so that
events on an executor can be easily seen after a test failure.
Note that it hijacks the usual logging, so e.g. pytest's log capturing
won't work any more."""
delegate = getattr(ex, "_delegate", None)
if delegate:
add_debug_logging(delegate)
if not getattr(ex, "_log", None):
return
logs = collections.deque(maxlen=1000)
logger = CollectionLogger(logs)
logger.setLevel(logging.DEBUG)
ex.__logs = logs
ex._log = logger
def dump_executor(ex):
print("Executor %s:" % ex)
all_vars = vars(ex)
# Don't worry about the with_* stuff from Executors
dump_vars = {}
for key, val in all_vars.items():
if key.startswith("with_"):
continue
dump_vars[key] = val
if "__logs" in dump_vars:
lock = dump_vars["_log"].lock
else:
lock = Lock()
with lock:
pprint.pprint(dump_vars, indent=2, width=400)
delegate = getattr(ex, "_delegate", None)
if delegate:
print("")
dump_executor(delegate)