feat(sentry): error tracking, performance monitoring, and session replay#280
Open
Just-Insane wants to merge 9 commits intodevfrom
Open
feat(sentry): error tracking, performance monitoring, and session replay#280Just-Insane wants to merge 9 commits intodevfrom
Just-Insane wants to merge 9 commits intodevfrom
Conversation
Contributor
|
OpenTofu plan for production Plan: 2 to add, 0 to change, 2 to destroy.OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
OpenTofu will perform the following actions:
# cloudflare_worker_version.site must be replaced
-/+ resource "cloudflare_worker_version" "site" {
!~ annotations = {
+ workers_message = (known after apply)
+ workers_tag = (known after apply)
!~ workers_triggered_by = "create_version_api" -> (known after apply)
} -> (known after apply)
!~ assets = { # forces replacement
!~ asset_manifest_sha256 = "b44bbf6a507cd60278d078f529f5e4b4bfe36580139ecb1ff4fc842de520526f" -> "b09b2cd338d7f1837f401a4e7ecfadd5c5dc08b21c56faf91effbe4bfc7f2547" # forces replacement
!~ directory = "/home/runner/work/Sable/Sable/dist" -> "/github/workspace/dist"
# (1 unchanged attribute hidden)
}
+ compatibility_flags = (known after apply)
!~ created_on = "2026-03-14T23:24:54Z" -> (known after apply)
!~ id = "************************************" -> (known after apply)
+ limits = (known after apply)
+ main_script_base64 = (known after apply)
!~ number = 112 -> (known after apply)
!~ source = "terraform" -> (known after apply)
+ startup_time_ms = (known after apply)
# (4 unchanged attributes hidden)
}
# cloudflare_workers_deployment.site must be replaced
-/+ resource "cloudflare_workers_deployment" "site" {
!~ annotations = { # forces replacement
!~ workers_message = "b90555a27c7dac104a96bf0e2e5b66c2c815da2f" -> "feat(sentry): error tracking, performance monitoring, and session replay"
!~ workers_triggered_by = "deployment" -> (known after apply)
}
!~ author_email = "cloudflare@sl.sable.moe" -> (known after apply)
!~ created_on = "2026-03-14T23:24:55Z" -> (known after apply)
!~ id = "************************************" -> (known after apply)
!~ source = "terraform" -> (known after apply)
!~ versions = [ # forces replacement
!~ {
!~ version_id = "************************************" -> (known after apply)
# (1 unchanged attribute hidden)
},
]
# (3 unchanged attributes hidden)
}
Plan: 2 to add, 0 to change, 2 to destroy.
Warning: Attribute Deprecated
with cloudflare_workers_custom_domain.site,
on main.tf line 41, in resource "cloudflare_workers_custom_domain" "site":
41: environment = "production"
This attribute is deprecated.
(and one more similar warning elsewhere)📝 Plan generated in Cloudflare Infra #72 |
660f8a4 to
d9bffa6
Compare
…settings, and error capture Adds Sentry error tracking, performance monitoring, and session replay: - Environment-based sampling: production 10%, preview/dev 100% - Session replay with full privacy masking (text, media, inputs) - Hashed MXID as anonymous user ID with homeserver tag - Error rate limiting (50 events/session) to protect quota Performance spans and metrics across all critical paths: auth login, message send, timeline jump-load, per-event decryption, sliding sync cycles, key backup, store wipe, UTD failures, sync degradation, background notification clients, media uploads. Developer settings panel (Settings → General → Diagnostics & Privacy): error reporting toggle, session replay opt-in, breadcrumb category controls, debug log export. Crash page shows Sentry event ID with one-click feedback dialog. Bug report modal sends structured reports to Sentry feedback API with optional debug log attachment. CI: Sentry env vars injected into preview (100% sampling) and production (10% sampling) builds. Source maps uploaded via @sentry/vite-plugin.
- Tag all Sentry events with PR number via VITE_SENTRY_PR env var
(instrument.ts: setTag('pr', prNumber) on global scope)
- cloudflare-web-preview.yml: inject Sentry DSN, environment=preview,
PR number, and source-map secrets into build env for PR runs
- New workflow sentry-preview-issues.yml: on every PR push, query Sentry
for unresolved issues tagged with the PR number and environment=preview;
create a GitHub issue per unique error (deduplicated by sentry-id marker),
labelled 'sentry-preview' + 'pr-{N}'; maintain a sticky PR comment
with a summary table; reopen closed issues on regression
- Labels allow filtering: -label:sentry-preview hides all automated issues
…policy Before-send scrubbing in instrument.ts covers: - Matrix user IDs (@user:server → @[USER_ID]) - Room IDs (!room:server → ![ROOM_ID]) - Event IDs ($event → $[EVENT_ID]) - Percent-encoded sigils (%40, %21, %24, %23) - Hybrid-encoded forms (decoded sigil + %3A colon) - Tokens, credentials, and query params (access_token, next_batch, etc.) - All span data string values (not just http.url) - Profile URLs, key backup paths, media paths in span data - preview_url query params and exception values - 'none' sentinel for unset room tags Privacy policy added to docs/PRIVACY.md (linked from General settings). DiagnosticsAndPrivacy component added to General settings with error reporting and session replay toggles. TS2367 redundant phase guard removed from useCallSignaling.ts.
…h monitoring, and anomaly detection RoomTimeline.tsx: - atBottom transition breadcrumbs (ui.scroll) with rapid-flip detection: true→false within 200ms captures warning (IntersectionObserver false-positive) - Virtual paginator window shift breadcrumbs (ui.timeline) - scroll-to-bottom trigger breadcrumb; captures warning when fired while user is scrolled up without a pending reset - eventsLength batch monitoring: breadcrumb on every batch with delta, batchSize label (single/small/medium/large), rangeGap, atBottom - Large batch warning (>50 events, liveTimelineLinked=true) for sliding sync adaptive load detection slidingSync.ts / initMatrix.ts: - sync.sliding breadcrumb in onFirstRoomData with latency + event count - sable.sync.room_sub_event_count distribution metric Message interaction metrics (IncomingCallModal, MessageDelete, MessageForward, MessageReport, RoomTimeline reaction handler): - sable.call.answered/declined, sable.message.delete/forward/report/reaction.*
e17a427 to
793dfed
Compare
…ption batch The scroll queued at TimelineReset fires when range.end=0 (SDK fires Reset before populating the fresh timeline), so the DOM is empty and the scroll is a no-op. The stay-at-bottom effect then correctly expands the range but never re-queues a scroll, leaving the user at the wrong position. Fix: increment scrollToBottomRef.count in the stay-at-bottom effect before calling setTimeline. The next render sees the new count, the useLayoutEffect fires, and scroll-to-bottom runs against the now-populated DOM. Also adds monitoring: - Sentry breadcrumb (ui.scroll) recording each range expansion + scroll, with rangeGap, eventsLength, and wasReset fields for Replay analysis. - slidingSync onFirstRoomData now records the live-timeline event count at subscription activation time as a Sentry metric and breadcrumb, making the list-to-subscription page size visible in dashboards.
793dfed to
9d08518
Compare
6f770e1 to
a7ab52d
Compare
- Flip Sentry from opt-out to opt-in: enabled only when sable_sentry_enabled === 'true' - Add TelemetryConsentBanner: fixed bottom slide-up banner shown once on first login - 'Enable crash reports' reloads page so Sentry initialises immediately - 'No thanks' / dismiss X sets sable_sentry_enabled=false, no repeat prompts - Only shown when VITE_SENTRY_DSN is configured (self-hosters unaffected) - Update DiagnosticsAndPrivacy toggle: enable path now sets 'true' instead of removeItem - Update SentrySettings dev panel to match new opt-in read logic
a7ab52d to
31bee68
Compare
Add Sentry integration for error tracking and bug reporting
Contributor
Deploying with
|
| Status | Preview URL | Commit | Alias | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! | https://pr-280-sable.raspy-dream-bb1d.workers.dev | 2abb63f | pr-280 |
Mon, 16 Mar 2026 18:01:13 GMT |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds comprehensive Sentry error tracking, performance monitoring, and session replay to Sable.
What's in this PR
Core integration (G1)
ErrorBoundary; crash page shows the Sentry event ID with one-click feedback/bugreportmodal sends to Sentry Feedback API; debug log attachment optionalSettings → Developer Tools → Error Tracking (Sentry)— breadcrumb category toggles, live error/warning counters, debug log exportVITE_SENTRY_DSN+VITE_SENTRY_ENVIRONMENTpassed in preview (100% sampling) and production (10%) builds; source maps uploaded via@sentry/vite-pluginSentry→GitHub Issues triage (G2)
New
.github/workflows/sentry-preview-issues.yml: After every PR preview build, a GitHub Actions run queries Sentry for errors seen in that preview environment and posts a summary comment to the PR. Helps catch regressions before merge.Data scrubbing + settings UI + privacy policy (G3)
beforeSendscrubbing ininstrument.tsnow covers:@user:server→@[USER_ID]), room IDs, event IDs%40,%21,%24,%23) and hybrid-encoded forms (decoded sigil +%3Acolon)access_token,next_batch, etc.)http.url)preview_urlparams, exception values'none'sentinel for unset room tagsSettings → General → Diagnostics & Privacysection with error reporting and session replay toggles, plus a link todocs/PRIVACY.md.Timeline instrumentation + anomaly detection (G4)
RoomTimeline.tsxnow emits structured breadcrumbs for every significant scroll/layout event:ui.scroll: EveryatBottomtrue→false transition, with rapid-flip anomaly detection (true→false within 200 ms flags a likely IntersectionObserver false positive from a DOM layout shift)ui.scroll: Every scroll-to-bottom trigger; warns if fired while user is scrolled up without a pending reset (indicates a logic error)ui.timeline: Virtual paginator window shifts (start/end changes)timeline.events: EveryeventsLengthbatch update — delta, batch size label (single/small/medium/large), range gap,atBottom; warns on large batch whileliveTimelineLinked=true(sliding sync adaptive load)sync.sliding:onFirstRoomDatabreadcrumb inslidingSync.tswith latency + event count; emitssable.sync.room_sub_event_countdistribution metricMessage interaction counters:
sable.call.answered/declined,sable.message.delete/forward/report.*,sable.message.reaction.toggle.Bug fixes found via Sentry Replay (G5)
Scroll-to-bottom after list→subscription timeline expansion
When a room with a single cached event (list subscription,
timeline_limit=1) transitions to a full subscription,TimelineResetfires before any events land. The "stay at bottom" effect queued a scroll while the DOM was still empty — by the time events loaded, the scroll had fired against an empty container and was a no-op. Fix: the stay-at-bottom effect now re-queues a scroll viascrollToBottomRef.current.count += 1aftersetTimeline(getInitialTimeline(room)). —src/app/features/room/RoomTimeline.tsxTS2367 redundant phase guard in
useCallSignalingA
phase !== undefinedcheck was alwaystrueat that point in the control flow; TypeScript correctly flagged it as a comparison error. Dead branch removed. —src/app/hooks/useCallSignaling.tsSetup
Sentry is opt-in: the integration is a no-op unless
VITE_SENTRY_DSNis set at build time. Self-hosters who do not set this variable are unaffected.VITE_SENTRY_DSNVITE_SENTRY_ENVIRONMENTproduction(10% sampling) orpreview(100%)SENTRY_AUTH_TOKENSee docs/SENTRY_INTEGRATION.md for full configuration details, self-hosting with Docker, and the complete metrics reference.