"""Logging utilities.""" import logging import simplejson as json from collections import OrderedDict from datetime import datetime, timezone from src.utils.json import serialize_value class JsonFormatter(logging.Formatter): """Format message as JSON. Args: datefmt (str): Same as corresponding parameter for parent class. style (str): Same as corresponding parameter for parent class. This formatter does not receive fmt argument. """ def __init__(self, datefmt=None, style="%"): super(JsonFormatter, self).__init__(None, datefmt, style) def get_extra_fields(self, record): """Return additional fields for adding to the result JSON. Python logging documentation lists standard record attributes: http://docs.python.org/library/logging.html#logrecord-attributes Most of them are added to JSON by the format() function. Additional fields may be provided by means of extra kwarg to logger.debug (info, warning, error, critical). This method extracts additional fields from record.__dict__ and returns them. Args: record (logging.LogRecord): Instance of LogRecord. Returns: list: List of tuples with extra attribute names and values. """ skip_list = ( "args", "asctime", "created", "exc_info", "exc_text", "filename", "funcName", "id", "levelname", "levelno", "lineno", "message", "module", "msecs", "msg", "name", "pathname", "process", "processName", "relativeCreated", "stack_info", "thread", "threadName", "extra", ) return [ (key, serialize_value(value, self.datefmt)) for key, value in sorted(record.__dict__.items()) if key not in skip_list ] def formatTime(self, record, datefmt=None): """Format timestamp of log record. Args: record (logging.LogRecord): LogRecord instance. datefmt (str): If provided, time will be formatted by strftime using this parameter as format string. Otherwise default format is applied. In both cases formatted time will be in UTC. Returns: str: Formatted timestamp. """ ts = datetime.utcfromtimestamp(record.created).replace(tzinfo=timezone.utc) if datefmt: return ts.strftime(datefmt) return ts.isoformat() def format(self, record): """Format message as JSON. Args: record (logging.LogRecord): LogRecord instance. Returns: str: Record serialised to JSON. """ record.message = record.getMessage() message = OrderedDict( [ ("created", self.formatTime(record)), ("level", record.levelname), ("logger", record.name), ("path", record.pathname), ("lineno", record.lineno), ("message", record.message), ] ) if record.exc_info: message.update( [ ("func_name", getattr(record, "funcName", None)), ("process", record.process), ("process_name", getattr(record, "processName", None)), ("thread", record.thread), ("thread_name", record.threadName), ("exc_info", self.formatException(record.exc_info)), ] ) message.update(self.get_extra_fields(record)) return json.dumps(message)