Commit 80791d859f38c9e5402bb652e2cc3a1178f62765
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").
Showing
3 changed files
with
31 additions
and
5 deletions
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 |