add pinia action trail to sentry events

This commit is contained in:
pa
2026-01-07 20:28:16 +09:00
committed by Natsumi
parent acbc0ca0fc
commit 7ab3ba959b
4 changed files with 170 additions and 22 deletions

View File

@@ -0,0 +1,141 @@
const STORAGE_KEY = 'vrcx:sentry:piniaActions';
function getStorage() {
try {
return localStorage;
} catch {
return null;
}
}
function safeJsonParse(value) {
if (!value) return null;
try {
return JSON.parse(value);
} catch {
return null;
}
}
function safeJsonStringify(value, fallback = '[]') {
try {
return JSON.stringify(value);
} catch {
return fallback;
}
}
export function getPiniaActionTrail() {
const storage = getStorage();
if (!storage) return [];
const data = safeJsonParse(storage.getItem(STORAGE_KEY));
return Array.isArray(data) ? data : [];
}
export function clearPiniaActionTrail() {
const storage = getStorage();
if (!storage) return;
storage.removeItem(STORAGE_KEY);
}
export function appendPiniaActionTrail(entry, maxEntries = 200) {
const storage = getStorage();
if (!storage) return;
const existing = getPiniaActionTrail();
existing.push(entry);
if (existing.length > maxEntries) {
existing.splice(0, existing.length - maxEntries);
}
try {
storage.setItem(STORAGE_KEY, safeJsonStringify(existing));
} catch {
// ignore
}
}
export function createPiniaActionTrailPlugin({ maxEntries = 200 } = {}) {
if (typeof window !== 'undefined') {
// @ts-ignore
if (!window.__VRCX_PINIA_ACTION_TRAIL__) {
// @ts-ignore
window.__VRCX_PINIA_ACTION_TRAIL__ = true;
window.addEventListener('beforeunload', () => {
clearPiniaActionTrail();
});
}
}
return ({ store }) => {
store.$onAction(({ name }) => {
appendPiniaActionTrail(
{
ts: Date.now(),
storeId: store.$id,
action: name
},
maxEntries
);
});
};
}
function readPerformanceMemory() {
// @ts-ignore
const memory = window?.performance?.memory;
if (!memory) return null;
const { usedJSHeapSize, jsHeapSizeLimit } = memory;
if (
typeof usedJSHeapSize !== 'number' ||
typeof jsHeapSizeLimit !== 'number'
) {
return null;
}
return { usedJSHeapSize, jsHeapSizeLimit };
}
export function startRendererMemoryThresholdReport(
Sentry,
{ intervalMs = 10_000, thresholdRatio = 0.8, cooldownMs = 5 * 60_000 } = {}
) {
const initial = readPerformanceMemory();
if (!initial) return null;
if (!Sentry?.withScope) return null;
let lastSent = 0;
return setInterval(() => {
const m = readPerformanceMemory();
if (!m) return;
const ratio = m.usedJSHeapSize / m.jsHeapSizeLimit;
if (!Number.isFinite(ratio) || ratio < thresholdRatio) return;
const now = Date.now();
if (now - lastSent < cooldownMs) return;
lastSent = now;
const trail = getPiniaActionTrail();
Sentry.withScope((scope) => {
scope.setLevel('warning');
scope.setTag('reason', 'high-js-heap');
scope.setContext('memory', {
usedMB: m.usedJSHeapSize / 1024 / 1024,
limitMB: m.jsHeapSizeLimit / 1024 / 1024,
ratio
});
scope.setContext('pinia_actions', {
trail,
count: trail.length
});
Sentry.captureMessage(
'Memory usage critical: nearing JS heap limit'
);
});
}, intervalMs);
}

View File

@@ -1,4 +1,5 @@
import { router } from './router';
import { startRendererMemoryThresholdReport } from './piniaActionTrail';
import configRepository from '../service/config';
@@ -81,22 +82,7 @@ export async function initSentry(app) {
}
return event;
},
beforeSendSpan(span) {
span.data = {
...span.data,
usedJSHeapSize:
// @ts-ignore
window.performance.memory.usedJSHeapSize / 1024 / 1024,
totalJSHeapSize:
// @ts-ignore
window.performance.memory.totalJSHeapSize / 1024 / 1024,
jsHeapSizeLimit:
// @ts-ignore
window.performance.memory.jsHeapSizeLimit / 1024 / 1024,
vrcxId: vrcxId
};
return span;
},
integrations: [
Sentry.replayIntegration({
maskAllText: true,
@@ -119,6 +105,12 @@ export async function initSentry(app) {
]
});
console.log('Sentry initialized');
startRendererMemoryThresholdReport(Sentry, {
thresholdRatio: 0.8,
intervalMs: 10_000,
cooldownMs: 5 * 60_000
});
} catch (e) {
console.error('Failed to initialize Sentry:', e);
return;