Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

to_repr() can leak exceptions with Rich #678

Closed
rmartin16 opened this issue Nov 19, 2024 · 4 comments
Closed

to_repr() can leak exceptions with Rich #678

rmartin16 opened this issue Nov 19, 2024 · 4 comments

Comments

@rmartin16
Copy link

Upgraded from v24.2.0 to v24.4.0 and exception logging started raising its own exceptions. Previously, the repr-error text would be used; therefore, rich.pretty.traverse() is hitting the same error as the previous logic and letting the exception leak. It isn't clear to me if Rich should be catching these or structlog (or if we just shouldn't have a few classes that cause this...) but I think it is unexpected for exception logging to start raising itself.

@rmartin16
Copy link
Author

rmartin16 commented Nov 19, 2024

Managed to boil down an example, fwiw:

# /// script
# dependencies = [
#     "structlog>=24.4.0",
#     "pydantic",
#     "rich",
# ]
# # run this file with "uv run --script script.py"
# ///

import structlog
from pydantic import BaseModel, computed_field

class Model(BaseModel):
    a: int

    @computed_field
    def a_otherwise(self) -> int:
        return self.a

def main():
    try:
        Model(a="")
    except Exception as e:
        structlog.get_logger().exception("error")

main()

@sscherfke
Copy link
Contributor

The exception happens in rich.traceback.Traceback.from_exception(). So I guess it's an issue in Rich?

@hynek
Copy link
Owner

hynek commented Jan 4, 2025

Yeah, here's the traceback:

Reading inline script metadata from `t.py`
Traceback (most recent call last):
  File "/Users/hynek/FOSS/structlog/t.py", line 23, in main
    Model(a="")
    ~~~~~^^^^^^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/pydantic/main.py", line 214, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for Model
a
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='', input_type=str]
    For further information visit https://errors.pydantic.dev/2.10/v/int_parsing

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/hynek/FOSS/structlog/t.py", line 27, in <module>
    main()
    ~~~~^^
  File "/Users/hynek/FOSS/structlog/t.py", line 25, in main
    structlog.get_logger().exception("error")
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/structlog/_native.py", line 45, in exception
    return self.error(event, *args, **kw)
           ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/structlog/_native.py", line 134, in meth
    return self._proxy_to_logger(name, event, **kw)
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/structlog/_base.py", line 214, in _proxy_to_logger
    args, kw = self._process_event(method_name, event, event_kw)
               ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/structlog/_base.py", line 165, in _process_event
    event_dict = proc(self._logger, method_name, event_dict)
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/structlog/dev.py", line 738, in __call__
    self._exception_formatter(sio, exc_info)
    ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/structlog/dev.py", line 382, in __call__
    Traceback.from_exception(
    ~~~~~~~~~~~~~~~~~~~~~~~~^
        *exc_info,
        ^^^^^^^^^^
    ...<11 lines>...
        suppress=self.suppress,
        ^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/rich/traceback.py", line 343, in from_exception
    rich_traceback = cls.extract(
        exc_type,
    ...<6 lines>...
        locals_hide_sunder=locals_hide_sunder,
    )
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/rich/traceback.py", line 489, in extract
    key: pretty.traverse(
         ~~~~~~~~~~~~~~~^
        value,
        ^^^^^^
        max_length=locals_max_length,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        max_string=locals_max_string,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/rich/pretty.py", line 874, in traverse
    node = _traverse(_object, root=True)
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/rich/pretty.py", line 667, in _traverse
    args = list(iter_rich_args(rich_repr_result))
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/rich/pretty.py", line 634, in iter_rich_args
    for arg in rich_args:
               ^^^^^^^^^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/pydantic/_internal/_repr.py", line 78, in __rich_repr__
    for name, field_repr in self.__repr_args__():
                            ~~~~~~~~~~~~~~~~~~^^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/pydantic/main.py", line 1118, in __repr_args__
    yield from ((k, getattr(self, k)) for k, v in self.__pydantic_computed_fields__.items() if v.repr)
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/pydantic/main.py", line 1118, in <genexpr>
    yield from ((k, getattr(self, k)) for k, v in self.__pydantic_computed_fields__.items() if v.repr)
                    ~~~~~~~^^^^^^^^^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/pydantic/main.py", line 889, in __getattr__
    return super().__getattribute__(item)  # Raises AttributeError if appropriate
           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/Users/hynek/FOSS/structlog/t.py", line 19, in a_otherwise
    return self.a
           ^^^^^^
  File "/Users/hynek/.cache/uv/archive-v0/J7LHAE7exIow9K-GX1NTG/lib/python3.13/site-packages/pydantic/main.py", line 892, in __getattr__
    raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')
AttributeError: 'Model' object has no attribute 'a'

It looks like rich is trying to repr the error and causes another error?


I don't have it in me to debug the combination of Rich and Pydantic but it might be somewhat related to #679 and should be reported to Rich.

@hynek hynek closed this as not planned Won't fix, can't repro, duplicate, stale Jan 4, 2025
@hynek
Copy link
Owner

hynek commented Jan 4, 2025

oh and also #655 as Stefan wrote

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants