sentry-python icon indicating copy to clipboard operation
sentry-python copied to clipboard

Exceptions are formatted incorrectly (or even fail) when `message` or `detail` attribute presents

Open ods opened this issue 2 months ago • 4 comments

How do you use Sentry?

Self-hosted/on-premise

Version

2.43.0

Steps to Reproduce

Some of our exception define properties message and detail. The formatting of error message for those exceptions is almost always incorrect (doesn't contain important information, not formatted properly) or sometimes even fails. There is a couple of such exceptions in simplified form:

class GRpcError(Exception):
    def __init__(self, code: int, message: bytes) -> None:
        super().__init__(code, message)
        self.code = code
        # Original protobuf message to allow its inspection in error handlers.
        # Here we use bytes, but it can be `google.protobuf.message.Message`
        # instance as well.
        self.message = message

    def __str__(self) -> str:
        return f"code={self.code}"


class ApiError(Exception):
    def __init__(self, code: str, detail: dict[str, Any]) -> None:
        super().__init__(code, detail)
        self.code = code
        self.detail = detail

    def __str__(self) -> str:
        formatted = f"[{self.code}]"
        for key, value in self.detail.items():
            formatted += f"\n  {key}={value}"
        return formatted

Expected Result

Here is how they are formatted by Python itself:

grpc_exc = GRpcError(13, b"unreadable protobuf message")

print("".join(traceback.format_exception_only(grpc_exc)).strip())
# GRpcError: code=13

grpc_exc.add_note("additional context note")

print("".join(traceback.format_exception_only(grpc_exc)).strip())
# GRpcError: code=13
# additional context note

api_exc = ApiError("RATE_LIMIT_EXCEEDED", detail={"retry_after": 30})

print("".join(traceback.format_exception_only(api_exc)).strip())
# ApiError: [RATE_LIMIT_EXCEEDED]
#   retry_after=30

api_exc.add_note("additional context note")

print("".join(traceback.format_exception_only(api_exc)).strip())
# ApiError: [RATE_LIMIT_EXCEEDED]
#   retry_after=30
# additional context note

Actual Result

And the formatting by sentry (get_error_message is used internally by event_from_exception, here I call it directly):

grpc_exc = GRpcError(13, b"unreadable protobuf message")

print(sentry_sdk.utils.get_error_message(grpc_exc))
# No `code` field, only unreadable field in the result:
# b'unreadable protobuf message'

grpc_exc.add_note("additional context note")

print(sentry_sdk.utils.get_error_message(grpc_exc))
# Fails with the error:
# TypeError: can't concat str to bytes

api_exc = ApiError("RATE_LIMIT_EXCEEDED", detail={"retry_after": 30})

print(sentry_sdk.utils.get_error_message(api_exc))
# No `code` field, `detail` is not formatted:
# {'retry_after': 30}

api_exc.add_note("additional context note")

print(sentry_sdk.utils.get_error_message(api_exc))
# Fails with the error:
# TypeError: unsupported operand type(s) for +=: 'dict' and 'str'

ods avatar Oct 31 '25 10:10 ods

PY-1957

linear[bot] avatar Oct 31 '25 10:10 linear[bot]

Looks like this special handling of message attribute is from Python 2 world, as BaseException.message was deprecated in Python 2.6 and removed in 2.7 and 3.0 (see PEP 352 for details). So the fix in #2193 (adds special handling of detail) to solve #2192, doesn't look correct to me. Instead, we should have removed special handling of message attribute. As you can see, the exception from example in #2192 is formatted correctly by standard tools, but misses status code when formatted by sentry:

fapi_exc = fastapi.HTTPException(429, detail="Too Many Requests")

print("".join(traceback.format_exception_only(fapi_exc)).strip())
# fastapi.exceptions.HTTPException: 429: Too Many Requests

print(sentry_sdk.utils.get_error_message(fapi_exc))
# `status_code` field is missing:
# Too Many Requests

ods avatar Oct 31 '25 10:10 ods

The special case for exception with a detail field seems to have been added because Starlette did not have a __str__ method on their exceptions until recently: https://github.com/Kludex/starlette/commit/2168e47052239da5df35d5353bb986f760c51cef.

https://github.com/getsentry/sentry-python/blob/5b055894042e0bc68a7945a5b8cb43b975b407fc/sentry_sdk/utils.py#L655-L661

alexander-alderman-webb avatar Nov 05 '25 09:11 alexander-alderman-webb

Hi @ods,

We'll fix the unhandled exception you have reported first, and remove special handling of the message and detail attributes in the next major version. Thanks again for the research!

alexander-alderman-webb avatar Nov 18 '25 12:11 alexander-alderman-webb