diff --git a/haystack/logging.py b/haystack/logging.py index d32af45d79..4d320e410d 100644 --- a/haystack/logging.py +++ b/haystack/logging.py @@ -116,7 +116,7 @@ def exception( msg: str, *, _: Any = None, - exc_info: Any = None, + exc_info: Any = True, stack_info: Any = False, stacklevel: int = 1, **kwargs: Any, @@ -152,12 +152,23 @@ def setLevel(self, level: int) -> None: """Set the logging level.""" -def patch_log_method_to_kwargs_only(func: typing.Callable) -> typing.Callable: - """A decorator to make sure that a function is only called with keyword arguments.""" +def patch_log_method_to_kwargs_only(func: typing.Callable, *, default_exc_info: Any = None) -> typing.Callable: + """ + A decorator to make sure that a function is only called with keyword arguments. + + :param func: The log method to wrap. + :param default_exc_info: The `exc_info` to pass when the caller omits it. Use `True` for `exception`. + """ @functools.wraps(func) def _log_only_with_kwargs( - msg: str, *, _: Any = None, exc_info: Any = None, stack_info: Any = False, stacklevel: int = 1, **kwargs: Any + msg: str, + *, + _: Any = None, + exc_info: Any = default_exc_info, + stack_info: Any = False, + stacklevel: int = 1, + **kwargs: Any, ) -> typing.Callable: # we need the `_` to avoid a syntax error existing_extra = kwargs.pop("extra", {}) return func( @@ -273,7 +284,7 @@ def getLogger(name: str) -> PatchedLogger: logger.warning = patch_log_method_to_kwargs_only(logger.warning) # type: ignore logger.error = patch_log_method_to_kwargs_only(logger.error) # type: ignore logger.critical = patch_log_method_to_kwargs_only(logger.critical) # type: ignore - logger.exception = patch_log_method_to_kwargs_only(logger.exception) # type: ignore + logger.exception = patch_log_method_to_kwargs_only(logger.exception, default_exc_info=True) # type: ignore logger.fatal = patch_log_method_to_kwargs_only(logger.fatal) # type: ignore logger.log = patch_log_with_level_method_to_kwargs_only(logger.log) # type: ignore diff --git a/releasenotes/notes/fix-logger-exception-traceback-3a4aaf6016812011.yaml b/releasenotes/notes/fix-logger-exception-traceback-3a4aaf6016812011.yaml new file mode 100644 index 0000000000..198690fbb1 --- /dev/null +++ b/releasenotes/notes/fix-logger-exception-traceback-3a4aaf6016812011.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + ``logger.exception()`` on loggers from ``haystack.logging.getLogger`` now logs the active traceback, as the + standard library does. Previously it logged only the message, without the stack trace, in both JSON and console + output. Passing ``exc_info`` explicitly still works. diff --git a/test/test_logging.py b/test/test_logging.py index 15322a5b99..dbdea61ab0 100644 --- a/test/test_logging.py +++ b/test/test_logging.py @@ -557,6 +557,28 @@ def test_various_levels(self, capfd: CaptureFixture, method: str, expected_level "module": "haystack.test_logging", } + def test_exception_logs_traceback(self, caplog: LogCaptureFixture) -> None: + logger = haystack_logging.getLogger("haystack.test_logging") + + try: + 1 / 0 + except ZeroDivisionError: + logger.exception("An error happened") + + record = caplog.records[0] + assert record.exc_info is not None + assert record.exc_info[0] is ZeroDivisionError + + def test_exception_respects_explicit_exc_info(self, caplog: LogCaptureFixture) -> None: + logger = haystack_logging.getLogger("haystack.test_logging") + + try: + 1 / 0 + except ZeroDivisionError: + logger.exception("An error happened", exc_info=False) + + assert not caplog.records[0].exc_info + def test_log(self, capfd: CaptureFixture) -> None: haystack_logging.configure_logging(use_json=True) @@ -641,7 +663,7 @@ def test_haystack_logger_with_positional_args(self, method: str, expected_level: logger = haystack_logging.getLogger("haystack.test_logging") logger.setLevel(logging.DEBUG) - with pytest.raises(TypeError): + with pytest.raises(TypeError, match=rf"Logger\.{method}\(\) takes 1 positional argument"): getattr(logger, method)("Hello, structured logging %s!", "logging") @pytest.mark.parametrize(