Commit 80791d859f38c9e5402bb652e2cc3a1178f62765

Authored by Christian Herdtweck
1 parent 6bc04064

log_helper: Capture warnings into logging

We set logging.captureWarnings to True and use our logging framework for
python's builtin warnings logger. Warnings from that logger will have
"type=warning" per default (as opposed to "type=msg").
oletools/common/log_helper/_json_formatter.py
... ... @@ -18,12 +18,22 @@ class JsonFormatter(logging.Formatter):
18 18 the output JSON-compatible. The only exception is when printing the first line,
19 19 so we need to keep track of it.
20 20  
21   - We assume that all input comes from the OletoolsLoggerAdapter which
22   - ensures that there is a `type` field in the record. Otherwise will have
23   - to add a try-except around the access to `record.type`.
  21 + The resulting text is just a json dump of the :py:class:`logging.LogRecord`
  22 + object that is received as input, so no %-formatting or similar is done. Raw
  23 + unformatted message and formatting arguments are contained in fields `msg` and
  24 + `args` of the output.
  25 +
  26 + Arg `record` has a `type` field when created by `OletoolLoggerAdapter`. If not
  27 + (e.g. captured warnings or output from third-party libraries), we add one.
24 28 """
25 29 json_dict = dict(msg=record.msg.replace('\n', ' '), level=record.levelname)
26   - json_dict['type'] = record.type
  30 + try:
  31 + json_dict['type'] = record.type
  32 + except AttributeError:
  33 + if record.name == 'py.warnings': # this is the name of the logger
  34 + json_dict['type'] = 'warning'
  35 + else:
  36 + json_dict['type'] = 'msg'
27 37 formatted_message = ' ' + json.dumps(json_dict)
28 38  
29 39 if self._is_first_line:
... ...
oletools/common/log_helper/_logger_adapter.py
... ... @@ -7,6 +7,7 @@ class OletoolsLoggerAdapter(logging.LoggerAdapter):
7 7 Adapter class for all loggers returned by the logging module.
8 8 """
9 9 _json_enabled = None
  10 + _is_warn_logger = False # this is always False
10 11  
11 12 def print_str(self, message, **kwargs):
12 13 """
... ... @@ -44,7 +45,10 @@ class OletoolsLoggerAdapter(logging.LoggerAdapter):
44 45 kwargs['extra']['type'] = kwargs['type']
45 46 del kwargs['type'] # downstream loggers cannot deal with this
46 47 if 'type' not in kwargs['extra']:
47   - kwargs['extra']['type'] = 'msg' # type will be added to LogRecord
  48 + if self._is_warn_logger:
  49 + kwargs['extra']['type'] = 'warning' # this will add field
  50 + else:
  51 + kwargs['extra']['type'] = 'msg' # 'type' to LogRecord
48 52 return msg, kwargs
49 53  
50 54 def set_json_enabled_function(self, json_enabled):
... ... @@ -53,6 +57,12 @@ class OletoolsLoggerAdapter(logging.LoggerAdapter):
53 57 """
54 58 self._json_enabled = json_enabled
55 59  
  60 + def set_warnings_logger(self):
  61 + """Make this the logger for warnings"""
  62 + # create a object attribute that shadows the class attribute which is
  63 + # always False
  64 + self._is_warn_logger = True
  65 +
56 66 def level(self):
57 67 """Return current level of logger."""
58 68 return self.logger.level
... ...
oletools/common/log_helper/log_helper.py
... ... @@ -152,6 +152,11 @@ class LogHelper:
152 152 self._use_json = use_json
153 153 sys.excepthook = self._get_except_hook(sys.excepthook)
154 154  
  155 + # make sure warnings do not mess up our output
  156 + logging.captureWarnings(True)
  157 + warn_logger = self.get_or_create_silent_logger('py.warnings')
  158 + warn_logger.set_warnings_logger()
  159 +
155 160 # since there could be loggers already created we go through all of them
156 161 # and set their levels to 0 so they will use the root logger's level
157 162 for name in self._all_names:
... ... @@ -174,6 +179,7 @@ class LogHelper:
174 179  
175 180 # end logging
176 181 self._all_names = set()
  182 + logging.captureWarnings(False)
177 183 logging.shutdown()
178 184  
179 185 # end json list
... ...