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..32f69fc03 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( @@ -292,7 +298,6 @@ 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 @@ -493,13 +498,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