from collections.abc import Mapping
from logging import Formatter, LogRecord
import json


#: Attributes of a LogRecord. See <the docs `https://docs.python.org/3/library/logging.html#logrecord-attributes`>_.
RECORD_ATTRIBUTES = (
    'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename', 'funcName',
    'levelname', 'levelno', 'lineno', 'message', 'module', 'msecs',
    'msg', 'name', 'pathname', 'process', 'processName', 'relativeCreated',
    'stack_info', 'thread', 'threadName',
)


class JsonFormatter(Formatter):
    """
    Formatter that emits data as JSON

    :param fmt: default data for all records
    :param datefmt: format for timestamps

    The ``datefmt`` parameter has almost the same meaning as for :py:class:`~.Formatter`.
    Setting it to ``None`` uses the default time format.
    However, setting it to any other value that is boolean false excludes the timestamp from reports.
    """
    def __init__(self, fmt: dict = None, datefmt: str = None):
        super().__init__(fmt=None, datefmt=datefmt, style='%')
        self._defaults = fmt or {}
        if not isinstance(self._defaults, Mapping):
            raise TypeError('`fmt` must be a Mapping or None')
        self._add_time = self.datefmt or self.datefmt is None

    def format(self, record: LogRecord):
        args = record.args
        assert isinstance(args, Mapping), 'monitor record argument must be a mapping, not %r' % type(args)
        data = self._defaults
        if self._add_time:
            data['time'] = self.formatTime(record, self.datefmt)
        data['message'] = record.getMessage()
        data.update(record.args)
        return json.dumps(data)


if __name__ == '__main__':
    import logging
    logger = logging.getLogger()
    logger.handlers = [logging.StreamHandler()]
    logger.handlers[0].formatter = JsonFormatter({'latitude': 49, 'longitude': 8})
    logger.warning('forecast', {'temperature': 298, 'humidity': 0.45})
