mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 06:02:24 +08:00

* Track last user interaction to identify crashes happening after user interactions * Add keypress event and use capture phase
105 lines
3.6 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
},
|
|
});
|
|
}
|