summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/harness/wptrunner/wptlogging.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/harness/wptrunner/wptlogging.py')
-rw-r--r--testing/web-platform/harness/wptrunner/wptlogging.py125
1 files changed, 125 insertions, 0 deletions
diff --git a/testing/web-platform/harness/wptrunner/wptlogging.py b/testing/web-platform/harness/wptrunner/wptlogging.py
new file mode 100644
index 000000000..047e025fa
--- /dev/null
+++ b/testing/web-platform/harness/wptrunner/wptlogging.py
@@ -0,0 +1,125 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import logging
+import sys
+import threading
+from StringIO import StringIO
+from multiprocessing import Queue
+
+from mozlog import commandline, stdadapter
+
+def setup(args, defaults):
+ logger = commandline.setup_logging("web-platform-tests", args, defaults)
+ setup_stdlib_logger()
+
+ for name in args.keys():
+ if name.startswith("log_"):
+ args.pop(name)
+
+ return logger
+
+
+def setup_stdlib_logger():
+ logging.root.handlers = []
+ logging.root = stdadapter.std_logging_adapter(logging.root)
+
+
+class LogLevelRewriter(object):
+ """Filter that replaces log messages at specified levels with messages
+ at a different level.
+
+ This can be used to e.g. downgrade log messages from ERROR to WARNING
+ in some component where ERRORs are not critical.
+
+ :param inner: Handler to use for messages that pass this filter
+ :param from_levels: List of levels which should be affected
+ :param to_level: Log level to set for the affected messages
+ """
+ def __init__(self, inner, from_levels, to_level):
+ self.inner = inner
+ self.from_levels = [item.upper() for item in from_levels]
+ self.to_level = to_level.upper()
+
+ def __call__(self, data):
+ if data["action"] == "log" and data["level"].upper() in self.from_levels:
+ data = data.copy()
+ data["level"] = self.to_level
+ return self.inner(data)
+
+
+
+class LogThread(threading.Thread):
+ def __init__(self, queue, logger, level):
+ self.queue = queue
+ self.log_func = getattr(logger, level)
+ threading.Thread.__init__(self, name="Thread-Log")
+ self.daemon = True
+
+ def run(self):
+ while True:
+ try:
+ msg = self.queue.get()
+ except (EOFError, IOError):
+ break
+ if msg is None:
+ break
+ else:
+ self.log_func(msg)
+
+
+class LoggingWrapper(StringIO):
+ """Wrapper for file like objects to redirect output to logger
+ instead"""
+
+ def __init__(self, queue, prefix=None):
+ StringIO.__init__(self)
+ self.queue = queue
+ self.prefix = prefix
+
+ def write(self, data):
+ if isinstance(data, str):
+ data = data.decode("utf8")
+
+ if data.endswith("\n"):
+ data = data[:-1]
+ if data.endswith("\r"):
+ data = data[:-1]
+ if not data:
+ return
+ if self.prefix is not None:
+ data = "%s: %s" % (self.prefix, data)
+ self.queue.put(data)
+
+ def flush(self):
+ pass
+
+
+class CaptureIO(object):
+ def __init__(self, logger, do_capture):
+ self.logger = logger
+ self.do_capture = do_capture
+ self.logging_queue = None
+ self.logging_thread = None
+ self.original_stdio = None
+
+ def __enter__(self):
+ if self.do_capture:
+ self.original_stdio = (sys.stdout, sys.stderr)
+ self.logging_queue = Queue()
+ self.logging_thread = LogThread(self.logging_queue, self.logger, "info")
+ sys.stdout = LoggingWrapper(self.logging_queue, prefix="STDOUT")
+ sys.stderr = LoggingWrapper(self.logging_queue, prefix="STDERR")
+ self.logging_thread.start()
+
+ def __exit__(self, *args, **kwargs):
+ if self.do_capture:
+ sys.stdout, sys.stderr = self.original_stdio
+ if self.logging_queue is not None:
+ self.logger.info("Closing logging queue")
+ self.logging_queue.put(None)
+ if self.logging_thread is not None:
+ self.logging_thread.join(10)
+ self.logging_queue.close()
+ self.logger.info("queue closed")