From 4b8a0286224d71f2ccacbc0304beb3320d4baad0 Mon Sep 17 00:00:00 2001 From: cjr125 Date: Sun, 22 Sep 2024 10:48:25 -0400 Subject: [PATCH 01/10] feat: Add callback option to config to capture context when starting transactions and spans --- docs/configuration.asciidoc | 451 ++++++++++++++++++ .../rum-core/src/common/config-service.js | 3 +- .../transaction-service.js | 17 +- .../src/performance-monitoring/transaction.js | 7 + packages/rum/src/index.d.ts | 3 +- 5 files changed, 478 insertions(+), 3 deletions(-) create mode 100644 docs/configuration.asciidoc diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc new file mode 100644 index 000000000..8c7351425 --- /dev/null +++ b/docs/configuration.asciidoc @@ -0,0 +1,451 @@ +[[configuration]] +== Configuration + +While initializing the agent you can provide the following configuration options: + +[float] +[[service-name]] +=== `serviceName` + +* *Type:* String +* *Required* + +The Elastic APM service name is used to differentiate data from each of your services. +Can only contain alphanumeric characters, spaces, underscores, and dashes (must match ^[a-zA-Z0-9 _-]+$). + +[float] +[[server-url]] +=== `serverUrl` + +* *Type:* String +* *Default:* `http://localhost:8200` + +The URL used to make requests to the APM Server. + +[float] +[[server-url-prefix]] +=== `serverUrlPrefix` + +* *Type:* String +* *Default* `/intake/v${apiVersion}/rum/events` + +The server prefix URL used to make requests to the APM Server. Some ad blockers block outgoing requests +from the browser if the default server prefix URL is being used. Using a custom server prefix URL can be used to +evade the ad blocker. + +NOTE: If the default server prefix URL is overwritten, the reverse proxy that sits between the +APM Server and the rum agent must reroute traffic to `/intake/v${apiVersion}/rum/events` +so the APM Server knows what intake API to use. + +[float] +[[service-version]] +=== `serviceVersion` + +* *Type:* String + + +The version of the app. +This could be the version from your `package.json` file, +a git commit reference, +or any other string that might help you reference a specific version. +This option is used on the APM Server to find the right sourcemap file to apply to the stack trace. + + +[float] +[[active]] +=== `active` + +* *Type:* Boolean +* *Default:* `true` + +A Boolean value that specifies if the agent should be active or not. +If active, the agent will send APM transactions and track errors. +This option can be used to deactivate the agent in your staging environment. +It can also be used to sample a number of clients. Below is an example to sample 10% of the page loads: + + +[source,js] +---- +var options = { + active: Math.random() < 0.1 +} +---- + +[float] +[[instrument]] +=== `instrument` + +* *Type:* Boolean +* *Default:* `true` + +A Boolean value that specifies if the agent should automatically instrument the application to collect +performance metrics for the application. + +NOTE: Both active and instrument needs to be true for instrumentation to be running. + +[float] +[[disable-instrumentations]] +=== `disableInstrumentations` + +* *Type:* Array +* *Default:* `[]` + +A list of instrumentations which can be disabled. When disabled, no transactions or spans will be created for that type. +The valid options are: + +* `page-load` +* `history` +* `eventtarget` +* `click` +* `xmlhttprequest` +* `fetch` +* `error` + +NOTE: To disable all `http-request` transactions, add both `fetch` and `xmlhttprequest`. +to this config. + +NOTE: To disable `user-interaction` transactions, add `eventtarget` or `click` to this config. +The option `eventtarget` is deprecated and will be removed in the future releases. + +[float] +[[environment]] +=== `environment` + +* *Type:* String +* *Default:* `''` + +The environment where the service being monitored is deployed, e.g. "production", "development", "test", etc. + +Environments allow you to easily filter data on a global level in the APM app. +It's important to be consistent when naming environments across agents. +See {apm-app-ref}/filters.html#environment-selector[environment selector] in the APM app for more information. + +NOTE: This feature is fully supported in the APM app in Kibana versions >= 7.2. +You must use the query bar to filter for a specific environment in versions prior to 7.2. + +[float] +[[log-level]] +=== `logLevel` + +* *Type:* String +* *Default:* `'warn'` + + +Set the verbosity level for the agent. +This does not have any influence on the types of errors that are sent to the APM Server. This option is useful when you want to report an issue with the agent to us. + + +Possible levels are: `trace`, `debug`, `info`, `warn`, and `error`. + +[float] +[[api-version]] +=== `apiVersion` + +* *Type:* number +* *Default:* `2` + +This denotes the version of APM Server's intake API. Setting this value to any number +above `2` will compress the events (transactions and errors) payload sent to the server. + +NOTE: This feature requires APM Server >= 7.8. Setting this flag to number > 2 with older +APM server version would break the RUM payload from reaching the server. + +[float] +[[breakdown-metrics]] +=== `breakdownMetrics` + +* *Type:* Boolean +* *Default:* `false` + +Enable or disable the tracking and collection of breakdown metrics for the transaction. + +NOTE: This feature requires APM Server and Kibana >= 7.4. Setting this flag to `true` with older APM server version +would break the RUM payload from reaching the server. + +NOTE: Breakdown distribution for the transaction varies depending on the type of the transaction. +To understand the different types, see <> + +[float] +[[flush-interval]] +=== `flushInterval` + +* *Type:* Number +* *Default:* `500` + +The agent maintains a single queue to record transaction and error events when they are added. +This option sets the flush interval in *milliseconds* for the queue. + +NOTE: After each flush of the queue, the next flush isn't scheduled until an item is added to the queue. + +[float] +[[page-load-trace-id]] +=== `pageLoadTraceId` + +* *Type:* String + +This option overrides the page load transactions trace ID. + +[float] +[[page-load-parent-id]] +=== `pageLoadParentId` + +* *Type:* String + +This option allows the creation of the page load transaction as child of an existing one. By default, +the agent treats it as the root transaction. + + +[float] +[[page-load-sampled]] +=== `pageLoadSampled` + +* *Type:* Boolean + +This option overrides the page load transactions sampled property. +It is only applicable to `page-load` transactions. + + +[float] +[[page-load-span-id]] +=== `pageLoadSpanId` + +* *Type:* String + +This option overrides the ID of the span that is generated for receiving the initial document. + +[float] +[[page-load-transaction-name]] +=== `pageLoadTransactionName` + +* *Type:* String + +This option sets the name for the page load transaction. By default, transaction names for hard (page load) and soft (route change) navigations are +inferred by the agent based on the current URL. Check the <> +documentation for more details. + + +[float] +[[distributed-tracing]] +=== `distributedTracing` + +* *Type:* Boolean +* *Default:* `true` + +Distributed tracing is enabled by default. Use this option to disable it. + + +[float] +[[distributed-tracing-origins]] +=== `distributedTracingOrigins` + +* *Type:* Array +* *Default:* `[]` + +This option can be set to an array containing one or more Strings or RegExp objects and determines which origins should be monitored as part of distributed tracing. +This option is consulted when the agent is about to add the distributed tracing HTTP header (`traceparent`) to a request. +Please note that each item in the array should be a valid URL containing the origin (other parts of the url are ignored) or a RegExp object. If an item in the array is a string, an exact match will be performed. If it's a RegExp object, its test function will be called with the request origin. + +[source,js] +---- +var options = { + distributedTracingOrigins: ['https://example.com', /https?:\/\/example\.com:\d{4}/] +} +---- + +[float] +[[propagate-tracestate]] +=== `propagateTracestate` + +* *Type:* Boolean +* *Default:* `false` + +When distributed tracing is enabled, this option can be used to propagate the https://www.w3.org/TR/trace-context/#tracestate-header[tracestate] +HTTP header to the configured origins. Before enabling this flag, make sure to change your <> to avoid +Cross-Origin Resource Sharing errors. + +[float] +[[event-throttling]] +=== Event throttling + +Throttle the number of events sent to APM Server. + +[float] +[[events-limit]] +==== `eventsLimit` + +By default, the agent can only send up to `80` events every `60000` milliseconds (one minute). + +* *Type:* Number +* *Default:* `80` + +[float] +[[transaction-sample-rate]] +==== `transactionSampleRate` + +* *Type:* Number +* *Default:* `1.0` + +A number between `0.0` and `1.0` that specifies the sample rate of transactions. By default, all transactions are sampled. + + +[float] +[[central-config]] +==== `centralConfig` + +* *Type:* Boolean +* *Default:* `false` + +This option activates APM Agent Configuration via Kibana. +When set to `true`, the agent starts fetching configurations via the APM Server during the initialization phase. +These central configurations are cached in `sessionStorage`, and will not be fetched again until +the session is closed and/or `sessionStorage` is cleared. +In most cases, this means when the tab/window of the page is closed. + +NOTE: Currently, only <> can be configured via Kibana. + +NOTE: This feature requires APM Server v7.5 or later. +More information is available in {apm-app-ref}/agent-configuration.html[APM Agent configuration]. + + +[float] +[[ignore-transactions]] +==== `ignoreTransactions` + + +* *Type:* Array +* *Default:* `[]` + +An array containing a list of transaction names that should be ignored when sending the payload to the APM server. +It can be set to an array containing one or more Strings or RegExp objects. If an element in the array is a String, an exact match will be performed. +If an element in the array is a RegExp object, its test function will be called with the name of the transation. + +[source,js] +---- +const options = { + ignoreTransactions: [/login*/, '/app'] +} +---- + +NOTE: Spans that are captured as part of the ignored transactions would also be ignored. + + +[float] +[[monitor-longtasks]] +==== `monitorLongtasks` + +* *Type:* Boolean +* *Default:* `true` + +Instructs the agent to start monitoring for browser tasks that block the UI +thread and might delay other user inputs by affecting the overall page +responsiveness. Learn more about <> and how to interpret them. + + +[float] +[[apm-request]] +==== `apmRequest` + +* *Type:* Function +* *Default:* `null` + +[source,js] +---- +apm.init({ apmRequest: (requestParams) => true}) +---- + +Arguments: + +* `requestParams` - This is an object that contains the APM HTTP request details: + +** `url` - The full url of the APM server + +** `method` - Method of the HTTP request + +** `headers` - Headers of the HTTP request + +** `payload` - Body of the HTTP request + +** `xhr` - The `XMLHttpRequest` instance used by the agent to send the request + +`apmRequest` can be used to change or reject requests that are made to the +APM Server. This config can be set to a function, which is called whenever the agent +needs to make a request to the APM Server. + +The callback function is called with a single argument and is expected to return +an output synchronously. If the return value is `true` then the agent continues +with making the (potentially modified) request to the APM Server. + +If this function returns a falsy value the request is discarded with a warning in the console. + +The following example adds a header to the HTTP request: + +[source,js] +---- +apm.init({ + apmRequest({ xhr }) { + xhr.setRequestHeader('custom', 'header') + return true + } +}) +---- + +This example instructs the agent to discard the request, since it's handled by the user: + +[source,js] +---- +apm.init({ + apmRequest({ url, method, headers, payload }) { + // Handle the APM request here or at some later point. + fetch(url, { + method, + headers, + body: payload + }); + return false + } +}) +---- + + +[float] +[[send-credentials]] +==== `sendCredentials` + +* *Type:* Boolean +* *Default:* `false` + +This allows the agent to send cookies when making requests to the APM server. +This is useful on scenarios where the APM server is behind a reverse proxy that requires requests to be authenticated. + +NOTE: If APM Server is deployed in an origin different than the page’s origin, you will need to +<>. + + +[function] +[[transaction-context-callback]] +==== `transactionContextCallback` + +* *Type:* Function +* *Default:* `null` + +`transactionContextCallback` allows the agent to specify a function to be called when starting automatically instrumented transactions and spans and return +context to be set as tags. This enables the agent to capture the context when instrumented events are fired from files which do not import the RUM agent library. + +The following example illustrates an example which captures the stack trace: + +[source,js] +---- +var options = { + transactionContextCallback: () => { + let stack + try { + throw new Error('') + } + catch (error) { + stack = (error as Error).stack || '' + } + stack = stack.split('\n').map(function (line) { return line.trim(); }) + return { stack }; + } +} +---- \ No newline at end of file diff --git a/packages/rum-core/src/common/config-service.js b/packages/rum-core/src/common/config-service.js index a11a52677..4517e4dbb 100644 --- a/packages/rum-core/src/common/config-service.js +++ b/packages/rum-core/src/common/config-service.js @@ -95,7 +95,8 @@ class Config { context: {}, session: false, apmRequest: null, - sendCredentials: false + sendCredentials: false, + transactionContextCallback: null } this.events = new EventHandler() diff --git a/packages/rum-core/src/performance-monitoring/transaction-service.js b/packages/rum-core/src/performance-monitoring/transaction-service.js index 981c2fe60..5f1971055 100644 --- a/packages/rum-core/src/performance-monitoring/transaction-service.js +++ b/packages/rum-core/src/performance-monitoring/transaction-service.js @@ -101,7 +101,15 @@ class TransactionService { createOptions(options) { const config = this._config.config - let presetOptions = { transactionSampleRate: config.transactionSampleRate } + let presetOptions = { + transactionSampleRate: config.transactionSampleRate + } + if (config.transactionContextCallback) { + presetOptions = { + ...presetOptions, + transactionContextCallback: config.transactionContextCallback + } + } let perfOptions = extend(presetOptions, options) if (perfOptions.managed) { perfOptions = extend( @@ -485,6 +493,13 @@ class TransactionService { ) } + if (this._config.config.transactionContextCallback) { + options = { + ...options, + tags: this._config.config.transactionContextCallback() + } + } + const span = tr.startSpan(name, type, options) if (__DEV__) { this._logger.debug( diff --git a/packages/rum-core/src/performance-monitoring/transaction.js b/packages/rum-core/src/performance-monitoring/transaction.js index 09f2d34f8..bacd448e8 100644 --- a/packages/rum-core/src/performance-monitoring/transaction.js +++ b/packages/rum-core/src/performance-monitoring/transaction.js @@ -54,6 +54,13 @@ class Transaction extends SpanBase { this.sampleRate = this.options.transactionSampleRate this.sampled = Math.random() <= this.sampleRate + + if (this.options.transactionContextCallback) { + this.options = { + ...this.options, + tags: this.options.transactionContextCallback() + } + } } addMarks(obj) { diff --git a/packages/rum/src/index.d.ts b/packages/rum/src/index.d.ts index acdd045a6..2f3f62d2b 100644 --- a/packages/rum/src/index.d.ts +++ b/packages/rum/src/index.d.ts @@ -107,7 +107,8 @@ declare module '@elastic/apm-rum' { method: string payload?: string headers?: Record - }) => boolean + }) => boolean, + transactionContextCallback?: (...args: any[]) => any } type Init = (options?: AgentConfigOptions) => ApmBase From 681e9defe4200b01e14d0b8337e5d1ad2418be70 Mon Sep 17 00:00:00 2001 From: cjr125 Date: Wed, 4 Dec 2024 15:13:58 -0500 Subject: [PATCH 02/10] added unit test --- .../transaction-service.spec.js | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/rum-core/test/performance-monitoring/transaction-service.spec.js b/packages/rum-core/test/performance-monitoring/transaction-service.spec.js index 09a270b6c..6cda8dfe1 100644 --- a/packages/rum-core/test/performance-monitoring/transaction-service.spec.js +++ b/packages/rum-core/test/performance-monitoring/transaction-service.spec.js @@ -651,6 +651,34 @@ describe('TransactionService', function () { transaction.end(pageLoadTime + 1000) }) + + it('should capture tags from dispatch context', done => { + config.setConfig({ + transactionContextCallback: () => { + let stack + try { + throw new Error('') + } + catch (error) { + stack = error.stack || '' + } + stack = stack.split('\n').map(function (line) { return line.trim(); }) + return { stack }; + } + }) + const transactionService = new TransactionService(logger, config) + + const tr1 = transactionService.startTransaction( + 'transaction1', + 'transaction' + ) + + tr1.onEnd = () => { + expect(tr1.options.tags.stack).toBeTruthy() + done() + } + tr1.end() + }) }) it('should truncate active spans after transaction ends', () => { From 3652ff6122793a9ae913e2fdd209caa65ecf941d Mon Sep 17 00:00:00 2001 From: cjr125 Date: Wed, 22 Jan 2025 12:51:00 -0500 Subject: [PATCH 03/10] feat: added spanContextCallback --- docs/configuration.asciidoc | 38 ++++++++++++++++-- .../rum-core/src/common/config-service.js | 3 +- .../src/performance-monitoring/span.js | 13 +++++++ .../transaction-service.js | 15 ++++--- .../src/performance-monitoring/transaction.js | 26 ++++++++++--- .../transaction-service.spec.js | 39 ++++++++++++++++--- packages/rum/src/index.d.ts | 3 +- 7 files changed, 113 insertions(+), 24 deletions(-) diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 8c7351425..09fd317f1 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -428,8 +428,9 @@ NOTE: If APM Server is deployed in an origin different than the page’s origin, * *Type:* Function * *Default:* `null` -`transactionContextCallback` allows the agent to specify a function to be called when starting automatically instrumented transactions and spans and return -context to be set as tags. This enables the agent to capture the context when instrumented events are fired from files which do not import the RUM agent library. +`transactionContextCallback` allows the agent to specify a function to be called when starting automatically instrumented transactions and return context to +be set as tags. This enables the agent to capture data such as call stack frames and variable values from the scope when instrumented events are fired from +files which do not import the RUM agent library. The following example illustrates an example which captures the stack trace: @@ -448,4 +449,35 @@ var options = { return { stack }; } } ----- \ No newline at end of file +---- + + +[function] +[[span-context-callback]] +==== `spanContextCallback` + +* *Type:* Function +* *Default:* `null` + +`spanContextCallback` allows the agent to specify a function to be called when starting automatically instrumented spans and return context to be set as tags. +This enables the agent to capture data such as call stack frames and variable values from the scope when instrumented events are fired from files which do +not import the RUM agent library. + +The following example illustrates an example which captures the stack trace: + +[source,js] +---- +var options = { + spanContextCallback: () => { + let stack + try { + throw new Error('') + } + catch (error) { + stack = (error as Error).stack || '' + } + stack = stack.split('\n').map(function (line) { return line.trim(); }) + return { stack }; + } +} +---- diff --git a/packages/rum-core/src/common/config-service.js b/packages/rum-core/src/common/config-service.js index 4517e4dbb..a0e7c1cd1 100644 --- a/packages/rum-core/src/common/config-service.js +++ b/packages/rum-core/src/common/config-service.js @@ -96,7 +96,8 @@ class Config { session: false, apmRequest: null, sendCredentials: false, - transactionContextCallback: null + transactionContextCallback: null, + spanContextCallback: null } this.events = new EventHandler() diff --git a/packages/rum-core/src/performance-monitoring/span.js b/packages/rum-core/src/performance-monitoring/span.js index d584b1070..4be34cd13 100644 --- a/packages/rum-core/src/performance-monitoring/span.js +++ b/packages/rum-core/src/performance-monitoring/span.js @@ -39,6 +39,19 @@ class Span extends SpanBase { this.action = fields[2] } this.sync = this.options.sync + + if ( + this.options.spanContextCallback && + typeof this.options.spanContextCallback === 'function' + ) { + let tags + try { + tags = this.options.spanContextCallback() + this.addLabels(tags) + } catch (e) { + console.error('Failed to execute span context callback', e) + } + } } end(endTime, data) { diff --git a/packages/rum-core/src/performance-monitoring/transaction-service.js b/packages/rum-core/src/performance-monitoring/transaction-service.js index 5f1971055..f5b8ce299 100644 --- a/packages/rum-core/src/performance-monitoring/transaction-service.js +++ b/packages/rum-core/src/performance-monitoring/transaction-service.js @@ -101,7 +101,7 @@ class TransactionService { createOptions(options) { const config = this._config.config - let presetOptions = { + let presetOptions = { transactionSampleRate: config.transactionSampleRate } if (config.transactionContextCallback) { @@ -110,6 +110,12 @@ class TransactionService { transactionContextCallback: config.transactionContextCallback } } + if (config.spanContextCallback) { + presetOptions = { + ...presetOptions, + spanContextCallback: config.spanContextCallback + } + } let perfOptions = extend(presetOptions, options) if (perfOptions.managed) { perfOptions = extend( @@ -493,13 +499,6 @@ class TransactionService { ) } - if (this._config.config.transactionContextCallback) { - options = { - ...options, - tags: this._config.config.transactionContextCallback() - } - } - const span = tr.startSpan(name, type, options) if (__DEV__) { this._logger.debug( diff --git a/packages/rum-core/src/performance-monitoring/transaction.js b/packages/rum-core/src/performance-monitoring/transaction.js index bacd448e8..8bcf6e8f1 100644 --- a/packages/rum-core/src/performance-monitoring/transaction.js +++ b/packages/rum-core/src/performance-monitoring/transaction.js @@ -55,10 +55,16 @@ class Transaction extends SpanBase { this.sampleRate = this.options.transactionSampleRate this.sampled = Math.random() <= this.sampleRate - if (this.options.transactionContextCallback) { - this.options = { - ...this.options, - tags: this.options.transactionContextCallback() + if ( + this.options.transactionContextCallback && + typeof this.options.transactionContextCallback === 'function' + ) { + let tags + try { + tags = this.options.transactionContextCallback() + this.addLabels(tags) + } catch (e) { + console.error('Failed to execute transaction context callback', e) } } } @@ -103,7 +109,17 @@ class Transaction extends SpanBase { if (this.ended) { return } - const opts = extend({}, options) + let opts = extend({}, options) + + if ( + this.options.spanContextCallback && + typeof this.options.spanContextCallback === 'function' + ) { + opts = { + ...opts, + spanContextCallback: this.options.spanContextCallback + } + } opts.onEnd = trc => { this._onSpanEnd(trc) diff --git a/packages/rum-core/test/performance-monitoring/transaction-service.spec.js b/packages/rum-core/test/performance-monitoring/transaction-service.spec.js index 6cda8dfe1..b53caff2c 100644 --- a/packages/rum-core/test/performance-monitoring/transaction-service.spec.js +++ b/packages/rum-core/test/performance-monitoring/transaction-service.spec.js @@ -652,18 +652,19 @@ describe('TransactionService', function () { transaction.end(pageLoadTime + 1000) }) - it('should capture tags from dispatch context', done => { + it('should capture tags from transaction dispatch context', done => { config.setConfig({ transactionContextCallback: () => { let stack try { throw new Error('') - } - catch (error) { + } catch (error) { stack = error.stack || '' } - stack = stack.split('\n').map(function (line) { return line.trim(); }) - return { stack }; + stack = stack.split('\n').map(function (line) { + return line.trim() + }) + return { stack } } }) const transactionService = new TransactionService(logger, config) @@ -674,11 +675,37 @@ describe('TransactionService', function () { ) tr1.onEnd = () => { - expect(tr1.options.tags.stack).toBeTruthy() + expect(tr1.context.tags.stack).toBeTruthy() done() } tr1.end() }) + + it('should capture tags from span dispatch context', done => { + config.setConfig({ + spanContextCallback: () => { + let stack + try { + throw new Error('') + } catch (error) { + stack = error.stack || '' + } + stack = stack.split('\n').map(function (line) { + return line.trim() + }) + return { stack } + } + }) + const transactionService = new TransactionService(logger, config) + + const sp1 = transactionService.startSpan('span1', 'span') + + sp1.onEnd = () => { + expect(sp1.context.tags.stack).toBeTruthy() + done() + } + sp1.end() + }) }) it('should truncate active spans after transaction ends', () => { diff --git a/packages/rum/src/index.d.ts b/packages/rum/src/index.d.ts index 2f3f62d2b..4adff4432 100644 --- a/packages/rum/src/index.d.ts +++ b/packages/rum/src/index.d.ts @@ -108,7 +108,8 @@ declare module '@elastic/apm-rum' { payload?: string headers?: Record }) => boolean, - transactionContextCallback?: (...args: any[]) => any + transactionContextCallback?: (...args: any[]) => any, + spanContextCallback?: (...args: any[]) => any } type Init = (options?: AgentConfigOptions) => ApmBase From 0a9970be460c41e2008164084d1a8f4dfd185893 Mon Sep 17 00:00:00 2001 From: cjr125 Date: Thu, 23 Jan 2025 09:46:08 -0500 Subject: [PATCH 04/10] fix: pr comments --- .../src/performance-monitoring/span.js | 13 ++----- .../transaction-service.js | 33 +++++++++++++----- .../src/performance-monitoring/transaction.js | 23 +++---------- .../transaction-service.spec.js | 34 +++++++++++++++++++ packages/rum/src/index.d.ts | 4 +-- 5 files changed, 66 insertions(+), 41 deletions(-) diff --git a/packages/rum-core/src/performance-monitoring/span.js b/packages/rum-core/src/performance-monitoring/span.js index 4be34cd13..8120cab7c 100644 --- a/packages/rum-core/src/performance-monitoring/span.js +++ b/packages/rum-core/src/performance-monitoring/span.js @@ -40,17 +40,8 @@ class Span extends SpanBase { } this.sync = this.options.sync - if ( - this.options.spanContextCallback && - typeof this.options.spanContextCallback === 'function' - ) { - let tags - try { - tags = this.options.spanContextCallback() - this.addLabels(tags) - } catch (e) { - console.error('Failed to execute span context callback', e) - } + if (this.options.spanContextCallback) { + this.addLabels(this.options.spanContextCallback()) } } diff --git a/packages/rum-core/src/performance-monitoring/transaction-service.js b/packages/rum-core/src/performance-monitoring/transaction-service.js index f5b8ce299..50a2dcfb3 100644 --- a/packages/rum-core/src/performance-monitoring/transaction-service.js +++ b/packages/rum-core/src/performance-monitoring/transaction-service.js @@ -104,17 +104,32 @@ class TransactionService { let presetOptions = { transactionSampleRate: config.transactionSampleRate } - if (config.transactionContextCallback) { - presetOptions = { - ...presetOptions, - transactionContextCallback: config.transactionContextCallback + if (typeof config.transactionContextCallback === 'function') { + const tCC = function () { + let tags = {} + try { + tags = config.transactionContextCallback() + } catch (err) { + this._logger.error( + 'Failed to execute transaction context callback', + err + ) + } + return tags } + presetOptions.transactionContextCallback = tCC.bind(this) } - if (config.spanContextCallback) { - presetOptions = { - ...presetOptions, - spanContextCallback: config.spanContextCallback + if (typeof config.spanContextCallback === 'function') { + const sCC = function () { + let tags = {} + try { + tags = config.spanContextCallback() + } catch (err) { + this._logger.error('Failed to execute span context callback', err) + } + return tags } + presetOptions.spanContextCallback = sCC.bind(this) } let perfOptions = extend(presetOptions, options) if (perfOptions.managed) { @@ -298,7 +313,7 @@ class TransactionService { if (name === NAME_UNKNOWN && pageLoadTransactionName) { tr.name = pageLoadTransactionName } - + /** * Capture the TBT as span after observing for all long task entries * and once performance observer is disconnected diff --git a/packages/rum-core/src/performance-monitoring/transaction.js b/packages/rum-core/src/performance-monitoring/transaction.js index 8bcf6e8f1..0effed85a 100644 --- a/packages/rum-core/src/performance-monitoring/transaction.js +++ b/packages/rum-core/src/performance-monitoring/transaction.js @@ -55,17 +55,8 @@ class Transaction extends SpanBase { this.sampleRate = this.options.transactionSampleRate this.sampled = Math.random() <= this.sampleRate - if ( - this.options.transactionContextCallback && - typeof this.options.transactionContextCallback === 'function' - ) { - let tags - try { - tags = this.options.transactionContextCallback() - this.addLabels(tags) - } catch (e) { - console.error('Failed to execute transaction context callback', e) - } + if (this.options.transactionContextCallback) { + this.addLabels(this.options.transactionContextCallback()) } } @@ -111,14 +102,8 @@ class Transaction extends SpanBase { } let opts = extend({}, options) - if ( - this.options.spanContextCallback && - typeof this.options.spanContextCallback === 'function' - ) { - opts = { - ...opts, - spanContextCallback: this.options.spanContextCallback - } + if (this.options.spanContextCallback) { + opts.spanContextCallback = this.options.spanContextCallback } opts.onEnd = trc => { diff --git a/packages/rum-core/test/performance-monitoring/transaction-service.spec.js b/packages/rum-core/test/performance-monitoring/transaction-service.spec.js index b53caff2c..913df9fc2 100644 --- a/packages/rum-core/test/performance-monitoring/transaction-service.spec.js +++ b/packages/rum-core/test/performance-monitoring/transaction-service.spec.js @@ -706,6 +706,40 @@ describe('TransactionService', function () { } sp1.end() }) + + it('should safely catch and log errors for an invalid callback', () => { + logger = new LoggingService() + spyOn(logger, 'error') + + config.setConfig({ + transactionContextCallback: () => { + throw new Error('Error in transaction callback') + }, + spanContextCallback: () => { + throw new Error('Error in span callback') + } + }) + const transactionService = new TransactionService(logger, config) + + const tr1 = transactionService.startTransaction( + 'transaction1', + 'transaction' + ) + expect(logger.error).toHaveBeenCalledWith( + 'Failed to execute transaction context callback', + new Error('Error in transaction callback') + ) + logger.error.calls.reset() + + const sp1 = tr1.startSpan('span1', 'span') + expect(logger.error).toHaveBeenCalledWith( + 'Failed to execute span context callback', + new Error('Error in span callback') + ) + + sp1.end() + tr1.end() + }) }) it('should truncate active spans after transaction ends', () => { diff --git a/packages/rum/src/index.d.ts b/packages/rum/src/index.d.ts index 4adff4432..f64e7fc8e 100644 --- a/packages/rum/src/index.d.ts +++ b/packages/rum/src/index.d.ts @@ -107,8 +107,8 @@ declare module '@elastic/apm-rum' { method: string payload?: string headers?: Record - }) => boolean, - transactionContextCallback?: (...args: any[]) => any, + }) => boolean + transactionContextCallback?: (...args: any[]) => any spanContextCallback?: (...args: any[]) => any } From f1bf0956372816b3fa15d5cbd5112ec41b6bc236 Mon Sep 17 00:00:00 2001 From: cjr125 Date: Fri, 24 Jan 2025 07:40:25 -0500 Subject: [PATCH 05/10] fix: used more explicit typing for callbacks --- packages/rum/src/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rum/src/index.d.ts b/packages/rum/src/index.d.ts index f64e7fc8e..ae4540975 100644 --- a/packages/rum/src/index.d.ts +++ b/packages/rum/src/index.d.ts @@ -108,8 +108,8 @@ declare module '@elastic/apm-rum' { payload?: string headers?: Record }) => boolean - transactionContextCallback?: (...args: any[]) => any - spanContextCallback?: (...args: any[]) => any + transactionContextCallback?: () => Labels + spanContextCallback?: () => Labels } type Init = (options?: AgentConfigOptions) => ApmBase From d244a011903aa957905e951f55758263c51f8a76 Mon Sep 17 00:00:00 2001 From: cjr125 Date: Fri, 24 Jan 2025 12:18:17 -0500 Subject: [PATCH 06/10] fix: updated binding context for callbacks to only define logger --- .../src/performance-monitoring/transaction-service.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/rum-core/src/performance-monitoring/transaction-service.js b/packages/rum-core/src/performance-monitoring/transaction-service.js index 50a2dcfb3..9dba8c439 100644 --- a/packages/rum-core/src/performance-monitoring/transaction-service.js +++ b/packages/rum-core/src/performance-monitoring/transaction-service.js @@ -117,7 +117,9 @@ class TransactionService { } return tags } - presetOptions.transactionContextCallback = tCC.bind(this) + presetOptions.transactionContextCallback = tCC.bind({ + _logger: this._logger + }) } if (typeof config.spanContextCallback === 'function') { const sCC = function () { @@ -129,7 +131,9 @@ class TransactionService { } return tags } - presetOptions.spanContextCallback = sCC.bind(this) + presetOptions.spanContextCallback = sCC.bind({ + _logger: this._logger + }) } let perfOptions = extend(presetOptions, options) if (perfOptions.managed) { From 25349bdc23c679899206a3c6730a03aef52e1ff6 Mon Sep 17 00:00:00 2001 From: cjr125 Date: Fri, 24 Jan 2025 12:31:19 -0500 Subject: [PATCH 07/10] fix: removed this keyword from callbacks --- .../transaction-service.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/rum-core/src/performance-monitoring/transaction-service.js b/packages/rum-core/src/performance-monitoring/transaction-service.js index 9dba8c439..d546e4fd6 100644 --- a/packages/rum-core/src/performance-monitoring/transaction-service.js +++ b/packages/rum-core/src/performance-monitoring/transaction-service.js @@ -101,39 +101,31 @@ class TransactionService { createOptions(options) { const config = this._config.config + const logger = this._logger let presetOptions = { transactionSampleRate: config.transactionSampleRate } if (typeof config.transactionContextCallback === 'function') { - const tCC = function () { + presetOptions.transactionContextCallback = function () { let tags = {} try { tags = config.transactionContextCallback() } catch (err) { - this._logger.error( - 'Failed to execute transaction context callback', - err - ) + logger.error('Failed to execute transaction context callback', err) } return tags } - presetOptions.transactionContextCallback = tCC.bind({ - _logger: this._logger - }) } if (typeof config.spanContextCallback === 'function') { - const sCC = function () { + presetOptions.spanContextCallback = function () { let tags = {} try { tags = config.spanContextCallback() } catch (err) { - this._logger.error('Failed to execute span context callback', err) + logger.error('Failed to execute span context callback', err) } return tags } - presetOptions.spanContextCallback = sCC.bind({ - _logger: this._logger - }) } let perfOptions = extend(presetOptions, options) if (perfOptions.managed) { From ec058e8136fde24e3e15eb74d80af13e26ede118 Mon Sep 17 00:00:00 2001 From: cjr125 Date: Mon, 27 Jan 2025 11:20:10 -0500 Subject: [PATCH 08/10] Update docs/configuration.asciidoc Co-authored-by: David Luna --- docs/configuration.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 09fd317f1..277c12faa 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -421,7 +421,7 @@ NOTE: If APM Server is deployed in an origin different than the page’s origin, <>. -[function] +[float] [[transaction-context-callback]] ==== `transactionContextCallback` From e6d9e83923879043ca1b6a96901789264889eb37 Mon Sep 17 00:00:00 2001 From: cjr125 Date: Mon, 27 Jan 2025 11:20:21 -0500 Subject: [PATCH 09/10] Update docs/configuration.asciidoc Co-authored-by: David Luna --- docs/configuration.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 277c12faa..6f6460ddb 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -452,7 +452,7 @@ var options = { ---- -[function] +[float] [[span-context-callback]] ==== `spanContextCallback` From 12cc019332af2c8e28e05daf183125388fd5539d Mon Sep 17 00:00:00 2001 From: cjr125 Date: Tue, 1 Apr 2025 11:24:06 -0400 Subject: [PATCH 10/10] Updated docs --- docs/configuration.asciidoc | 483 -------------------------------- docs/reference/configuration.md | 27 ++ 2 files changed, 27 insertions(+), 483 deletions(-) delete mode 100644 docs/configuration.asciidoc diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc deleted file mode 100644 index 6f6460ddb..000000000 --- a/docs/configuration.asciidoc +++ /dev/null @@ -1,483 +0,0 @@ -[[configuration]] -== Configuration - -While initializing the agent you can provide the following configuration options: - -[float] -[[service-name]] -=== `serviceName` - -* *Type:* String -* *Required* - -The Elastic APM service name is used to differentiate data from each of your services. -Can only contain alphanumeric characters, spaces, underscores, and dashes (must match ^[a-zA-Z0-9 _-]+$). - -[float] -[[server-url]] -=== `serverUrl` - -* *Type:* String -* *Default:* `http://localhost:8200` - -The URL used to make requests to the APM Server. - -[float] -[[server-url-prefix]] -=== `serverUrlPrefix` - -* *Type:* String -* *Default* `/intake/v${apiVersion}/rum/events` - -The server prefix URL used to make requests to the APM Server. Some ad blockers block outgoing requests -from the browser if the default server prefix URL is being used. Using a custom server prefix URL can be used to -evade the ad blocker. - -NOTE: If the default server prefix URL is overwritten, the reverse proxy that sits between the -APM Server and the rum agent must reroute traffic to `/intake/v${apiVersion}/rum/events` -so the APM Server knows what intake API to use. - -[float] -[[service-version]] -=== `serviceVersion` - -* *Type:* String - - -The version of the app. -This could be the version from your `package.json` file, -a git commit reference, -or any other string that might help you reference a specific version. -This option is used on the APM Server to find the right sourcemap file to apply to the stack trace. - - -[float] -[[active]] -=== `active` - -* *Type:* Boolean -* *Default:* `true` - -A Boolean value that specifies if the agent should be active or not. -If active, the agent will send APM transactions and track errors. -This option can be used to deactivate the agent in your staging environment. -It can also be used to sample a number of clients. Below is an example to sample 10% of the page loads: - - -[source,js] ----- -var options = { - active: Math.random() < 0.1 -} ----- - -[float] -[[instrument]] -=== `instrument` - -* *Type:* Boolean -* *Default:* `true` - -A Boolean value that specifies if the agent should automatically instrument the application to collect -performance metrics for the application. - -NOTE: Both active and instrument needs to be true for instrumentation to be running. - -[float] -[[disable-instrumentations]] -=== `disableInstrumentations` - -* *Type:* Array -* *Default:* `[]` - -A list of instrumentations which can be disabled. When disabled, no transactions or spans will be created for that type. -The valid options are: - -* `page-load` -* `history` -* `eventtarget` -* `click` -* `xmlhttprequest` -* `fetch` -* `error` - -NOTE: To disable all `http-request` transactions, add both `fetch` and `xmlhttprequest`. -to this config. - -NOTE: To disable `user-interaction` transactions, add `eventtarget` or `click` to this config. -The option `eventtarget` is deprecated and will be removed in the future releases. - -[float] -[[environment]] -=== `environment` - -* *Type:* String -* *Default:* `''` - -The environment where the service being monitored is deployed, e.g. "production", "development", "test", etc. - -Environments allow you to easily filter data on a global level in the APM app. -It's important to be consistent when naming environments across agents. -See {apm-app-ref}/filters.html#environment-selector[environment selector] in the APM app for more information. - -NOTE: This feature is fully supported in the APM app in Kibana versions >= 7.2. -You must use the query bar to filter for a specific environment in versions prior to 7.2. - -[float] -[[log-level]] -=== `logLevel` - -* *Type:* String -* *Default:* `'warn'` - - -Set the verbosity level for the agent. -This does not have any influence on the types of errors that are sent to the APM Server. This option is useful when you want to report an issue with the agent to us. - - -Possible levels are: `trace`, `debug`, `info`, `warn`, and `error`. - -[float] -[[api-version]] -=== `apiVersion` - -* *Type:* number -* *Default:* `2` - -This denotes the version of APM Server's intake API. Setting this value to any number -above `2` will compress the events (transactions and errors) payload sent to the server. - -NOTE: This feature requires APM Server >= 7.8. Setting this flag to number > 2 with older -APM server version would break the RUM payload from reaching the server. - -[float] -[[breakdown-metrics]] -=== `breakdownMetrics` - -* *Type:* Boolean -* *Default:* `false` - -Enable or disable the tracking and collection of breakdown metrics for the transaction. - -NOTE: This feature requires APM Server and Kibana >= 7.4. Setting this flag to `true` with older APM server version -would break the RUM payload from reaching the server. - -NOTE: Breakdown distribution for the transaction varies depending on the type of the transaction. -To understand the different types, see <> - -[float] -[[flush-interval]] -=== `flushInterval` - -* *Type:* Number -* *Default:* `500` - -The agent maintains a single queue to record transaction and error events when they are added. -This option sets the flush interval in *milliseconds* for the queue. - -NOTE: After each flush of the queue, the next flush isn't scheduled until an item is added to the queue. - -[float] -[[page-load-trace-id]] -=== `pageLoadTraceId` - -* *Type:* String - -This option overrides the page load transactions trace ID. - -[float] -[[page-load-parent-id]] -=== `pageLoadParentId` - -* *Type:* String - -This option allows the creation of the page load transaction as child of an existing one. By default, -the agent treats it as the root transaction. - - -[float] -[[page-load-sampled]] -=== `pageLoadSampled` - -* *Type:* Boolean - -This option overrides the page load transactions sampled property. -It is only applicable to `page-load` transactions. - - -[float] -[[page-load-span-id]] -=== `pageLoadSpanId` - -* *Type:* String - -This option overrides the ID of the span that is generated for receiving the initial document. - -[float] -[[page-load-transaction-name]] -=== `pageLoadTransactionName` - -* *Type:* String - -This option sets the name for the page load transaction. By default, transaction names for hard (page load) and soft (route change) navigations are -inferred by the agent based on the current URL. Check the <> -documentation for more details. - - -[float] -[[distributed-tracing]] -=== `distributedTracing` - -* *Type:* Boolean -* *Default:* `true` - -Distributed tracing is enabled by default. Use this option to disable it. - - -[float] -[[distributed-tracing-origins]] -=== `distributedTracingOrigins` - -* *Type:* Array -* *Default:* `[]` - -This option can be set to an array containing one or more Strings or RegExp objects and determines which origins should be monitored as part of distributed tracing. -This option is consulted when the agent is about to add the distributed tracing HTTP header (`traceparent`) to a request. -Please note that each item in the array should be a valid URL containing the origin (other parts of the url are ignored) or a RegExp object. If an item in the array is a string, an exact match will be performed. If it's a RegExp object, its test function will be called with the request origin. - -[source,js] ----- -var options = { - distributedTracingOrigins: ['https://example.com', /https?:\/\/example\.com:\d{4}/] -} ----- - -[float] -[[propagate-tracestate]] -=== `propagateTracestate` - -* *Type:* Boolean -* *Default:* `false` - -When distributed tracing is enabled, this option can be used to propagate the https://www.w3.org/TR/trace-context/#tracestate-header[tracestate] -HTTP header to the configured origins. Before enabling this flag, make sure to change your <> to avoid -Cross-Origin Resource Sharing errors. - -[float] -[[event-throttling]] -=== Event throttling - -Throttle the number of events sent to APM Server. - -[float] -[[events-limit]] -==== `eventsLimit` - -By default, the agent can only send up to `80` events every `60000` milliseconds (one minute). - -* *Type:* Number -* *Default:* `80` - -[float] -[[transaction-sample-rate]] -==== `transactionSampleRate` - -* *Type:* Number -* *Default:* `1.0` - -A number between `0.0` and `1.0` that specifies the sample rate of transactions. By default, all transactions are sampled. - - -[float] -[[central-config]] -==== `centralConfig` - -* *Type:* Boolean -* *Default:* `false` - -This option activates APM Agent Configuration via Kibana. -When set to `true`, the agent starts fetching configurations via the APM Server during the initialization phase. -These central configurations are cached in `sessionStorage`, and will not be fetched again until -the session is closed and/or `sessionStorage` is cleared. -In most cases, this means when the tab/window of the page is closed. - -NOTE: Currently, only <> can be configured via Kibana. - -NOTE: This feature requires APM Server v7.5 or later. -More information is available in {apm-app-ref}/agent-configuration.html[APM Agent configuration]. - - -[float] -[[ignore-transactions]] -==== `ignoreTransactions` - - -* *Type:* Array -* *Default:* `[]` - -An array containing a list of transaction names that should be ignored when sending the payload to the APM server. -It can be set to an array containing one or more Strings or RegExp objects. If an element in the array is a String, an exact match will be performed. -If an element in the array is a RegExp object, its test function will be called with the name of the transation. - -[source,js] ----- -const options = { - ignoreTransactions: [/login*/, '/app'] -} ----- - -NOTE: Spans that are captured as part of the ignored transactions would also be ignored. - - -[float] -[[monitor-longtasks]] -==== `monitorLongtasks` - -* *Type:* Boolean -* *Default:* `true` - -Instructs the agent to start monitoring for browser tasks that block the UI -thread and might delay other user inputs by affecting the overall page -responsiveness. Learn more about <> and how to interpret them. - - -[float] -[[apm-request]] -==== `apmRequest` - -* *Type:* Function -* *Default:* `null` - -[source,js] ----- -apm.init({ apmRequest: (requestParams) => true}) ----- - -Arguments: - -* `requestParams` - This is an object that contains the APM HTTP request details: - -** `url` - The full url of the APM server - -** `method` - Method of the HTTP request - -** `headers` - Headers of the HTTP request - -** `payload` - Body of the HTTP request - -** `xhr` - The `XMLHttpRequest` instance used by the agent to send the request - -`apmRequest` can be used to change or reject requests that are made to the -APM Server. This config can be set to a function, which is called whenever the agent -needs to make a request to the APM Server. - -The callback function is called with a single argument and is expected to return -an output synchronously. If the return value is `true` then the agent continues -with making the (potentially modified) request to the APM Server. - -If this function returns a falsy value the request is discarded with a warning in the console. - -The following example adds a header to the HTTP request: - -[source,js] ----- -apm.init({ - apmRequest({ xhr }) { - xhr.setRequestHeader('custom', 'header') - return true - } -}) ----- - -This example instructs the agent to discard the request, since it's handled by the user: - -[source,js] ----- -apm.init({ - apmRequest({ url, method, headers, payload }) { - // Handle the APM request here or at some later point. - fetch(url, { - method, - headers, - body: payload - }); - return false - } -}) ----- - - -[float] -[[send-credentials]] -==== `sendCredentials` - -* *Type:* Boolean -* *Default:* `false` - -This allows the agent to send cookies when making requests to the APM server. -This is useful on scenarios where the APM server is behind a reverse proxy that requires requests to be authenticated. - -NOTE: If APM Server is deployed in an origin different than the page’s origin, you will need to -<>. - - -[float] -[[transaction-context-callback]] -==== `transactionContextCallback` - -* *Type:* Function -* *Default:* `null` - -`transactionContextCallback` allows the agent to specify a function to be called when starting automatically instrumented transactions and return context to -be set as tags. This enables the agent to capture data such as call stack frames and variable values from the scope when instrumented events are fired from -files which do not import the RUM agent library. - -The following example illustrates an example which captures the stack trace: - -[source,js] ----- -var options = { - transactionContextCallback: () => { - let stack - try { - throw new Error('') - } - catch (error) { - stack = (error as Error).stack || '' - } - stack = stack.split('\n').map(function (line) { return line.trim(); }) - return { stack }; - } -} ----- - - -[float] -[[span-context-callback]] -==== `spanContextCallback` - -* *Type:* Function -* *Default:* `null` - -`spanContextCallback` allows the agent to specify a function to be called when starting automatically instrumented spans and return context to be set as tags. -This enables the agent to capture data such as call stack frames and variable values from the scope when instrumented events are fired from files which do -not import the RUM agent library. - -The following example illustrates an example which captures the stack trace: - -[source,js] ----- -var options = { - spanContextCallback: () => { - let stack - try { - throw new Error('') - } - catch (error) { - stack = (error as Error).stack || '' - } - stack = stack.split('\n').map(function (line) { return line.trim(); }) - return { stack }; - } -} ----- diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 740f59c2c..a8b387639 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -363,3 +363,30 @@ If APM Server is deployed in an origin different than the page’s origin, you w :::: +### `transactionContextCallback` [transaction-context-callback] + +* **Type:** Function +* **Default:** `null` + +`transactionContextCallback` allows the agent to specify a function to be called when starting automatically instrumented transactions and spans and return +context to be set as tags. This enables the agent to capture the context when instrumented events are fired from files which do not import the RUM agent library. + +The following example illustrates an example which captures the stack trace: + +```js +var options = { + transactionContextCallback: () => { + let stack + try { + throw new Error('') + } + catch (error) { + stack = (error as Error).stack || '' + } + stack = stack.split('\n').map(function (line) { return line.trim(); }) + return { stack }; + } +} +``` + +