diff --git a/CHANGELOG.md b/CHANGELOG.md index c3bb16b..56fa6fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ (used for the log `message`) and `event.dataset` (a field provided by the `elasticapm` integration) ([#46](https://github.com/elastic/ecs-logging-python/pull/46)) * Add default/fallback handling for json.dumps ([#47](https://github.com/elastic/ecs-logging-python/pull/47)) +* Fixed an issue in `StdLibFormatter` when `exc_info=False` ([#42](https://github.com/elastic/ecs-logging-python/pull/42)) ## 1.0.0 (2021-02-08) diff --git a/ecs_logging/_stdlib.py b/ecs_logging/_stdlib.py index 2de6e6d..a89abee 100644 --- a/ecs_logging/_stdlib.py +++ b/ecs_logging/_stdlib.py @@ -16,6 +16,7 @@ # under the License. import logging +import sys import time from traceback import format_tb from ._meta import ECS_VERSION @@ -112,6 +113,34 @@ def __init__(self, stack_trace_limit=None, exclude_fields=()): self._exclude_fields = frozenset(exclude_fields) self._stack_trace_limit = stack_trace_limit + def _record_error_type(self, record): + # type: (logging.LogRecord) -> Optional[str] + exc_info = record.exc_info + if not exc_info: + # exc_info is either an iterable or bool. If it doesn't + # evaluate to True, then no error type is used. + return None + if isinstance(exc_info, bool): + # if it is a bool, then look at sys.exc_info + exc_info = sys.exc_info() + if isinstance(exc_info, (list, tuple)) and exc_info[0] is not None: + return exc_info[0].__name__ + return None + + def _record_error_message(self, record): + # type: (logging.LogRecord) -> Optional[str] + exc_info = record.exc_info + if not exc_info: + # exc_info is either an iterable or bool. If it doesn't + # evaluate to True, then no error message is used. + return None + if isinstance(exc_info, bool): + # if it is a bool, then look at sys.exc_info + exc_info = sys.exc_info() + if isinstance(exc_info, (list, tuple)) and exc_info[1]: + return str(exc_info[1]) + return None + def format(self, record): # type: (logging.LogRecord) -> str result = self.format_to_ecs(record) @@ -145,14 +174,8 @@ def format_to_ecs(self, record): "process.name": self._record_attribute("processName"), "process.thread.id": self._record_attribute("thread"), "process.thread.name": self._record_attribute("threadName"), - "error.type": lambda r: ( - r.exc_info[0].__name__ - if (r.exc_info is not None and r.exc_info[0] is not None) - else None - ), - "error.message": lambda r: ( - str(r.exc_info[1]) if r.exc_info and r.exc_info[1] else None - ), + "error.type": self._record_error_type, + "error.message": self._record_error_message, "error.stack_trace": self._record_error_stack_trace, } # type: Dict[str, Callable[[logging.LogRecord],Any]] diff --git a/tests/test_stdlib_formatter.py b/tests/test_stdlib_formatter.py index a6758af..f0a3d32 100644 --- a/tests/test_stdlib_formatter.py +++ b/tests/test_stdlib_formatter.py @@ -183,6 +183,21 @@ def test_stack_trace_limit_disabled(stack_trace_limit, logger): assert ecs["log"]["original"] == "there was an error" +def test_exc_info_false_does_not_raise(logger): + stream = StringIO() + handler = logging.StreamHandler(stream) + handler.setFormatter(ecs_logging.StdlibFormatter()) + logger.addHandler(handler) + logger.setLevel(logging.DEBUG) + + logger.info("there was %serror", "no ", exc_info=False) + + ecs = json.loads(stream.getvalue().rstrip()) + assert ecs["log.level"] == "info" + assert ecs["message"] == "there was no error" + assert "error" not in ecs + + def test_stack_trace_limit_traceback(logger): def f(): g()