Skip to content

AttributeError raised when calling logger.exception without exception information #506

@am1ter

Description

@am1ter

Hello everyone!

Before I describe the issue, let me provide some context.

Main issue:

I encountered an error when attempting to call logger.exception outside an except block without including any information about an exception. For example:

from structlog import getLogger

logger = getLogger()
logger.exception('test')

This code raises the following error:

Traceback (most recent call last):
  File "/home/amiter/coding/structlog/amiter.py", line 3, in <module>
    bl.exception('test_exception')
  File "/home/amiter/coding/structlog/src/structlog/_log_levels.py", line 94, in exception
    return self.error(event, **kw)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/amiter/coding/structlog/src/structlog/_log_levels.py", line 169, in meth
    return self._proxy_to_logger(name, event, **kw)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/amiter/coding/structlog/src/structlog/_base.py", line 204, in _proxy_to_logger
    args, kw = self._process_event(method_name, event, event_kw)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/amiter/coding/structlog/src/structlog/_base.py", line 159, in _process_event
    event_dict = proc(self._logger, method_name, event_dict)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/amiter/coding/structlog/src/structlog/dev.py", line 426, in __call__
    self._exception_formatter(sio, exc_info)
  File "/home/amiter/coding/structlog/src/structlog/dev.py", line 191, in rich_traceback
    Traceback.from_exception(*exc_info, show_locals=True)
  File "/home/amiter/coding/structlog/.venv/lib/python3.11/site-packages/rich/traceback.py", line 335, in from_exception
    rich_traceback = cls.extract(
                     ^^^^^^^^^^^^
  File "/home/amiter/coding/structlog/.venv/lib/python3.11/site-packages/rich/traceback.py", line 406, in extract
    exc_type=safe_str(exc_type.__name__),
                      ^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute '__name__'. Did you mean: '__ne__'?

I find this error message confusing because it seems like an internal bug.

It is clear that moving the logger call inside the try/except block resolves the issue. Additionally, including information about the exception using constructions like logger.exception('test', exc_info=False) or logger.exception('test', exc_info=ZeroDivisionError()) also prevents the error.

The cause of the AttributeError is that the code blocks in stdlib.BoundLogger.exception() and _log_levels.exception() set exc_info to True using kw.setdefault("exc_info", True), even when there is no exc_info in sys.exc_info(). Subsequently, the processors._figure_out_exc_info() function runs the following code:

def _figure_out_exc_info(v: Any) -> ExcInfo:
    """
    Depending on the Python version will try to do the smartest thing possible
    to transform *v* into an ``exc_info`` tuple.
    """
    if isinstance(v, BaseException):
        return (v.__class__, v, v.__traceback__)

    if isinstance(v, tuple):
        return v  # type: ignore[return-value]

    if v:
        return sys.exc_info()  # type: ignore[return-value]

    return v

Because sys.exc_info() is blank we get tuple with value (None, None, None) and then the following code raises AttributeError, that I showed above.

Tests-related issue:

Furthermore, I noticed that some tests in the structlog library ignore this unexpected behavior. For instance, the tests TestBoundLogger.test_exception_exc_info() and TestFilteringLogger.test_exception() use BoundLogger(ReturnLogger(), [], {}) and do not check this case.

Solution

To address this issue, I would like to create a pull request to fix the problem. However, I would appreciate your advice on the following questions:

  1. The standard library logging module allows calling logger.exception() without any additional arguments, and it does not raise any exceptions. Should we follow the same behavior in structlog, or maybe should we raise an exception when sys.exc_info() is empty?
  2. Should I add new tests to ensure that logger.exception("test") works correctly even when there is no additional information about the exception, or should I modify existing tests to cover this case?

Thank you for your attention, and I look forward to your feedback.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions