EchoSrv: Guard backend initialisation with try/catch (#111612)

* EchoSrv: Guard backend initialisation with try/catch

* remove test exception
This commit is contained in:
Josh Hunt 2025-09-25 18:05:24 +01:00 committed by GitHub
parent dab39c873f
commit e426b1d9d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 195 additions and 123 deletions

View File

@ -23,10 +23,8 @@ import { DEFAULT_LANGUAGE } from '@grafana/i18n';
import { initializeI18n, loadNamespacedResources } from '@grafana/i18n/internal';
import {
locationService,
registerEchoBackend,
setBackendSrv,
setDataSourceSrv,
setEchoSrv,
setLocationSrv,
setQueryRunnerFactory,
setRunRequest,
@ -79,8 +77,7 @@ import { CorrelationsService } from './core/services/CorrelationsService';
import { NewFrontendAssetsChecker } from './core/services/NewFrontendAssetsChecker';
import { backendSrv } from './core/services/backend_srv';
import { contextSrv, RedirectToUrlKey } from './core/services/context_srv';
import { Echo } from './core/services/echo/Echo';
import { reportPerformance } from './core/services/echo/EchoSrv';
import { initEchoSrv } from './core/services/echo/init';
import { KeybindingSrv } from './core/services/keybindingSrv';
import { startMeasure, stopMeasure } from './core/utils/metrics';
import { initAlerting } from './features/alerting/unified/initAlerting';
@ -325,125 +322,6 @@ function initExtensions() {
}
}
async function initEchoSrv() {
setEchoSrv(new Echo({ debug: process.env.NODE_ENV === 'development' }));
window.addEventListener('load', (e) => {
const loadMetricName = 'frontend_boot_load_time_seconds';
// Metrics below are marked in public/views/index.html
const jsLoadMetricName = 'frontend_boot_js_done_time_seconds';
const cssLoadMetricName = 'frontend_boot_css_time_seconds';
if (performance) {
performance.mark(loadMetricName);
reportMetricPerformanceMark('first-paint', 'frontend_boot_', '_time_seconds');
reportMetricPerformanceMark('first-contentful-paint', 'frontend_boot_', '_time_seconds');
reportMetricPerformanceMark(loadMetricName);
reportMetricPerformanceMark(jsLoadMetricName);
reportMetricPerformanceMark(cssLoadMetricName);
}
});
if (contextSrv.user.orgRole !== '') {
const { PerformanceBackend } = await import('./core/services/echo/backends/PerformanceBackend');
registerEchoBackend(new PerformanceBackend({}));
}
if (config.grafanaJavascriptAgent.enabled) {
// Ignore Rudderstack URLs
const rudderstackUrls = [
config.rudderstackConfigUrl,
config.rudderstackDataPlaneUrl,
config.rudderstackIntegrationsUrl,
]
.filter(Boolean)
.map((url) => new RegExp(`${url}.*.`));
const { GrafanaJavascriptAgentBackend } = await import(
'./core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend'
);
registerEchoBackend(
new GrafanaJavascriptAgentBackend({
...config.grafanaJavascriptAgent,
app: {
version: config.buildInfo.version,
environment: config.buildInfo.env,
},
buildInfo: config.buildInfo,
user: {
id: String(contextSrv.user?.id),
email: contextSrv.user?.email,
},
ignoreUrls: rudderstackUrls,
})
);
}
if (config.googleAnalyticsId) {
const { GAEchoBackend } = await import('./core/services/echo/backends/analytics/GABackend');
registerEchoBackend(
new GAEchoBackend({
googleAnalyticsId: config.googleAnalyticsId,
})
);
}
if (config.googleAnalytics4Id) {
const { GA4EchoBackend } = await import('./core/services/echo/backends/analytics/GA4Backend');
registerEchoBackend(
new GA4EchoBackend({
googleAnalyticsId: config.googleAnalytics4Id,
googleAnalytics4SendManualPageViews: config.googleAnalytics4SendManualPageViews,
})
);
}
if (config.rudderstackWriteKey && config.rudderstackDataPlaneUrl) {
const { RudderstackBackend } = await import('./core/services/echo/backends/analytics/RudderstackBackend');
registerEchoBackend(
new RudderstackBackend({
writeKey: config.rudderstackWriteKey,
dataPlaneUrl: config.rudderstackDataPlaneUrl,
user: contextSrv.user,
sdkUrl: config.rudderstackSdkUrl,
configUrl: config.rudderstackConfigUrl,
integrationsUrl: config.rudderstackIntegrationsUrl,
buildInfo: config.buildInfo,
})
);
}
if (config.applicationInsightsConnectionString) {
const { ApplicationInsightsBackend } = await import(
'./core/services/echo/backends/analytics/ApplicationInsightsBackend'
);
registerEchoBackend(
new ApplicationInsightsBackend({
connectionString: config.applicationInsightsConnectionString,
endpointUrl: config.applicationInsightsEndpointUrl,
})
);
}
if (config.analyticsConsoleReporting) {
const { BrowserConsoleBackend } = await import('./core/services/echo/backends/analytics/BrowseConsoleBackend');
registerEchoBackend(new BrowserConsoleBackend());
}
}
/**
* Report when a metric of a given name was marked during the document lifecycle. Works for markers with no duration,
* like PerformanceMark or PerformancePaintTiming (e.g. created with performance.mark, or first-contentful-paint)
*/
function reportMetricPerformanceMark(metricName: string, prefix = '', suffix = ''): void {
const metric = performance.getEntriesByName(metricName).at(0);
if (metric) {
const metricName = metric.name.replace(/-/g, '_');
reportPerformance(`${prefix}${metricName}${suffix}`, Math.round(metric.startTime) / 1000);
}
}
function handleRedirectTo(): void {
const queryParams = locationService.getSearch();
const redirectToParamKey = 'redirectTo';

View File

@ -0,0 +1,182 @@
import { config, registerEchoBackend, setEchoSrv } from '@grafana/runtime';
import { reportMetricPerformanceMark } from 'app/core/utils/metrics';
import { contextSrv } from '../context_srv';
import { Echo } from './Echo';
// Initialise EchoSrv backends, calls during frontend app startup
export async function initEchoSrv() {
setEchoSrv(new Echo({ debug: process.env.NODE_ENV === 'development' }));
window.addEventListener('load', (e) => {
const loadMetricName = 'frontend_boot_load_time_seconds';
// Metrics below are marked in public/views/index.html
const jsLoadMetricName = 'frontend_boot_js_done_time_seconds';
const cssLoadMetricName = 'frontend_boot_css_time_seconds';
if (performance) {
performance.mark(loadMetricName);
reportMetricPerformanceMark('first-paint', 'frontend_boot_', '_time_seconds');
reportMetricPerformanceMark('first-contentful-paint', 'frontend_boot_', '_time_seconds');
reportMetricPerformanceMark(loadMetricName);
reportMetricPerformanceMark(jsLoadMetricName);
reportMetricPerformanceMark(cssLoadMetricName);
}
});
try {
await initPerformanceBackend();
} catch (error) {
console.error('Error initializing EchoSrv Performance backend', error);
}
try {
await initFaroBackend();
} catch (error) {
console.error('Error initializing EchoSrv Faro backend', error);
}
try {
await initGoogleAnalyticsBackend();
} catch (error) {
console.error('Error initializing EchoSrv GoogleAnalytics backend', error);
}
try {
await initGoogleAnalaytics4Backend();
} catch (error) {
console.error('Error initializing EchoSrv GoogleAnalaytics4 backend', error);
}
try {
await initRudderstackBackend();
} catch (error) {
console.error('Error initializing EchoSrv Rudderstack backend', error);
}
try {
await initAzureAppInsightsBackend();
} catch (error) {
console.error('Error initializing EchoSrv AzureAppInsights backend', error);
}
try {
await initConsoleBackend();
} catch (error) {
console.error('Error initializing EchoSrv Console backend', error);
}
}
async function initPerformanceBackend() {
if (contextSrv.user.orgRole === '') {
return;
}
const { PerformanceBackend } = await import('./backends/PerformanceBackend');
registerEchoBackend(new PerformanceBackend({}));
}
async function initFaroBackend() {
if (!config.grafanaJavascriptAgent.enabled) {
return;
}
// Ignore Rudderstack URLs
const rudderstackUrls = [
config.rudderstackConfigUrl,
config.rudderstackDataPlaneUrl,
config.rudderstackIntegrationsUrl,
]
.filter(Boolean)
.map((url) => new RegExp(`${url}.*.`));
const { GrafanaJavascriptAgentBackend } = await import(
'./backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend'
);
registerEchoBackend(
new GrafanaJavascriptAgentBackend({
...config.grafanaJavascriptAgent,
app: {
version: config.buildInfo.version,
environment: config.buildInfo.env,
},
buildInfo: config.buildInfo,
user: {
id: String(contextSrv.user?.id),
email: contextSrv.user?.email,
},
ignoreUrls: rudderstackUrls,
})
);
}
async function initGoogleAnalyticsBackend() {
if (!config.googleAnalyticsId) {
return;
}
const { GAEchoBackend } = await import('./backends/analytics/GABackend');
registerEchoBackend(
new GAEchoBackend({
googleAnalyticsId: config.googleAnalyticsId,
})
);
}
async function initGoogleAnalaytics4Backend() {
if (!config.googleAnalytics4Id) {
return;
}
const { GA4EchoBackend } = await import('./backends/analytics/GA4Backend');
registerEchoBackend(
new GA4EchoBackend({
googleAnalyticsId: config.googleAnalytics4Id,
googleAnalytics4SendManualPageViews: config.googleAnalytics4SendManualPageViews,
})
);
}
async function initRudderstackBackend() {
if (!(config.rudderstackWriteKey && config.rudderstackDataPlaneUrl)) {
return;
}
const { RudderstackBackend } = await import('./backends/analytics/RudderstackBackend');
registerEchoBackend(
new RudderstackBackend({
writeKey: config.rudderstackWriteKey,
dataPlaneUrl: config.rudderstackDataPlaneUrl,
user: contextSrv.user,
sdkUrl: config.rudderstackSdkUrl,
configUrl: config.rudderstackConfigUrl,
integrationsUrl: config.rudderstackIntegrationsUrl,
buildInfo: config.buildInfo,
})
);
}
async function initAzureAppInsightsBackend() {
if (!config.applicationInsightsConnectionString) {
return;
}
const { ApplicationInsightsBackend } = await import('./backends/analytics/ApplicationInsightsBackend');
registerEchoBackend(
new ApplicationInsightsBackend({
connectionString: config.applicationInsightsConnectionString,
endpointUrl: config.applicationInsightsEndpointUrl,
})
);
}
async function initConsoleBackend() {
if (!config.analyticsConsoleReporting) {
return;
}
const { BrowserConsoleBackend } = await import('./backends/analytics/BrowseConsoleBackend');
registerEchoBackend(new BrowserConsoleBackend());
}

View File

@ -35,3 +35,15 @@ export function stopMeasure(eventName: string) {
return;
}
}
/**
* Report when a metric of a given name was marked during the document lifecycle. Works for markers with no duration,
* like PerformanceMark or PerformancePaintTiming (e.g. created with performance.mark, or first-contentful-paint)
*/
export function reportMetricPerformanceMark(metricName: string, prefix = '', suffix = ''): void {
const metric = performance.getEntriesByName(metricName).at(0);
if (metric) {
const metricName = metric.name.replace(/-/g, '_');
reportPerformance(`${prefix}${metricName}${suffix}`, Math.round(metric.startTime) / 1000);
}
}