Commit 9dc1dff336f6d5812fc9cdc78e37a355fc5d1b4d

Authored by Christian Herdtweck
1 parent f55f8966

logger: introduce 'type' field for json output

This is a start to bring back some structure into the json output.

Might not be a permanent solution, need to further discuss in PR 308
oletools/common/log_helper/_json_formatter.py
... ... @@ -13,8 +13,13 @@ class JsonFormatter(logging.Formatter):
13 13 Since we don't buffer messages, we always prepend messages with a comma to make
14 14 the output JSON-compatible. The only exception is when printing the first line,
15 15 so we need to keep track of it.
  16 +
  17 + We assume that all input comes from the OletoolsLoggerAdapter which
  18 + ensures that there is a `type` field in the record. Otherwise will have
  19 + to add a try-except around the access to `record.type`.
16 20 """
17 21 json_dict = dict(msg=record.msg, level=record.levelname)
  22 + json_dict['type'] = record.type
18 23 formatted_message = ' ' + json.dumps(json_dict)
19 24  
20 25 if self._is_first_line:
... ...
oletools/common/log_helper/_logger_adapter.py
... ... @@ -8,18 +8,45 @@ class OletoolsLoggerAdapter(logging.LoggerAdapter):
8 8 """
9 9 _json_enabled = None
10 10  
11   - def print_str(self, message):
  11 + def print_str(self, message, **kwargs):
12 12 """
13 13 This function replaces normal print() calls so we can format them as JSON
14 14 when needed or just print them right away otherwise.
15 15 """
16 16 if self._json_enabled and self._json_enabled():
17 17 # Messages from this function should always be printed,
18   - # so when using JSON we log using the same level that set
19   - self.log(_root_logger_wrapper.level(), message)
  18 + # so when using JSON we log using the same level that set.
  19 + # Additional information in kwargs is added to LogRecord
  20 + self.log(_root_logger_wrapper.level(), message, extra=kwargs)
20 21 else:
21 22 print(message)
22 23  
  24 + def log(self, lvl, msg, *args, **kwargs):
  25 + """
  26 + Run :py:meth:`process` on kwargs, then forward to actual logger.
  27 +
  28 + This is based on the logging cookbox, section "Using LoggerAdapter to
  29 + impart contextual information".
  30 + """
  31 + msg, kwargs = self.process(msg, kwargs)
  32 + self.logger.log(lvl, msg, *args, **kwargs)
  33 +
  34 + def process(self, msg, kwargs):
  35 + """
  36 + Ensure `kwargs['extra']['type']` exists, init with given arg `type`.
  37 +
  38 + The `type` field will be added to the :py:class:`logging.LogRecord` and
  39 + is used by the :py:class:`JsonFormatter`.
  40 + """
  41 + if 'extra' not in kwargs:
  42 + kwargs['extra'] = {}
  43 + if 'type' in kwargs:
  44 + kwargs['extra']['type'] = kwargs['type']
  45 + del kwargs['type'] # downstream loggers cannot deal with this
  46 + if 'type' not in kwargs['extra']:
  47 + kwargs['extra']['type'] = 'msg' # type will be added to LogRecord
  48 + return msg, kwargs
  49 +
23 50 def set_json_enabled_function(self, json_enabled):
24 51 """
25 52 Set a function to be called to check whether JSON output is enabled.
... ...