mirror of
https://github.com/grafana/grafana.git
synced 2025-01-11 08:32:10 -06:00
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 fixes
This commit is contained in:
parent
6e7de36a67
commit
3a6858cf26
@ -222,6 +222,7 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `enableExtensionsAdminPage` | Enables the extension admin page regardless of development mode |
|
||||
| `zipkinBackendMigration` | Enables querying Zipkin data source without the proxy |
|
||||
| `enableSCIM` | Enables SCIM support for user and group management |
|
||||
| `crashDetection` | Enables browser crash detection reporting to Faro. |
|
||||
|
||||
## Development feature toggles
|
||||
|
||||
|
@ -165,6 +165,7 @@
|
||||
"codeowners": "^5.1.1",
|
||||
"copy-webpack-plugin": "12.0.2",
|
||||
"core-js": "3.38.1",
|
||||
"crashme": "0.0.15",
|
||||
"css-loader": "7.1.2",
|
||||
"css-minimizer-webpack-plugin": "6.0.0",
|
||||
"cypress": "13.10.0",
|
||||
|
@ -236,4 +236,5 @@ export interface FeatureToggles {
|
||||
enableExtensionsAdminPage?: boolean;
|
||||
zipkinBackendMigration?: boolean;
|
||||
enableSCIM?: boolean;
|
||||
crashDetection?: boolean;
|
||||
}
|
||||
|
@ -1628,6 +1628,13 @@ var (
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: identityAccessTeam,
|
||||
},
|
||||
{
|
||||
Name: "crashDetection",
|
||||
Description: "Enables browser crash detection reporting to Faro.",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaObservabilityTracesAndProfilingSquad,
|
||||
FrontendOnly: true,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -217,3 +217,4 @@ exploreMetricsRelatedLogs,experimental,@grafana/observability-metrics,false,fals
|
||||
enableExtensionsAdminPage,experimental,@grafana/plugins-platform-backend,false,true,false
|
||||
zipkinBackendMigration,experimental,@grafana/oss-big-tent,false,false,false
|
||||
enableSCIM,experimental,@grafana/identity-access-team,false,false,false
|
||||
crashDetection,experimental,@grafana/observability-traces-and-profiling,false,false,true
|
||||
|
|
@ -878,4 +878,8 @@ const (
|
||||
// FlagEnableSCIM
|
||||
// Enables SCIM support for user and group management
|
||||
FlagEnableSCIM = "enableSCIM"
|
||||
|
||||
// FlagCrashDetection
|
||||
// Enables browser crash detection reporting to Faro.
|
||||
FlagCrashDetection = "crashDetection"
|
||||
)
|
||||
|
@ -855,6 +855,19 @@
|
||||
"expression": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "crashDetection",
|
||||
"resourceVersion": "1730381712885",
|
||||
"creationTimestamp": "2024-10-31T13:35:12Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enables browser crash detection reporting to Faro.",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/observability-traces-and-profiling",
|
||||
"frontend": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "dashboardNewLayouts",
|
||||
@ -3470,4 +3483,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ import { AppChromeService } from './core/components/AppChrome/AppChromeService';
|
||||
import { getAllOptionEditors, getAllStandardFieldConfigs } from './core/components/OptionsUI/registry';
|
||||
import { PluginPage } from './core/components/Page/PluginPage';
|
||||
import { GrafanaContextType, useChromeHeaderHeight, useReturnToPreviousInternal } from './core/context/GrafanaContext';
|
||||
import { initializeCrashDetection } from './core/crash';
|
||||
import { initIconCache } from './core/icons/iconBundle';
|
||||
import { initializeI18n } from './core/internationalization';
|
||||
import { setMonacoEnv } from './core/monacoEnv';
|
||||
@ -267,6 +268,10 @@ export class GrafanaApp {
|
||||
|
||||
initializeScopes();
|
||||
|
||||
if (config.featureToggles.crashDetection) {
|
||||
initializeCrashDetection();
|
||||
}
|
||||
|
||||
const root = createRoot(document.getElementById('reactRoot')!);
|
||||
root.render(
|
||||
createElement(AppWrapper, {
|
||||
|
7
public/app/core/crash/client.worker.ts
Normal file
7
public/app/core/crash/client.worker.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { initClientWorker } from 'crashme';
|
||||
|
||||
initClientWorker({
|
||||
dbName: 'grafana.crashes',
|
||||
// How often the tab will report its state
|
||||
pingInterval: 1000,
|
||||
});
|
58
public/app/core/crash/crash.utils.ts
Normal file
58
public/app/core/crash/crash.utils.ts
Normal file
@ -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;
|
||||
}
|
8
public/app/core/crash/detector.worker.ts
Normal file
8
public/app/core/crash/detector.worker.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { initDetectorWorker } from 'crashme';
|
||||
|
||||
initDetectorWorker({
|
||||
dbName: 'grafana.crashes',
|
||||
interval: 5000,
|
||||
crashThreshold: 5000,
|
||||
staleThreshold: 5000,
|
||||
});
|
81
public/app/core/crash/index.ts
Normal file
81
public/app/core/crash/index.ts
Normal file
@ -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,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
@ -14324,6 +14324,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"crashme@npm:0.0.15":
|
||||
version: 0.0.15
|
||||
resolution: "crashme@npm:0.0.15"
|
||||
checksum: 10/576110b3d61f996869b993c2f6b8dca33ac82d07ea708b39217b6349653e38a9894c1af838136bc60ba61f6c69bd68f1c60e3da772cb2fa1fe0b64c2fbc53b79
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"create-jest@npm:^29.7.0":
|
||||
version: 29.7.0
|
||||
resolution: "create-jest@npm:29.7.0"
|
||||
@ -18878,6 +18885,7 @@ __metadata:
|
||||
common-tags: "npm:1.8.2"
|
||||
copy-webpack-plugin: "npm:12.0.2"
|
||||
core-js: "npm:3.38.1"
|
||||
crashme: "npm:0.0.15"
|
||||
css-loader: "npm:7.1.2"
|
||||
css-minimizer-webpack-plugin: "npm:6.0.0"
|
||||
cypress: "npm:13.10.0"
|
||||
|
Loading…
Reference in New Issue
Block a user