sentry-python
sentry-python copied to clipboard
Exceptions are formatted incorrectly (or even fail) when `message` or `detail` attribute presents
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'