1import logging
2from threading import Thread
3
4from mozlog import commandline, stdadapter, set_default_logger
5from mozlog.structuredlog import StructuredLogger, log_levels
6
7
8def setup(args, defaults, formatter_defaults=None):
9    logger = args.pop('log', None)
10    if logger:
11        set_default_logger(logger)
12        StructuredLogger._logger_states["web-platform-tests"] = logger._state
13    else:
14        logger = commandline.setup_logging("web-platform-tests", args, defaults,
15                                           formatter_defaults=formatter_defaults)
16    setup_stdlib_logger()
17
18    for name in list(args.keys()):
19        if name.startswith("log_"):
20            args.pop(name)
21
22    return logger
23
24
25def setup_stdlib_logger():
26    logging.root.handlers = []
27    logging.root = stdadapter.std_logging_adapter(logging.root)
28
29
30class LogLevelRewriter(object):
31    """Filter that replaces log messages at specified levels with messages
32    at a different level.
33
34    This can be used to e.g. downgrade log messages from ERROR to WARNING
35    in some component where ERRORs are not critical.
36
37    :param inner: Handler to use for messages that pass this filter
38    :param from_levels: List of levels which should be affected
39    :param to_level: Log level to set for the affected messages
40    """
41    def __init__(self, inner, from_levels, to_level):
42        self.inner = inner
43        self.from_levels = [item.upper() for item in from_levels]
44        self.to_level = to_level.upper()
45
46    def __call__(self, data):
47        if data["action"] == "log" and data["level"].upper() in self.from_levels:
48            data = data.copy()
49            data["level"] = self.to_level
50        return self.inner(data)
51
52
53class LoggedAboveLevelHandler(object):
54    """Filter that records whether any log message above a certain level has been
55    seen.
56
57    :param min_level: Minimum level to record as a str (e.g., "CRITICAL")
58
59    """
60    def __init__(self, min_level):
61        self.min_level = log_levels[min_level.upper()]
62        self.has_log = False
63
64    def __call__(self, data):
65        if (data["action"] == "log" and
66            not self.has_log and
67            log_levels[data["level"]] <= self.min_level):
68            self.has_log = True
69
70
71class QueueHandler(logging.Handler):
72    def __init__(self, queue, level=logging.NOTSET):
73        self.queue = queue
74        logging.Handler.__init__(self, level=level)
75
76    def createLock(self):
77        # The queue provides its own locking
78        self.lock = None
79
80    def emit(self, record):
81        msg = self.format(record)
82        data = {"action": "log",
83                "level": record.levelname,
84                "thread": record.threadName,
85                "pid": record.process,
86                "source": self.name,
87                "message": msg}
88        self.queue.put(data)
89
90
91class LogQueueThread(Thread):
92    """Thread for handling log messages from a queue"""
93    def __init__(self, queue, logger):
94        self.queue = queue
95        self.logger = logger
96        super().__init__(name="Thread-Log")
97
98    def run(self):
99        while True:
100            try:
101                data = self.queue.get()
102            except (EOFError, IOError):
103                break
104            if data is None:
105                # A None message is used to shut down the logging thread
106                break
107            self.logger.log_raw(data)
108