Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}
}
```


4 changes: 3 additions & 1 deletion packages/rum-core/src/common/config-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ class Config {
context: {},
session: false,
apmRequest: null,
sendCredentials: false
sendCredentials: false,
transactionContextCallback: null,
spanContextCallback: null
}

this.events = new EventHandler()
Expand Down
4 changes: 4 additions & 0 deletions packages/rum-core/src/performance-monitoring/span.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class Span extends SpanBase {
this.action = fields[2]
}
this.sync = this.options.sync

if (this.options.spanContextCallback) {
this.addLabels(this.options.spanContextCallback())
}
}

end(endTime, data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,32 @@ class TransactionService {

createOptions(options) {
const config = this._config.config
let presetOptions = { transactionSampleRate: config.transactionSampleRate }
const logger = this._logger
let presetOptions = {
transactionSampleRate: config.transactionSampleRate
}
if (typeof config.transactionContextCallback === 'function') {
presetOptions.transactionContextCallback = function () {
let tags = {}
try {
tags = config.transactionContextCallback()
} catch (err) {
logger.error('Failed to execute transaction context callback', err)
}
return tags
}
}
if (typeof config.spanContextCallback === 'function') {
presetOptions.spanContextCallback = function () {
let tags = {}
try {
tags = config.spanContextCallback()
} catch (err) {
logger.error('Failed to execute span context callback', err)
}
return tags
}
}
let perfOptions = extend(presetOptions, options)
if (perfOptions.managed) {
perfOptions = extend(
Expand Down Expand Up @@ -284,7 +309,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
Expand Down
10 changes: 9 additions & 1 deletion packages/rum-core/src/performance-monitoring/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class Transaction extends SpanBase {

this.sampleRate = this.options.transactionSampleRate
this.sampled = Math.random() <= this.sampleRate

if (this.options.transactionContextCallback) {
this.addLabels(this.options.transactionContextCallback())
}
}

addMarks(obj) {
Expand Down Expand Up @@ -96,7 +100,11 @@ class Transaction extends SpanBase {
if (this.ended) {
return
}
const opts = extend({}, options)
let opts = extend({}, options)

if (this.options.spanContextCallback) {
opts.spanContextCallback = this.options.spanContextCallback
}

opts.onEnd = trc => {
this._onSpanEnd(trc)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,95 @@ describe('TransactionService', function () {

transaction.end(pageLoadTime + 1000)
})

it('should capture tags from transaction 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.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 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', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/rum/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ declare module '@elastic/apm-rum' {
payload?: string
headers?: Record<string, string>
}) => boolean
transactionContextCallback?: () => Labels
spanContextCallback?: () => Labels
}

export interface ErrorOptions {
Expand Down
Loading