feat: init perps sentry implementation - #41528
Conversation
✨ Files requiring CODEOWNER review ✨🔑 @MetaMask/accounts-engineers (1 files, +3 -5)
👨🔧 @MetaMask/core-extension-ux (1 files, +3 -5)
👨🔧 @MetaMask/perps (23 files, +1917 -227)
|
Builds ready [1009523]
⚡ Performance Benchmarks (Total: 🟢 7 pass · 🟡 12 warn · 🔴 0 fail)
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
Builds ready [74a33b3]
⚡ Performance Benchmarks (Total: 🟢 7 pass · 🟡 12 warn · 🔴 0 fail)
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
Builds ready [eb70694]
⚡ Performance Benchmarks (Total: 🟢 7 pass · 🟡 12 warn · 🔴 0 fail)
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
There was a problem hiding this comment.
The tracer's bounded span tracking with FIFO eviction looks solid, and the observability primitives are separated cleanly. I am requesting changes on two points: the lifecycle breadcrumbs are mounted too low and drop coverage on non-home Perps routes, and the market-detail loaded measurement fires before the page is actually ready. I also don't see any integration of the new error translation module. I left comments on each.
geositta
left a comment
There was a problem hiding this comment.
Looks good, thanks for addressing my points.
Builds ready [d40b54c]
⚡ Performance Benchmarks (Total: 🟢 7 pass · 🟡 12 warn · 🔴 0 fail)
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c989573. Configure here.
Builds ready [c989573]
⚡ Performance Benchmarks (Total: 🟢 7 pass · 🟡 12 warn · 🔴 0 fail)
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
geositta
left a comment
There was a problem hiding this comment.
I found a new followup while verifying the breadcrumb change. My earlier comment focused on the routed Perps tree and suggested PerpsLayout as the right mount point for that path which you fixed.
However I noticed there is also an embedded Perps mount in ui/components/multichain/account-overview/account-overview-tabs.tsx that goes through perps-view-stream-boundary.tsx rather than PerpsLayout, so breadcrumb coverage is still not complete across every Perps surface.
If we want complete coverage across both mount paths, the simplest followup is to attach usePerpsLifecycleBreadcrumbs() in PerpsViewStreamBoundary as well.
|
Builds ready [35298ff]
⚡ Performance Benchmarks (Total: 🟢 7 pass · 🟡 12 warn · 🔴 0 fail)
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
🧪 Validation RunVerdict: 📋 every emission this PR adds has a named bound — but two of the background bounds work by calling Note Trial run of the MetaMask evidence skills — Each emission site the diff adds, read out of the committed tree at the PR head, next to whatever it is that limits how often it fires. Counted over the whole PR, base Ran to completion (exit 0) — read the output, no verdict assertedClaim under test: every Sentry emission this PR adds is bounded, and by what $ bash -c B=850806819e5d6d65ea5a632ff38fc03dc72c1827; H=35298ff56ddc38ad4d67ca53630f4fb91653c2c9
I=app/scripts/controllers/perps/infrastructure.ts
git fetch -q --depth=1 origin $B
echo "== production files this PR touches (tests excluded) =="
git diff --stat $B $H -- "*.ts" "*.tsx" ":(exclude)*.test.*" ":(exclude)test/*"
echo
echo "== usePerpsMeasurement: what bounds how often it reports =="
git show $H:ui/hooks/perps/usePerpsMeasurement.ts | cat -n | sed -n 18,32p
echo
echo "== every measurement name reaching Sentry from a production call site =="
git grep -hoE "usePerpsMeasurement[(][^,]+" $H -- "*.tsx" "*.ts" ":(exclude)*.test.*" | sort -u
echo
echo "== usePerpsLifecycleBreadcrumbs: listeners registered, and removed =="
git grep -nE "addEventListener|removeEventListener" $H -- ui/hooks/perps/usePerpsLifecycleBreadcrumbs.ts
echo
echo "== the background spans, which no component lifecycle bounds =="
git show $H:$I | cat -n | sed -n 80p
git show $H:$I | cat -n | sed -n 104,137p
echo
echo "== and how a span ends =="
git show $H:$I | cat -n | sed -n 139,155p
== production files this PR touches (tests excluded) ==
app/scripts/controllers/perps/infrastructure.ts | 94 ++++++++-
.../perps/close-position/close-position-modal.tsx | 15 +-
.../app/perps/perps-view-stream-boundary.tsx | 2 +
ui/components/app/perps/perps-view.tsx | 4 +
.../reverse-position/reverse-position-modal.tsx | 10 +-
.../app/perps/utils/translate-perps-error.ts | 233 +++++++++++++++++++++
.../account-overview/account-overview-tabs.tsx | 8 +-
ui/hooks/perps/index.ts | 2 +
ui/hooks/perps/usePerpsLifecycleBreadcrumbs.ts | 56 +++++
ui/hooks/perps/usePerpsMeasurement.ts | 32 +++
ui/pages/perps/perps-layout.tsx | 2 +
ui/pages/perps/perps-market-detail-page.tsx | 6 +
ui/pages/perps/perps-order-entry-page.tsx | 48 +----
ui/pages/perps/perps-withdraw-page.tsx | 18 +-
14 files changed, 462 insertions(+), 68 deletions(-)
== usePerpsMeasurement: what bounds how often it reports ==
18 const mountTimeRef = useRef<number>(performance.now());
19 const hasReportedRef = useRef(false);
20
21 useEffect(() => {
22 if (isReady && !hasReportedRef.current) {
23 hasReportedRef.current = true;
24 const duration = performance.now() - mountTimeRef.current;
25 globalThis.sentry?.setMeasurement?.(
26 measurementName,
27 duration,
28 'millisecond',
29 );
30 }
31 }, [isReady, measurementName]);
32 }
== every measurement name reaching Sentry from a production call site ==
usePerpsMeasurement('PerpsTabLoaded'
== usePerpsLifecycleBreadcrumbs: listeners registered, and removed ==
35298ff56ddc38ad4d67ca53630f4fb91653c2c9:ui/hooks/perps/usePerpsLifecycleBreadcrumbs.ts:48: document.addEventListener('visibilitychange', handleVisibilityChange);
35298ff56ddc38ad4d67ca53630f4fb91653c2c9:ui/hooks/perps/usePerpsLifecycleBreadcrumbs.ts:49: window.addEventListener('beforeunload', handleBeforeUnload);
35298ff56ddc38ad4d67ca53630f4fb91653c2c9:ui/hooks/perps/usePerpsLifecycleBreadcrumbs.ts:52: document.removeEventListener('visibilitychange', handleVisibilityChange);
35298ff56ddc38ad4d67ca53630f4fb91653c2c9:ui/hooks/perps/usePerpsLifecycleBreadcrumbs.ts:53: window.removeEventListener('beforeunload', handleBeforeUnload);
== the background spans, which no component lifecycle bounds ==
80 const MAX_PENDING_SPANS = 50;
104 const key = `${params.name}:${params.id}`;
105
106 // End any existing span with the same key before overwriting to avoid
107 // leaking the old span reference when trace() is called twice with the
108 // same name/id pair.
109 const existing = pendingSpans.get(key);
110 if (existing) {
111 existing.end();
112 pendingSpans.delete(key);
113 }
114
115 // Evict the oldest pending span when the map is at capacity so the map
116 // cannot grow unboundedly over long browser sessions.
117 if (pendingSpans.size >= MAX_PENDING_SPANS) {
118 const oldestKey = pendingSpans.keys().next().value;
119 if (oldestKey !== undefined) {
120 pendingSpans.get(oldestKey)?.end();
121 pendingSpans.delete(oldestKey);
122 }
123 }
124
125 startSpanManual(
126 {
127 name: params.name,
128 op: params.op,
129 attributes: { ...params.tags, ...params.data },
130 },
131 (span: {
132 setAttribute: (key: string, value: PerpsTraceValue) => void;
133 end: () => void;
134 }) => {
135 pendingSpans.set(key, span);
136 },
137 );
== and how a span ends ==
139 endTrace: (params: {
140 name: PerpsTraceName;
141 id: string;
142 data?: Record<string, PerpsTraceValue>;
143 }) => {
144 const key = `${params.name}:${params.id}`;
145 const pending = pendingSpans.get(key);
146 if (pending) {
147 if (params.data) {
148 for (const [attrKey, attrValue] of Object.entries(params.data)) {
149 pending.setAttribute(attrKey, attrValue);
150 }
151 }
152 pending.end();
153 pendingSpans.delete(key);
154 }
155 },Produced by Follows from the above
Open for review: for the trace names this PR introduces, is there a path where |




Description
Wires up Sentry observability for the Perps feature across the background controller and UI layers.
The controller's createLogger now uses Sentry's withScope to attach structured context to errors — every perps error gets a feature:perps tag plus optional tags, context, and extras from the controller. The createTracer stubs are replaced with real startSpanManual span tracking and setMeasurement support.
On the UI side, two new hooks are added:
usePerpsMeasurement — measures time from component mount to data-ready and reports it as a Sentry custom measurement. Used on the Perps tab and market detail page.
usePerpsLifecycleBreadcrumbs — adds breadcrumbs for popup open/hide/close events, which show up in the breadcrumb trail of any Sentry error captured while the user is in Perps.
Also adds translatePerpsError / handlePerpsError — a utility that maps PerpsErrorCode values and raw API error strings (via regex patterns) to i18n keys, so error messages surfaced to users are translated and meaningful rather than raw codes.
Changelog
CHANGELOG entry: Wires up sentry events
Related issues
Fixes:
Manual testing steps
Set SENTRY_DSN_DEV, ENABLE_SETTINGS_PAGE_DEV_OPTIONS=true, and DEBUG=metamask:sentry:* in .metamaskrc
Enable MetaMetrics in Settings > Security & Privacy
Navigate to the Perps tab and wait for data to load (fires PerpsTabLoaded measurement)
Throw a test error via Settings > Developer Options
In Sentry Issues, confirm the event has feature:perps tag and perps.lifecycle breadcrumbs
Note: you may need to setup a custom sentry dashboard for testing purposes, as the main dev dashboard gets 429 rate limit errors.
Screenshots/Recordings
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Adds Sentry logging/tracing/measurements and changes Perps UI error/toast messaging to use new translation logic, which could affect what users see during failures. While primarily observability, it touches controller infrastructure and several Perps flows, so regressions would be user-visible.
Overview
Adds first-pass Sentry instrumentation for Perps across controller infrastructure and UI. The Perps controller
loggernow reports errors via SentrywithScopewith a defaultfeature=perpstag plus optional tags/context/extras, and thetracernow creates/ends Sentry spans (with bounded pending-span tracking) and forwardssetMeasurement.Improves Perps error UX by translating controller/API failures into i18n messages. Introduces
translatePerpsError/handlePerpsError(code + regex-pattern mapping) and updates close/reverse/order/withdraw flows to use these translated messages/fallbacks; adds new Perps i18n strings for common failure cases.Adds UI performance/lifecycle breadcrumbs for debugging. New hooks
usePerpsMeasurement(reports load timings) andusePerpsLifecycleBreadcrumbs(popup open/hidden/closing breadcrumbs) are wired into Perps views/layout, with extensive new/updated tests around Sentry integration and translated error outputs.Reviewed by Cursor Bugbot for commit 35298ff. Bugbot is set up for automated code reviews on this repo. Configure here.