mirror of https://github.com/grafana/grafana
Frontend o11y: Report browser crashes to Faro (#95772)
* Report browser crashes to Faro * Fix linting * Change context log context prefix * Update types * Update crash detection library to report stale tabs * Post merge fixeschore/refactor-groupsync-service-test
parent
6e7de36a67
commit
3a6858cf26
|
@ -0,0 +1,7 @@ |
||||
import { initClientWorker } from 'crashme'; |
||||
|
||||
initClientWorker({ |
||||
dbName: 'grafana.crashes', |
||||
// How often the tab will report its state
|
||||
pingInterval: 1000, |
||||
}); |
@ -0,0 +1,58 @@ |
||||
import { LogContext } from '@grafana/faro-core/dist/types/api/logs/types'; |
||||
|
||||
export interface ChromePerformanceMemory { |
||||
totalJSHeapSize: number; |
||||
usedJSHeapSize: number; |
||||
jsHeapSizeLimit: number; |
||||
} |
||||
|
||||
export interface ChromePerformance { |
||||
memory: ChromePerformanceMemory; |
||||
} |
||||
|
||||
function isChromePerformanceMemory(memory: unknown): memory is ChromePerformanceMemory { |
||||
if (!memory || typeof memory !== 'object') { |
||||
return false; |
||||
} |
||||
|
||||
return 'totalJSHeapSize' in memory && 'usedJSHeapSize' in memory && 'jsHeapSizeLimit' in memory; |
||||
} |
||||
|
||||
export function isChromePerformance(performance: unknown): performance is ChromePerformance { |
||||
if (!performance || typeof performance !== 'object') { |
||||
return false; |
||||
} |
||||
|
||||
return 'memory' in performance && isChromePerformanceMemory(performance.memory); |
||||
} |
||||
|
||||
/** |
||||
* Ensures the context is a flat object with strings (required by Faro) |
||||
*/ |
||||
export function prepareContext(context: Object): LogContext { |
||||
const preparedContext: LogContext = {}; |
||||
function prepare(value: object | string | number, propertyName: string) { |
||||
if (typeof value === 'object' && value !== null) { |
||||
if (Array.isArray(value)) { |
||||
throw new Error('Array values are not supported.'); |
||||
} else { |
||||
for (const key in value) { |
||||
if (value.hasOwnProperty(key)) { |
||||
// @ts-ignore
|
||||
prepare(value[key], propertyName ? `${propertyName}_${key}` : key); |
||||
} |
||||
} |
||||
} |
||||
} else if (typeof value === 'string') { |
||||
preparedContext[propertyName] = value; |
||||
} else if (typeof value === 'number') { |
||||
if (Number.isInteger(value)) { |
||||
preparedContext[propertyName] = value.toString(); |
||||
} else { |
||||
preparedContext[propertyName] = value.toFixed(4); |
||||
} |
||||
} |
||||
} |
||||
prepare(context, 'crash'); |
||||
return preparedContext; |
||||
} |
@ -0,0 +1,8 @@ |
||||
import { initDetectorWorker } from 'crashme'; |
||||
|
||||
initDetectorWorker({ |
||||
dbName: 'grafana.crashes', |
||||
interval: 5000, |
||||
crashThreshold: 5000, |
||||
staleThreshold: 5000, |
||||
}); |
@ -0,0 +1,81 @@ |
||||
import { initCrashDetection } from 'crashme'; |
||||
import { BaseStateReport } from 'crashme/dist/types'; |
||||
import { nanoid } from 'nanoid'; |
||||
|
||||
import { config, createMonitoringLogger } from '@grafana/runtime'; |
||||
|
||||
import { contextSrv } from '../services/context_srv'; |
||||
|
||||
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; |
||||
}; |
||||
memory?: { |
||||
heapUtilization: number; |
||||
limitUtilization: number; |
||||
usedJSHeapSize: number; |
||||
totalJSHeapSize: number; |
||||
jsHeapSizeLimit: number; |
||||
}; |
||||
} |
||||
|
||||
export function initializeCrashDetection() { |
||||
initCrashDetection<GrafanaCrashReport>({ |
||||
id: nanoid(5), |
||||
|
||||
dbName: 'grafana.crashes', |
||||
|
||||
createClientWorker(): Worker { |
||||
return new Worker(new URL('./client.worker', import.meta.url)); |
||||
}, |
||||
|
||||
createDetectorWorker(): SharedWorker { |
||||
return new SharedWorker(new URL('./detector.worker', import.meta.url)); |
||||
}, |
||||
|
||||
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, |
||||
}; |
||||
|
||||
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, |
||||
}; |
||||
} |
||||
}, |
||||
}); |
||||
} |
Loading…
Reference in new issue