Jak proměnit titulek Sentry issue z ExceptionGroup: Download failed na DownloadError -> [HTTPDownloadError -> RuntimeError, HTTPStatusError].
Problém
Sentry zobrazuje issue tak, že titulkem je exception_type a podtitulkem exception_value. U ExceptionGroup to vede k identickým titulkům pro zcela odlišné root causes — titulek je vždy jen ExceptionGroup a podtitulek generický message:
ExceptionGroup: Download failed
ExceptionGroup: Download failed
ExceptionGroup: Download failed
ExceptionGroup: Download failed
Čtyři různé Sentry issues, každý s jiným problémem (DNS error, HTTP 403, HTTP 404, nevalidní URL), ale na první pohled nerozlišitelné. Stejný problém nastává u výjimek balených do generického obalu. To je běžný pattern — obal nese metainformaci (retry count, endpoint URL, batch ID), ale Sentry zobrazí jen typ obalu:
DownloadError: Download failed
DownloadError: Download failed
DownloadError: Download failed
Příčiny jsou přitom pokaždé jiné — ConnectionTimeout, SSLCertVerificationError, HTTPStatusError(403) — ale v seznamu issues vidíme jen generický DownloadError. Musíme rozkliknout každý event, abychom zjistili, co se skutečně stalo.
Nebo třeba:
TaskError: Task processing failed
TaskError: Task processing failed
Jeden je JSONDecodeError, druhý PermissionError — ale titulek je identický.
Jádro problému: Jak ExceptionGroup, tak výjimky s chain (__cause__/__context__) nesou skutečnou informaci ve vnořených výjimkách, ne v top-level message. Sentry tuhle strukturu neumí zobrazit v titulku.
Řešení: SDK tag + server-side fingerprint rule
Kombinace dvou mechanismů:
- SDK (Python,
before_sendhook) — má přístup k živému exception objektu, umí rekurzivně projítExceptionGroup.exceptionsa__cause__/__context__chainy. Výsledek zapíše jako tag. - Sentry server (fingerprint rule) — umí přepsat titulek, ale potřebuje hotová data. Tag z SDK je v eventu k dispozici.
Proč to funguje
before_sendse volá před odesláním eventu na server — tag je součástí payloadu- Server-side fingerprint rules se aplikují po přijetí eventu — vidí tagy z SDK
title=v fingerprint rule je jediný dokumentovaný způsob, jak přepsat titulek issue
Proč tag a ne jiný mechanismus
- Tag je standardní součást Event payloadu — žádné hackování
TypedDictu - Je viditelný v Sentry UI (filtrování, vyhledávání)
- Fingerprint rule
tags.*matcher je dokumentovaný a podporovaný - Čistá separace: backend dodá data, server je zobrazí
Implementace
Mixin pro výjimky
class SentryTitleMixin:
"""Mixin for exceptions with informative Sentry titles."""
@staticmethod
def _format_sentry_title(exc: BaseException) -> str:
name = type(exc).__name__
if isinstance(exc, BaseExceptionGroup):
inner = ", ".join(
SentryTitleMixin._format_sentry_title(e) for e in exc.exceptions
)
return f"{name} -> [{inner}]"
cause = exc.__cause__ or exc.__context__
if cause:
return f"{name} -> {SentryTitleMixin._format_sentry_title(cause)}"
return name
def sentry_title(self) -> str:
"""Return a human-readable title describing the exception chain."""
assert isinstance(self, BaseException)
return self._format_sentry_title(self)
_format_sentry_title() je @staticmethod, protože rekurzivně prochází vnořené výjimky, které samy SentryTitleMixin nemají — potřebuje přijmout libovolný BaseException, ne jen self.
Rekurzivní _format_sentry_title() sestaví hierarchický popis:
- ExceptionGroup →
Name -> [Sub1, Sub2] - Chained exception (
__cause__/__context__) →Name -> CauseName - Kombinace →
DownloadError -> [HTTPDownloadError -> RuntimeError, HTTPStatusError]
Výjimka s mixinem
class DownloadError(ExceptionGroup, SentryTitleMixin):
"""Download failed in all attempts."""
Žádný __new__, žádný derive() — mixin je opt-in a přidává se jen na výjimky, kde to dává smysl. Zasahuje pouze do Sentry zobrazení, žádné změny v chování výjimek.
before_send hook
def before_send(event, hint):
# ... existing filters ...
# SentryTitleMixin — set tag for server-side fingerprint rules.
# Requires a fingerprint rule in Sentry (Project Settings → Issue Grouping):
# tags.sentry_title:* -> {{ default }} title="{{ tags.sentry_title }}"
exc_info = hint.get("exc_info") if hint else None
if exc_info and len(exc_info) >= 2:
exc = exc_info[1]
if isinstance(exc, SentryTitleMixin):
event.setdefault("tags", {})["sentry_title"] = exc.sentry_title()
return event
Fingerprint rule v Sentry UI
Project Settings → Issue Grouping → Fingerprint Rules:
tags.sentry_title:* -> {{ default }} title="{{ tags.sentry_title }}"
{{ default }} zachovává výchozí grouping — pravidlo mění pouze zobrazený titulek.
Výsledek
Místo:
ExceptionGroup: Download failed
Sentry zobrazí:
DownloadError -> [HTTPDownloadError -> RuntimeError, HTTPStatusError]
Obecnější pozorování
- Sentry SDK a server mají asymetrické schopnosti: SDK vidí runtime objekty, server vidí jen serializovaný payload. Ale titulek umí přepsat jen server (přes fingerprint rules).
ExceptionGroup(Python 3.11+) je v Sentry stále občan druhé kategorie — titulek zobrazuje jen top-level message, ne strukturu sub-výjimek.- Fingerprint rules jsou primárně navržené pro grouping, ale
title=atribut z nich dělá jediný způsob, jak ovlivnit zobrazení issue v seznamu. - Pattern "SDK připraví data jako tag, server je spotřebuje v pravidle" je obecně použitelný i pro jiné scénáře, kde potřebujete runtime informaci v server-side konfiguraci.