feat(backend): integrate sentry-spring-boot-starter for exception reporting to GlitchTip #580
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
GlitchTip speaks the Sentry SDK wire protocol. Adding
sentry-spring-boot-starter-jakartato the Spring Boot backend automatically captures unhandled exceptions thrown from@RestControllermethods and sends them to GlitchTip with full stack traces and request context.DomainException4xx errors (not-found, forbidden, conflict) are user-generated conditions — they must NOT be sent to GlitchTip, as they would flood the error tracker with noise. Only unexpected 5xx errors should be captured.When
SENTRY_DSNis empty (default in.env.example), Sentry is fully disabled — the application starts cleanly and logsSentry is disabled.Depends on: GlitchTip infrastructure issue — a project DSN must exist before this can be tested end-to-end
backend/pom.xml— add dependencybackend/src/main/resources/application.ymlignored-exceptions-for-typeprevents allDomainExceptioninstances (404, 403, 409) from being reported. Only unhandled exceptions that bubble up as 5xx responses will be captured.docker-compose.yml— add env var to backend service.env.example— already added by scaffold issue; confirm this line exists:Acceptance Criteria
./mvnw clean packagesucceeds./mvnw testpasses — no test regressionsSENTRY_DSNis empty, application startup log containsSentry is disabledand no errorsSENTRY_DSNis set to the GlitchTip Django project DSN:RuntimeExceptionfrom a test endpoint causes an event in GlitchTip within 30 secondsDomainException.notFound(...)does not produce a GlitchTip event (verify by checking GlitchTip Issues list remains unchanged)DomainExceptiondo notDefinition of Done
pom.xmlandapplication.ymlcommitteddocker-compose.ymlenv var addedmain👨💻 Felix Brandt — Senior Fullstack Developer
Observations
pom.xml+application.yaml+docker-compose.yml. No new Java classes needed. This is the right approach — Sentry's Spring Boot auto-configuration handles everything.ignored-exceptions-for-typewithDomainExceptionis correct in principle, but there is a subtle gap:DomainException.internal(...)produces a 5xx response and is also aDomainExceptioninstance. The issue specifies "only 5xx should be captured", butDomainException.internal(...)is used in 8+ places in the codebase (OCR unavailable, file upload failures, invite code generation). These 5xxDomainExceptioninstances will be silently swallowed by the ignored-exceptions rule. This contradicts the stated goal.OcrService.javathrowsDomainException.internal(ErrorCode.OCR_SERVICE_UNAVAILABLE, ...)— an operational failure worth tracking in GlitchTip. The current config will suppress it.GlobalExceptionHandleralready has ahandleGeneric(Exception ex)catch-all that callslog.error("Unhandled exception", ex). The Sentry SDK will intercept before this handler fires for uncaught exceptions, which is correct. ButDomainExceptioninstances caught byhandleDomain()are consumed by the handler and never re-thrown — Sentry will not see them regardless of the ignored list. The ignored-exceptions setting is only redundant assurance; the real mechanism is handler-level suppression.traces-sample-rate: 1.0combined withenable-tracing: trueenables full distributed tracing. For a family archive with modest traffic this is fine, but be aware GlitchTip's tracing support is partial (performance data may not render as expected).send-default-pii: falseis correctly set — good default for GDPR.Recommendations
DomainExceptiongap: Either (a) use a SentryBeforeSendhook in a@Configurationclass that re-allowsDomainExceptioninstances whosegetStatus().is5xxServerError()is true, or (b) accept the current behavior and document it explicitly in the issue body. Option (b) is simpler and may be acceptable if OCR failures are already visible in logs. Make the decision explicit.ignored-exceptions-for-typelist is clean as-is for the 4xx suppression goal. No change needed there.enable-tracing: true/traces-sample-rate: 1.0until GlitchTip's tracing features are confirmed to work on your self-hosted version. These settings add overhead without guaranteed value. Safe to add in a follow-up.DomainException.notFound(...)does not produce a GlitchTip event". This is verifiable and well-written. Add a parallel AC: "ThrowingDomainException.internal(...)from a test endpoint either does or explicitly does not produce a GlitchTip event (document the chosen behavior)."🏛️ Markus Keller — Senior Application Architect
Observations
sentry-spring-boot-starter-jakartaat version 8.5.0 is a substantial third-party dependency. It integrates via Spring Boot auto-configuration, which means it will attempt to instrument HTTP requests, transactions, and potentially bean creation at startup. Worth confirming it doesn't conflict with the existing Jetty setup (the project uses Jetty, not Tomcat —sentry-spring-boot-starter-jakartagenerally works with any servlet container, but it's worth a conscious check).SENTRY_DSN: ${SENTRY_DSN:-}pattern indocker-compose.ymluses the dash-default syntax (empty string default). The application'ssentry.dsn: ${SENTRY_DSN:}uses the colon-no-default syntax which also resolves to empty string. Both are correct and consistent.docs/architecture/c4/l1-context.puml) is technically warranted — it is a new external system the backend communicates with. However, since GlitchTip is self-hosted on the same VPS, it could reasonably appear inl2-containers.pumlinstead as an internal service. The infrastructure issue that provisions GlitchTip should own the container-level diagram update; this issue owns at most the "backend → GlitchTip" arrow.Recommendations
sentry-spring-boot-starter-jakartaduring the first./mvnw clean packagerun. If there are classpath conflicts, the fallback issentry-spring-jakarta(manual setup, no starter).enable-tracing: trueis kept, document in the issue whether GlitchTip's hosted version supports Sentry performance data — partial tracing support is a known limitation of GlitchTip vs. full Sentry SaaS.application.yamlconfig block fits cleanly in the base config (not dev-profile-only) becauseSENTRY_DSNdefaults to empty. This is correct — Sentry is disabled when the var is absent, so there's no "dev only" concern.🔐 Nora "NullX" Steiner — Application Security Engineer
Observations
send-default-pii: falseis correctly set. This prevents Sentry from capturing usernames, IP addresses, cookies, and HTTP headers in error events. For a family archive containing personal documents, this setting is non-negotiable. Good call to include it explicitly.DomainException. TheDomainExceptiondeveloper messages in this codebase include things like"Not found: " + id(UUIDs) and"Failed to upload file: " + e.getMessage()(which may contain file paths or storage endpoint URLs). SinceDomainExceptioninstances are suppressed viaignored-exceptions-for-type, these won't reach GlitchTip — but thehandleGenericcatch-all for unexpected exceptions could include stack frames containing user-controlled data (e.g., filenames in S3 errors). This is a smell to watch, not a definite vulnerability..env(gitignored) and injecting via env var is the correct pattern — it's already how the issue specifies it. Confirm.envis in.gitignore.send-default-pii: false, Sentry won't add HTTP request bodies, but exception messages are always included. Auditthrow DomainException.internal(...)call sites to confirm developer messages do not embed user-controlled strings verbatim.Recommendations
DomainException.internal(...)call sites and the OCR/file error catch blocks where rawe.getMessage()is embedded in the exception message (e.g.,DocumentController.java:152,DocumentService.java:619). Thosee.getMessage()strings could contain S3 endpoint URLs, bucket names, or file paths. Since these exceptions are caught byhandleGenericand would reach Sentry, consider stripping the raw cause message:"Failed to upload file — see logs for details"rather than"Failed to upload file: " + e.getMessage(). This is a minor hardening step, not a blocking issue..envis in.gitignore(it should be, given the existing pattern with other secrets). TheSENTRY_DSN=line in.env.exampleis correct — an empty default exposes nothing.send-default-pii: false, but if GlitchTip is self-hosted in the EU (Hetzner), data residency is fine regardless.🧪 Sara Holt — QA Engineer & Test Strategist
Observations
SENTRY_DSNset to a real value) are manual verification only — they cannot be automated in unit or integration tests by design. This is acceptable for infrastructure wiring, but it should be acknowledged explicitly in the PR description so future contributors know these ACs aren't covered by the automated suite.Sentry is disabled,./mvnw testpasses) are automatically verifiable in CI. They are already implicitly covered by the existing test suite running with an empty DSN.traces-sample-rate: 1.0,enable-tracing: true). If these settings cause performance overhead or unexpected behavior in tests, it should be caught. A smoke test confirming the/actuator/healthendpoint still responds within an acceptable time after the change would provide some coverage.DomainExceptioncase (e.g.,DomainException.internal(...)). As Felix noted, these are also suppressed. If the intended behavior is that 5xxDomainExceptioninstances should also be suppressed (because they are "handled"), an explicit AC should say so. If they should be captured, a separate AC is needed.Recommendations
SENTRY_DSNis empty,./mvnw testproduces no Sentry-related errors or warnings." This is trivially automatable and gives CI confidence.SENTRY_DSN=emptystartup behavior." This sets expectations for reviewers.🚀 Tobias Wendt — DevOps & Platform Engineer
Observations
docker-compose.ymlchange (SENTRY_DSN: ${SENTRY_DSN:-}) is correct and follows the existing env-var injection pattern used by all other secrets in the Compose file. The dash-default (:-) means the container starts cleanly with an empty string when.envdoesn't defineSENTRY_DSN. No risk here..env.exampleline (SENTRY_DSN=) needs to actually be added — the issue body notes "confirm this line exists" but it was not found in the current.env.example. This is a required deliverable, not optional.sentry-spring-boot-starter-jakartaat version8.5.0with a comment to "check for latest stable 8.x". This is the right pattern — pin to a specific version and let Renovate bump it. The comment is informational, not a process blocker.https://glitchtip.your-domain.com/api/<project>/store/). This is a new outbound dependency for the backend container. Confirm the GlitchTip ingestion endpoint is accessible from the Docker internal network (or from the backend container's perspective). If Caddy proxies GlitchTip on the same VPS, the backend container will need to reach it via the internal Docker network or via the public hostname.application.yamlsentry.environment: ${SPRING_PROFILES_ACTIVE:dev}is clever — it reuses the existing profiles env var to tag Sentry events. This means events from the dev Docker Compose stack will be taggeddev,e2e(the currentSPRING_PROFILES_ACTIVEvalue indocker-compose.yml). That's useful for filtering in GlitchTip.Recommendations
SENTRY_DSN=line to.env.example— it's not there yet. This is a required deliverable per the issue's own AC.archive-backendcontainer. If GlitchTip runs on the same Compose stack or same VPS, test withdocker exec archive-backend curl -s <glitchtip-ingest-url>during integration.docs/infrastructure/self-hosted-catalogue.md(referenced in Tobias's domain knowledge) should document the GlitchTip DSN pattern so future operators know where to get it and how to rotate it. This is a recommendation for the GlitchTip infrastructure issue, not this one.SENTRY_DSNabsent (empty), so theSentry is disabledpath is exercised automatically on every CI run.📋 Elicit — Requirements Engineer
Observations
DomainException.internal(...)— aDomainExceptionsubtype that results in HTTP 500. Theignored-exceptions-for-typerule suppresses allDomainExceptioninstances, including these 5xx ones. The requirement as stated and the implementation as specified are in conflict. This needs an explicit decision:DomainExceptionregardless of HTTP status" — simplest, but misses real OCR/file-upload failures that are operationally significant.DomainException; capture 5xx ones" — matches the stated intent, requires aBeforeSendhook or a separate exception hierarchy.traces-sample-rate: 1.0+enable-tracing: truesettings are included but not covered by any acceptance criterion. There is no AC for "tracing data appears in GlitchTip" and no AC for "tracing does not degrade performance." These settings add scope without corresponding verification.Recommendations
enable-tracing: trueandtraces-sample-rate: 1.0from this issue, track them in a follow-up issue once GlitchTip tracing support is confirmed.🗳️ Decision Queue — Action Required
2 decisions need your input before implementation starts.
Architecture / Requirements
DomainException.internal(...)(HTTP 500) — Theignored-exceptions-for-type: DomainExceptionconfig suppresses ALLDomainExceptioninstances, including the 8+ places in the codebase whereDomainException.internal(...)signals real operational failures (OCR unavailable, file upload failures, invite code generation). The stated goal is "only unexpected 5xx errors should be captured" — but these 5xxDomainExceptioninstances are "expected by the domain" yet still operationally significant. Choose one:DomainException— including.internal()— are suppressed. Add an explicit AC noting this, and rely on logs for OCR/file-upload failure visibility.BeforeSendconfiguration bean to re-allowDomainExceptioninstances wheregetStatus().is5xxServerError()is true.(Raised by: Felix, Elicit, Sara)
Scope
enable-tracing: trueandtraces-sample-rate: 1.0, but there are no acceptance criteria for tracing, and GlitchTip's self-hosted tracing support is partial compared to full Sentry SaaS. These settings add unverified scope. Choose one:(Raised by: Felix, Markus, Elicit)
Implementation complete — PR #592: #592
What was changed:
backend/pom.xml— addedsentry-spring-boot-starter-jakarta8.5.0backend/src/main/resources/application.yaml— addedsentry:block with DSN from env, profile-based environment name, 100% trace sampling, PII disabled, andDomainExceptionon the ignore listdocker-compose.yml— addedSENTRY_DSN: ${SENTRY_DSN:-}to the backend service (empty default keeps SDK disabled).env.example—SENTRY_DSN=was already present, no change needed./mvnw clean package -DskipTestspasses (BUILD SUCCESS).