From 7bb631ab9a74e330470bdd4d4045a4667c11ec11 Mon Sep 17 00:00:00 2001 From: joshhunt Date: Tue, 7 Oct 2025 13:04:36 +0100 Subject: [PATCH] Clean up faro configuration to align with best practices --- conf/defaults.ini | 56 ++++++----- conf/sample.ini | 42 +++++---- packages/grafana-data/src/types/config.ts | 13 ++- packages/grafana-runtime/src/config.ts | 9 +- .../setting_grafana_javascript_agent.go | 48 +++++----- .../GrafanaJavascriptAgentBackend.ts | 94 ++++++++----------- public/app/core/services/echo/init.ts | 9 +- 7 files changed, 133 insertions(+), 138 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 1c47f30e6f3..b057f887fb4 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1194,11 +1194,39 @@ facility = tag = [log.frontend] -# Should Faro javascript agent be initialized +# Enables the Grafana Faro javascript agent integration for capturing frontend performance and error events. enabled = false -# Custom HTTP endpoint to send events to. Default will log the events to stdout. -custom_endpoint = +# Custom HTTP endpoint to send Grafana Faro events to. Default will send events to /log-grafana-javascript-agent and log the events to stdout. +custom_endpoint = /log-grafana-javascript-agent + +# API Key for Grafana Faro custom endpoint +api_key = + +# level of internal logging for debugging Grafana Javascript Agent. +# possible values are: 0 = OFF, 1 = ERROR, 2 = WARN, 3 = INFO, 4 = VERBOSE +# more details: https://github.com/grafana/faro-web-sdk/blob/v1.3.7/docs/sources/tutorials/quick-start-browser.md#how-to-activate-debugging +internal_logger_level = 0 + +# Enables the Console instrumentation for Grafana Faro +# See https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/console-instrumentation/ +instrumentations_console_enabled = true + +# Enables the Performance instrumentation for Grafana Faro. +# See https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/performance-instrumentation/ +instrumentations_performance_enabled = true + +# Enables the Content Security Policy Violations instrumentation for Grafana Faro. +# See https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/csp-violation-tracking/ +instrumentations_csp_enabled = true + +# Enables the Tracing instrumentation for Grafana Faro. +# See https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/tracing-instrumentation/ +instrumentations_tracing_enabled = true + +# Enables sending attribution data for web vitals with the Performance instrumentation. +# See https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/web-vitals/#web-vitals-attribution-data +web_vitals_attribution_enabled = true # Requests per second limit enforced per an extended period, for Grafana backend log ingestion endpoint (/log). log_endpoint_requests_per_second_limit = 3 @@ -1206,28 +1234,6 @@ log_endpoint_requests_per_second_limit = 3 # Max requests accepted per short interval of time for Grafana backend log ingestion endpoint (/log) log_endpoint_burst_limit = 15 -# Enables all Faro default instrumentation by using `getWebInstrumentations`. Overrides other instrumentation flags. -instrumentations_all_enabled = false - -# Should error instrumentation be enabled, only affects Grafana Javascript Agent -instrumentations_errors_enabled = true - -# Should console instrumentation be enabled, only affects Grafana Javascript Agent -instrumentations_console_enabled = false - -# Should webvitals instrumentation be enabled, only affects Grafana Javascript Agent -instrumentations_webvitals_enabled = false - -# Should tracing instrumentation be enabled, only affects Grafana Javascript Agent -instrumentations_tracing_enabled = false - -# level of internal logging for debugging Grafana Javascript Agent. -# possible values are: 0 = OFF, 1 = ERROR, 2 = WARN, 3 = INFO, 4 = VERBOSE -# more details: https://github.com/grafana/faro-web-sdk/blob/v1.3.7/docs/sources/tutorials/quick-start-browser.md#how-to-activate-debugging -internal_logger_level = 0 - -# Api Key, only applies to Grafana Javascript Agent provider -api_key = #################################### Usage Quotas ######################## [quota] diff --git a/conf/sample.ini b/conf/sample.ini index 964b3df5196..eb59e6688f3 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1172,35 +1172,39 @@ ;tag = [log.frontend] -# Should Faro javascript agent be initialized +# Enables the Grafana Faro javascript agent integration for capturing frontend performance and error events. ;enabled = false -# Custom HTTP endpoint to send events to. Default will log the events to stdout. +# Custom HTTP endpoint to send Grafana Faro events to. Default will send events to /log-grafana-javascript-agent and log the events to stdout. ;custom_endpoint = /log-grafana-javascript-agent -# Requests per second limit enforced an extended period, for Grafana backend log ingestion endpoint (/log). -;log_endpoint_requests_per_second_limit = 3 +# API Key for Grafana Faro custom endpoint +;api_key = -# Max requests accepted per short interval of time for Grafana backend log ingestion endpoint (/log). -;log_endpoint_burst_limit = 15 +# level of internal logging for debugging Grafana Javascript Agent. +# possible values are: 0 = OFF, 1 = ERROR, 2 = WARN, 3 = INFO, 4 = VERBOSE +# more details: https://github.com/grafana/faro-web-sdk/blob/v1.3.7/docs/sources/tutorials/quick-start-browser.md#how-to-activate-debugging +;internal_logger_level = -# Enables all Faro default instrumentation by using `getWebInstrumentations`. Overrides other instrumentation flags. -;instrumentations_all_enabled = false +# Enables the Console instrumentation for Grafana Faro +# See https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/console-instrumentation/ +;instrumentations_console_enabled = true -# Should error instrumentation be enabled, only affects Grafana Javascript Agent -;instrumentations_errors_enabled = true +# Enables the Performance instrumentation for Grafana Faro. +# See https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/performance-instrumentation/ +;instrumentations_performance_enabled = true -# Should console instrumentation be enabled, only affects Grafana Javascript Agent -;instrumentations_console_enabled = false +# Enables the Content Security Policy Violations instrumentation for Grafana Faro. +# See https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/csp-violation-tracking/ +; instrumentations_csp_enabled = true -# Should webvitals instrumentation be enabled, only affects Grafana Javascript Agent -;instrumentations_webvitals_enabled = false +# Enables the Tracing instrumentation for Grafana Faro. +# See https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/tracing-instrumentation/ +;instrumentations_tracing_enabled = true -# Should tracing instrumentation be enabled, only affects Grafana Javascript Agent -;instrumentations_tracing_enabled = false - -# Api Key, only applies to Grafana Javascript Agent provider -;api_key = testApiKey +# Enables sending attribution data for web vitals with the Performance instrumentation. +# See https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/instrument/web-vitals/#web-vitals-attribution-data +;web_vitals_attribution_enabled = true #################################### Usage Quotas ######################## [quota] diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 29079be8bd8..135d3ac22a8 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -1,3 +1,5 @@ +import type { InternalLoggerLevel } from '@grafana/faro-core'; + import { SystemDateFormatSettings } from '../datetime/formats'; import { MapLayerOptions } from '../geo/layer'; @@ -93,11 +95,14 @@ export interface LicenseInfo { export interface GrafanaJavascriptAgentConfig { enabled: boolean; customEndpoint: string; - errorInstrumentalizationEnabled: boolean; - consoleInstrumentalizationEnabled: boolean; - webVitalsInstrumentalizationEnabled: boolean; - tracingInstrumentalizationEnabled: boolean; apiKey: string; + internalLoggerLevel: InternalLoggerLevel; + + consoleInstrumentalizationEnabled: boolean; + performanceInstrumentalizationEnabled: boolean; + cspInstrumentalizationEnabled: boolean; + tracingInstrumentalizationEnabled: boolean; + webVitalsAttribution: boolean; } export interface UnifiedAlertingStateHistoryConfig { diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 2576de932b2..cdb2d8486da 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -153,13 +153,14 @@ export class GrafanaBootConfig { dateFormats?: SystemDateFormatSettings; grafanaJavascriptAgent = { enabled: false, - customEndpoint: '', apiKey: '', - allInstrumentationsEnabled: false, - errorInstrumentalizationEnabled: true, + customEndpoint: '', consoleInstrumentalizationEnabled: false, - webVitalsInstrumentalizationEnabled: false, + performanceInstrumentalizationEnabled: false, + cspInstrumentalizationEnabled: false, tracingInstrumentalizationEnabled: false, + webVitalsAttribution: false, + internalLoggerLevel: 0, }; pluginCatalogURL = 'https://grafana.com/grafana/plugins/'; pluginAdminEnabled = true; diff --git a/pkg/setting/setting_grafana_javascript_agent.go b/pkg/setting/setting_grafana_javascript_agent.go index 84d99162e47..9ce5633f492 100644 --- a/pkg/setting/setting_grafana_javascript_agent.go +++ b/pkg/setting/setting_grafana_javascript_agent.go @@ -1,32 +1,36 @@ package setting type GrafanaJavascriptAgent struct { - Enabled bool `json:"enabled"` - CustomEndpoint string `json:"customEndpoint"` - EndpointRPS int `json:"-"` - EndpointBurst int `json:"-"` - AllInstrumentationsEnabeld bool `json:"allInstrumentationEnabeld"` - ErrorInstrumentalizationEnabled bool `json:"errorInstrumentalizationEnabled"` - ConsoleInstrumentalizationEnabled bool `json:"consoleInstrumentalizationEnabled"` - WebVitalsInstrumentalizationEnabled bool `json:"webVitalsInstrumentalizationEnabled"` - TracingInstrumentalizationEnabled bool `json:"tracingInstrumentalizationEnabled"` - InternalLoggerLevel int `json:"internalLoggerLevel"` - ApiKey string `json:"apiKey"` + EndpointRPS int `json:"-"` + EndpointBurst int `json:"-"` + + // Faro config + Enabled bool `json:"enabled"` + CustomEndpoint string `json:"customEndpoint"` + ApiKey string `json:"apiKey"` + InternalLoggerLevel int `json:"internalLoggerLevel"` + ConsoleInstrumentalizationEnabled bool `json:"consoleInstrumentalizationEnabled"` + PerformanceInstrumentalizationEnabled bool `json:"performanceInstrumentalizationEnabled"` + CSPInstrumentalizationEnabled bool `json:"cspInstrumentalizationEnabled"` + TracingInstrumentalizationEnabled bool `json:"tracingInstrumentalizationEnabled"` + WebVitalsAttributionEnabled bool `json:"webVitalsAttributionEnabled"` } func (cfg *Cfg) readGrafanaJavascriptAgentConfig() { raw := cfg.Raw.Section("log.frontend") cfg.GrafanaJavascriptAgent = GrafanaJavascriptAgent{ - Enabled: raw.Key("enabled").MustBool(true), - CustomEndpoint: raw.Key("custom_endpoint").MustString("/log-grafana-javascript-agent"), - EndpointRPS: raw.Key("log_endpoint_requests_per_second_limit").MustInt(3), - EndpointBurst: raw.Key("log_endpoint_burst_limit").MustInt(15), - AllInstrumentationsEnabeld: raw.Key("instrumentations_all_enabled").MustBool(false), - ErrorInstrumentalizationEnabled: raw.Key("instrumentations_errors_enabled").MustBool(true), - ConsoleInstrumentalizationEnabled: raw.Key("instrumentations_console_enabled").MustBool(true), - WebVitalsInstrumentalizationEnabled: raw.Key("instrumentations_webvitals_enabled").MustBool(true), - TracingInstrumentalizationEnabled: raw.Key("instrumentations_tracing_enabled").MustBool(true), - InternalLoggerLevel: raw.Key("internal_logger_level").MustInt(0), - ApiKey: raw.Key("api_key").String(), + EndpointRPS: raw.Key("log_endpoint_requests_per_second_limit").MustInt(3), + EndpointBurst: raw.Key("log_endpoint_burst_limit").MustInt(15), + + // Faro config + Enabled: raw.Key("enabled").MustBool(false), + CustomEndpoint: raw.Key("custom_endpoint").MustString("/log-grafana-javascript-agent"), + ApiKey: raw.Key("api_key").String(), + InternalLoggerLevel: raw.Key("internal_logger_level").MustInt(0), + ConsoleInstrumentalizationEnabled: raw.Key("instrumentations_console_enabled").MustBool(true), + PerformanceInstrumentalizationEnabled: raw.Key("instrumentations_performance_enabled").MustBool(true), + CSPInstrumentalizationEnabled: raw.Key("instrumentations_csp_enabled").MustBool(true), + TracingInstrumentalizationEnabled: raw.Key("instrumentations_tracing_enabled").MustBool(true), + WebVitalsAttributionEnabled: raw.Key("web_vitals_attribution_enabled").MustBool(true), } } diff --git a/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.ts b/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.ts index e2ec294ff23..bd767fc4baf 100644 --- a/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.ts +++ b/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.ts @@ -1,23 +1,18 @@ import { BuildInfo, escapeRegex } from '@grafana/data'; -import { BaseTransport, defaultInternalLoggerLevel } from '@grafana/faro-core'; +import { BaseTransport, defaultInternalLoggerLevel, type InternalLoggerLevel } from '@grafana/faro-core'; import { initializeFaro, BrowserConfig, - ErrorsInstrumentation, - ConsoleInstrumentation, - WebVitalsInstrumentation, - SessionInstrumentation, FetchTransport, - type Instrumentation, getWebInstrumentations, - Config, + type Instrumentation, } from '@grafana/faro-web-sdk'; import { TracingInstrumentation } from '@grafana/faro-web-tracing'; import { EchoBackend, EchoEvent, EchoEventType } from '@grafana/runtime'; import { EchoSrvTransport } from './EchoSrvTransport'; import { beforeSendHandler } from './beforeSendHandler'; -import { GrafanaJavascriptAgentEchoEvent, User } from './types'; +import { GrafanaJavascriptAgentEchoEvent } from './types'; function isCrossOriginIframe() { try { @@ -27,15 +22,19 @@ function isCrossOriginIframe() { } } -export interface GrafanaJavascriptAgentBackendOptions extends BrowserConfig { - buildInfo: BuildInfo; +export interface GrafanaJavascriptAgentBackendOptions { + apiKey: string; customEndpoint: string; - user: User; - allInstrumentationsEnabled: boolean; - errorInstrumentalizationEnabled: boolean; + internalLoggerLevel: InternalLoggerLevel; + + webVitalsAttribution: boolean; consoleInstrumentalizationEnabled: boolean; - webVitalsInstrumentalizationEnabled: boolean; + performanceInstrumentalizationEnabled: boolean; + cspInstrumentalizationEnabled: boolean; tracingInstrumentalizationEnabled: boolean; + + buildInfo: BuildInfo; + userIdentifier: string; ignoreUrls: RegExp[]; } @@ -49,11 +48,20 @@ export class GrafanaJavascriptAgentBackend implements EchoBackend { supportedEvents = [EchoEventType.GrafanaJavascriptAgent]; - private faroInstance; constructor(public options: GrafanaJavascriptAgentBackendOptions) { // configure instrumentations. - const instrumentations: Instrumentation[] = []; + const instrumentations: Instrumentation[] = [ + ...getWebInstrumentations({ + captureConsole: options.consoleInstrumentalizationEnabled, + enablePerformanceInstrumentation: options.performanceInstrumentalizationEnabled, + enableContentSecurityPolicyInstrumentation: options.cspInstrumentalizationEnabled, + }), + ]; + + if (options.tracingInstrumentalizationEnabled) { + instrumentations.push(new TracingInstrumentation()); + } const ignoreUrls = [ new RegExp(`.*${escapeRegex(options.customEndpoint)}.*`), @@ -62,49 +70,31 @@ export class GrafanaJavascriptAgentBackend ]; const transports: BaseTransport[] = [new EchoSrvTransport({ ignoreUrls })]; - const consoleInstrumentationOptions: Config['consoleInstrumentation'] = - options.allInstrumentationsEnabled || options.consoleInstrumentalizationEnabled - ? { - serializeErrors: true, - } - : {}; // If in cross origin iframe, default to writing to instance logging endpoint if (options.customEndpoint && !isCrossOriginIframe()) { transports.push(new FetchTransport({ url: options.customEndpoint, apiKey: options.apiKey })); } - if (options.errorInstrumentalizationEnabled) { - instrumentations.push(new ErrorsInstrumentation()); - } - if (options.consoleInstrumentalizationEnabled) { - instrumentations.push(new ConsoleInstrumentation()); - } - if (options.webVitalsInstrumentalizationEnabled) { - instrumentations.push(new WebVitalsInstrumentation()); - } - if (options.tracingInstrumentalizationEnabled) { - instrumentations.push(new TracingInstrumentation()); - } - - // session instrumentation must be added! - instrumentations.push(new SessionInstrumentation()); - // initialize GrafanaJavascriptAgent so it can set up its hooks and start collecting errors const grafanaJavaScriptAgentOptions: BrowserConfig = { - globalObjectKey: options.globalObjectKey || 'faro', - preventGlobalExposure: options.preventGlobalExposure || false, app: { name: 'grafana-frontend', version: options.buildInfo.version, environment: options.buildInfo.env, }, - instrumentations: options.allInstrumentationsEnabled - ? [...getWebInstrumentations(), new TracingInstrumentation()] - : instrumentations, - consoleInstrumentation: consoleInstrumentationOptions, - trackWebVitalsAttribution: options.webVitalsInstrumentalizationEnabled || options.allInstrumentationsEnabled, + + user: { + id: options.userIdentifier, + }, + + instrumentations: instrumentations, transports, + + consoleInstrumentation: { + serializeErrors: true, + }, + trackWebVitalsAttribution: options.webVitalsAttribution, ignoreErrors: [ 'ResizeObserver loop limit exceeded', 'ResizeObserver loop completed', @@ -116,21 +106,13 @@ export class GrafanaJavascriptAgentBackend persistent: true, }, batching: { - sendTimeout: 1000, + sendTimeout: 1000, // [FIXME] increase timeout (to 5s)? }, - internalLoggerLevel: options.internalLoggerLevel || defaultInternalLoggerLevel, beforeSend: beforeSendHandler, + internalLoggerLevel: options.internalLoggerLevel ?? defaultInternalLoggerLevel, }; - this.faroInstance = initializeFaro(grafanaJavaScriptAgentOptions); - if (options.user) { - this.faroInstance.api.setUser({ - id: options.user.id, - attributes: { - orgId: String(options.user.orgId) || '', - }, - }); - } + initializeFaro(grafanaJavaScriptAgentOptions); } // noop because the EchoSrvTransport registered in Faro will already broadcast all signals emitted by the Faro API diff --git a/public/app/core/services/echo/init.ts b/public/app/core/services/echo/init.ts index f25e13f2325..b22a96fb7a7 100644 --- a/public/app/core/services/echo/init.ts +++ b/public/app/core/services/echo/init.ts @@ -98,15 +98,8 @@ async function initFaroBackend() { 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, - }, + userIdentifier: contextSrv.user.analytics.identifier, ignoreUrls: rudderstackUrls, }) );