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,12 +18,22 @@ class JsonFormatter(logging.Formatter):
18 the output JSON-compatible. The only exception is when printing the first line, 18 the output JSON-compatible. The only exception is when printing the first line,
19 so we need to keep track of it. 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 json_dict = dict(msg=record.msg.replace('\n', ' '), level=record.levelname) 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 formatted_message = ' ' + json.dumps(json_dict) 37 formatted_message = ' ' + json.dumps(json_dict)
28 38
29 if self._is_first_line: 39 if self._is_first_line:
oletools/common/log_helper/_logger_adapter.py
@@ -7,6 +7,7 @@ class OletoolsLoggerAdapter(logging.LoggerAdapter): @@ -7,6 +7,7 @@ class OletoolsLoggerAdapter(logging.LoggerAdapter):
7 Adapter class for all loggers returned by the logging module. 7 Adapter class for all loggers returned by the logging module.
8 """ 8 """
9 _json_enabled = None 9 _json_enabled = None
  10 + _is_warn_logger = False # this is always False
10 11
11 def print_str(self, message, **kwargs): 12 def print_str(self, message, **kwargs):
12 """ 13 """
@@ -44,7 +45,10 @@ class OletoolsLoggerAdapter(logging.LoggerAdapter): @@ -44,7 +45,10 @@ class OletoolsLoggerAdapter(logging.LoggerAdapter):
44 kwargs['extra']['type'] = kwargs['type'] 45 kwargs['extra']['type'] = kwargs['type']
45 del kwargs['type'] # downstream loggers cannot deal with this 46 del kwargs['type'] # downstream loggers cannot deal with this
46 if 'type' not in kwargs['extra']: 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 return msg, kwargs 52 return msg, kwargs
49 53
50 def set_json_enabled_function(self, json_enabled): 54 def set_json_enabled_function(self, json_enabled):
@@ -53,6 +57,12 @@ class OletoolsLoggerAdapter(logging.LoggerAdapter): @@ -53,6 +57,12 @@ class OletoolsLoggerAdapter(logging.LoggerAdapter):
53 """ 57 """
54 self._json_enabled = json_enabled 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 def level(self): 66 def level(self):
57 """Return current level of logger.""" 67 """Return current level of logger."""
58 return self.logger.level 68 return self.logger.level
oletools/common/log_helper/log_helper.py
@@ -152,6 +152,11 @@ class LogHelper: @@ -152,6 +152,11 @@ class LogHelper:
152 self._use_json = use_json 152 self._use_json = use_json
153 sys.excepthook = self._get_except_hook(sys.excepthook) 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 # since there could be loggers already created we go through all of them 160 # since there could be loggers already created we go through all of them
156 # and set their levels to 0 so they will use the root logger's level 161 # and set their levels to 0 so they will use the root logger's level
157 for name in self._all_names: 162 for name in self._all_names:
@@ -174,6 +179,7 @@ class LogHelper: @@ -174,6 +179,7 @@ class LogHelper:
174 179
175 # end logging 180 # end logging
176 self._all_names = set() 181 self._all_names = set()
  182 + logging.captureWarnings(False)
177 logging.shutdown() 183 logging.shutdown()
178 184
179 # end json list 185 # end json list