| # 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/. |
| |
| from threading import Lock |
| import codecs |
| |
| from ..structuredlog import log_levels |
| |
| |
| class BaseHandler(object): |
| """A base handler providing message handling facilities to |
| derived classes. |
| |
| :param inner: A handler-like callable that may receive messages |
| from a log user. |
| """ |
| |
| def __init__(self, inner): |
| self.wrapped = [] |
| if hasattr(inner, "handle_message"): |
| self.wrapped.append(inner) |
| self.message_handlers = {} |
| |
| def register_message_handlers(self, topic, handlers): |
| self.message_handlers[topic] = handlers |
| |
| def handle_message(self, topic, cmd, *args): |
| """Handles a message for the given topic by calling a subclass-defined |
| callback for the command. |
| |
| :param topic: The topic of the broadcasted message. Handlers opt-in to |
| receiving messages by identifying a topic when calling |
| register_message_handlers. |
| :param command: The command to issue. This is a string that corresponds |
| to a callback provided by the target. |
| :param arg: Arguments to pass to the identified message callback, if any. |
| """ |
| rv = [] |
| if topic in self.message_handlers and cmd in self.message_handlers[topic]: |
| rv.append(self.message_handlers[topic][cmd](*args)) |
| for inner in self.wrapped: |
| rv.extend(inner.handle_message(topic, cmd, *args)) |
| return rv |
| |
| |
| class LogLevelFilter(BaseHandler): |
| """Handler that filters out messages with action of log and a level |
| lower than some specified level. |
| |
| :param inner: Handler to use for messages that pass this filter |
| :param level: Minimum log level to process |
| """ |
| def __init__(self, inner, level): |
| BaseHandler.__init__(self, inner) |
| self.inner = inner |
| self.level = log_levels[level.upper()] |
| |
| def __call__(self, item): |
| if (item["action"] != "log" or |
| log_levels[item["level"].upper()] <= self.level): |
| return self.inner(item) |
| |
| |
| class StreamHandler(BaseHandler): |
| """Handler for writing to a file-like object |
| |
| :param stream: File-like object to write log messages to |
| :param formatter: formatter to convert messages to string format |
| """ |
| |
| _lock = Lock() |
| |
| def __init__(self, stream, formatter): |
| BaseHandler.__init__(self, formatter) |
| assert stream is not None |
| # This is a hack to deal with the case where we are passed a |
| # StreamWriter (e.g. by mach for stdout). A StreamWriter requires |
| # the code to handle unicode in exactly the opposite way compared |
| # to a normal stream i.e. you always have to pass in a Unicode |
| # object rather than a string object. Cope with that by extracting |
| # the underlying raw stream. |
| if isinstance(stream, codecs.StreamWriter): |
| stream = stream.stream |
| |
| self.formatter = formatter |
| self.stream = stream |
| |
| def __call__(self, data): |
| """Write a log message. |
| |
| :param data: Structured log message dictionary.""" |
| formatted = self.formatter(data) |
| if not formatted: |
| return |
| with self._lock: |
| if isinstance(formatted, unicode): |
| self.stream.write(formatted.encode("utf-8", "replace")) |
| elif isinstance(formatted, str): |
| self.stream.write(formatted) |
| else: |
| assert False, "Got output from the formatter of an unexpected type" |
| |
| self.stream.flush() |