Files
Piotr Jamróz dc8e010cf3 Crash detection: Track last user interaction to identify crashes happening after user interactions (#97914)
* Track last user interaction to identify crashes happening after user interactions

* Add keypress event and use capture phase
2025-01-03 13:45:05 +01:00

105 lines
3.6 KiB
TypeScript

import { initCrashDetection } from 'crashme';
import { BaseStateReport } from 'crashme/dist/types';
import { nanoid } from 'nanoid';
import { config, createMonitoringLogger } from '@grafana/runtime';
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
import { contextSrv } from '../services/context_srv';
import { CorsSharedWorker as SharedWorker, sharedWorkersSupported } from '../utils/CorsSharedWorker';
import { isChromePerformance, prepareContext } from './crash.utils';
const logger = createMonitoringLogger('core.crash-detection');
interface GrafanaCrashReport extends BaseStateReport {
app: {
version: string;
url: string;
};
user: {
email: string;
login: string;
name: string;
lastInteraction: number;
};
memory?: {
heapUtilization: number;
limitUtilization: number;
usedJSHeapSize: number;
totalJSHeapSize: number;
jsHeapSizeLimit: number;
};
}
export function initializeCrashDetection() {
if (!sharedWorkersSupported()) {
return;
}
let lastInteraction = Date.now();
// Add last interaction listeners to capture phase to reduce skipped events when stopPropagation() is called
document.body.addEventListener('click', () => (lastInteraction = Date.now()), true);
document.body.addEventListener('keypress', () => (lastInteraction = Date.now()), true);
initCrashDetection<GrafanaCrashReport>({
id: nanoid(5),
dbName: 'grafana.crashes',
createClientWorker(): Worker {
return new Worker(new URL('./client.worker', import.meta.url));
},
/**
* There are limitations that require us to manually assert the type here.
* 1) Webpack uses static code analysis to create a new entry point for a SharedWorker.
* It requies constructing an object with exact syntax new SharedWorker(...) (https://webpack.js.org/guides/web-workers/)
* 2) Some browsers may not support SharedWorkers hence we cannot extend CorsSharedWorker like CorsWorker and
* window.SharedWorker needs to be referenced during runtime only if it is supported (https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker)
*
* We guarantee the type assertion is correct by returning a SharedWorker in CorsSharedWorker constructor.
*/
createDetectorWorker() {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return new SharedWorker(new URL('./detector.worker', import.meta.url)) as globalThis.SharedWorker;
},
reportCrash: async (report) => {
const preparedContext = prepareContext(report);
logger.logWarning('browser crash detected', preparedContext);
return true;
},
reportStaleTab: async (report) => {
const preparedContext = prepareContext(report);
logger.logWarning('stale browser tab detected', preparedContext);
return true;
},
updateInfo: (info) => {
info.app = {
version: config.buildInfo.version,
url: window.location.href,
};
info.user = {
email: contextSrv.user.email,
login: contextSrv.user.login,
name: contextSrv.user.name,
lastInteraction,
};
if (isChromePerformance(performance)) {
info.memory = {
heapUtilization: performance.memory.usedJSHeapSize / performance.memory.totalJSHeapSize,
limitUtilization: performance.memory.totalJSHeapSize / performance.memory.jsHeapSizeLimit,
usedJSHeapSize: performance.memory.usedJSHeapSize,
totalJSHeapSize: performance.memory.totalJSHeapSize,
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
};
}
},
});
}