|
| 1 | +import io |
1 | 2 | import logging
|
2 | 3 | import numbers
|
3 |
| -from typing import Tuple |
| 4 | +import traceback |
| 5 | +from contextlib import closing |
| 6 | +from types import TracebackType |
| 7 | +from typing import Tuple, Type, cast |
| 8 | + |
| 9 | +ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType] |
4 | 10 |
|
5 | 11 | # Reserved log record attributes cannot be overwritten. They
|
6 | 12 | # will not included in the formatted log.
|
@@ -67,6 +73,31 @@ def format_value(cls, value) -> str:
|
67 | 73 |
|
68 | 74 | return cls.format_string(str(value))
|
69 | 75 |
|
| 76 | + @classmethod |
| 77 | + def format_exc_info(cls, exc_info: ExcInfo) -> str: |
| 78 | + """ |
| 79 | + Format the provided exc_info into a logfmt formatted string. |
| 80 | +
|
| 81 | + This function should only be used to format exceptions which are |
| 82 | + currently being handled. Not with those exceptions which are |
| 83 | + manually passed into the logger. For example: |
| 84 | +
|
| 85 | + try: |
| 86 | + raise Exception() |
| 87 | + except Exception: |
| 88 | + logging.exception() |
| 89 | + """ |
| 90 | + _type, exc, tb = exc_info |
| 91 | + |
| 92 | + with closing(io.StringIO()) as sio: |
| 93 | + traceback.print_exception(_type, exc, tb, None, sio) |
| 94 | + value = sio.getvalue() |
| 95 | + |
| 96 | + # Tracebacks have a single trailing newline that we don't need. |
| 97 | + value = value.rstrip("\n") |
| 98 | + |
| 99 | + return cls.format_string(value) |
| 100 | + |
70 | 101 | @classmethod
|
71 | 102 | def format_params(cls, params: dict) -> str:
|
72 | 103 | """
|
@@ -95,9 +126,12 @@ def format(self, record: logging.LogRecord) -> str:
|
95 | 126 | extra = self.get_extra(record)
|
96 | 127 | params = {"msg": record.getMessage(), **extra}
|
97 | 128 |
|
98 |
| - return " ".join( |
99 |
| - ( |
100 |
| - "at={}".format(record.levelname), |
101 |
| - self.format_params(params), |
102 |
| - ) |
103 |
| - ) |
| 129 | + tokens = ["at={}".format(record.levelname), self.format_params(params)] |
| 130 | + |
| 131 | + if record.exc_info: |
| 132 | + # Cast exc_info to its not null variant to make mypy happy. |
| 133 | + exc_info = cast(ExcInfo, record.exc_info) |
| 134 | + |
| 135 | + tokens.append("exc_info={}".format(self.format_exc_info(exc_info))) |
| 136 | + |
| 137 | + return " ".join(tokens) |
0 commit comments