diff --git a/public/app/app.ts b/public/app/app.ts index 8cd58c4c6d6..25c730a2d34 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -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'; diff --git a/public/app/core/services/echo/init.ts b/public/app/core/services/echo/init.ts new file mode 100644 index 00000000000..f25e13f2325 --- /dev/null +++ b/public/app/core/services/echo/init.ts @@ -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()); +} diff --git a/public/app/core/utils/metrics.ts b/public/app/core/utils/metrics.ts index e244851aa74..28df336b38b 100644 --- a/public/app/core/utils/metrics.ts +++ b/public/app/core/utils/metrics.ts @@ -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); + } +}