Skip to content
Merged
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
5 changes: 4 additions & 1 deletion docs/reference/agent-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,15 @@ Use this method to get the current active transaction. If there is no active tra
## `apm.captureError()` [capture-error]

```js
apm.captureError(error)
apm.captureError(error, options)
```

Arguments:

* `error` - An instance of `Error`.
* `options` - The following options are supported:

* `labels` - Add additional context with labels, these labels will be added to the error along with the labels from the current transaction.

Use this method to manually send an error to APM Server:

Expand Down
17 changes: 11 additions & 6 deletions packages/rum-core/src/error-logging/error-logging.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*/

import { createStackTraces, filterInvalidFrames } from './stack-trace'
import { generateRandomId, merge, extend } from '../common/utils'
import { generateRandomId, merge, extend, setLabel } from '../common/utils'
import { getPageContext } from '../common/context'
import { truncateModel, ERROR_MODEL } from '../common/truncate'
import stackParser from 'error-stack-parser'
Expand Down Expand Up @@ -77,7 +77,7 @@ class ErrorLogging {
/**
* errorEvent = { message, filename, lineno, colno, error }
*/
createErrorDataModel(errorEvent) {
createErrorDataModel(errorEvent, opts) {
const frames = createStackTraces(stackParser, errorEvent)
const filteredFrames = filterInvalidFrames(frames)

Expand All @@ -100,6 +100,11 @@ class ErrorLogging {
errorContext.custom = customProperties
}
}
if (opts && opts.labels) {
var keys = Object.keys(opts.labels)
errorContext.tags = {}
keys.forEach(k => setLabel(k, opts.labels[k], errorContext.tags))
}

if (!errorType) {
/**
Expand Down Expand Up @@ -151,11 +156,11 @@ class ErrorLogging {
return truncateModel(ERROR_MODEL, errorObject)
}

logErrorEvent(errorEvent) {
logErrorEvent(errorEvent, opts) {
if (typeof errorEvent === 'undefined') {
return
}
var errorObject = this.createErrorDataModel(errorEvent)
var errorObject = this.createErrorDataModel(errorEvent, opts)
if (typeof errorObject.exception.message === 'undefined') {
return
}
Expand Down Expand Up @@ -194,14 +199,14 @@ class ErrorLogging {
this.logErrorEvent(errorEvent)
}

logError(messageOrError) {
logError(messageOrError, opts) {
let errorEvent = {}
if (typeof messageOrError === 'string') {
errorEvent.message = messageOrError
} else {
errorEvent.error = messageOrError
}
return this.logErrorEvent(errorEvent)
return this.logErrorEvent(errorEvent, opts)
}

_parseRejectReason(reason) {
Expand Down
35 changes: 32 additions & 3 deletions packages/rum-core/test/error-logging/error-logging.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,23 @@ describe('ErrorLogging', function () {
try {
throw new Error('unittest error')
} catch (error) {
const opts = {
labels: { testLabelKey: 'testLabelValue' }
}
error.test = 'hamid'
error.aDate = new Date('2017-01-12T00:00:00.000Z')
var obj = { test: 'test' }
obj.obj = obj
error.anObject = obj
error.aFunction = function noop() {}
error.null = null
errorLogging.logErrorEvent({ error })
errorLogging.logErrorEvent({ error }, opts)
const events = getEvents()
expect(events.length).toBe(1)
const errorData = events[0][ERRORS]
expect(errorData.context.custom.test).toBe('hamid')
expect(errorData.context.custom.aDate).toBe('2017-01-12T00:00:00.000Z') // toISOString()
expect(errorData.context.tags).toEqual({ testLabelKey: 'testLabelValue' })
expect(errorData.context.custom.anObject).toBeUndefined()
expect(errorData.context.custom.aFunction).toBeUndefined()
expect(errorData.context.custom.null).toBeUndefined()
Expand Down Expand Up @@ -172,7 +176,10 @@ describe('ErrorLogging', function () {
const errorEvent = {
error: new Error(testErrorMessage)
}
const errorData = errorLogging.createErrorDataModel(errorEvent)
const opts = {
labels: { testLabelKey: 'testLabelValue' }
}
const errorData = errorLogging.createErrorDataModel(errorEvent, opts)
expect(errorData.context).toEqual(
jasmine.objectContaining({
page: {
Expand All @@ -184,7 +191,8 @@ describe('ErrorLogging', function () {
foo: 'bar',
bar: 20
},
user: { id: 12, username: 'test' }
user: { id: 12, username: 'test' },
tags: { testLabelKey: 'testLabelValue' }
})
)
transaction.end()
Expand Down Expand Up @@ -312,6 +320,27 @@ describe('ErrorLogging', function () {
}
})

it('should add error with context to queue', function () {
apmServer.init()
configService.setConfig({
serviceName: 'serviceName'
})
spyOn(apmServer, 'sendEvents')
try {
throw new Error('error with context')
} catch (error) {
const opts = {
labels: { testLabelKey: 'testLabelValue' }
}
errorLogging.logError('test error', opts)
expect(apmServer.sendEvents).not.toHaveBeenCalled()
expect(apmServer.queue.items.length).toBe(1)
expect(apmServer.queue.items[0].errors.context).toEqual(
jasmine.objectContaining({ tags: { testLabelKey: 'testLabelValue' } })
)
}
})

it('should capture unhandled rejection events', done => {
apmServer.init()
configService.setConfig({
Expand Down
4 changes: 2 additions & 2 deletions packages/rum/src/apm-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,10 @@ export default class ApmBase {
}
}

captureError(error) {
captureError(error, opts) {
if (this.isEnabled()) {
var errorLogging = this.serviceFactory.getService(ERROR_LOGGING)
return errorLogging.logError(error)
return errorLogging.logError(error, opts)
}
}

Expand Down
6 changes: 5 additions & 1 deletion packages/rum/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ declare module '@elastic/apm-rum' {
}) => boolean
}

export interface ErrorOptions {
labels: Labels
}

type Init = (options?: AgentConfigOptions) => ApmBase
const init: Init

Expand Down Expand Up @@ -142,7 +146,7 @@ declare module '@elastic/apm-rum' {
options?: SpanOptions
): Span | undefined
getCurrentTransaction(): Transaction | undefined
captureError(error: Error | string): void
captureError(error: Error | string, opts?: ErrorOptions): void
addFilter(fn: FilterFn): void
}
const apmBase: ApmBase
Expand Down
8 changes: 7 additions & 1 deletion packages/rum/test/specs/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,20 @@ describe('index', function () {
try {
throw new Error('ApmBase test error')
} catch (error) {
apmBase.captureError(error)
apmBase.captureError(error, {
labels: { testLabelKey: 'testLabelValue' }
})
expect(apmServer.sendEvents).not.toHaveBeenCalled()

if (isPlatformSupported()) {
expect(apmServer.queue.items.length).toBe(1)
setTimeout(() => {
expect(apmServer.sendEvents).toHaveBeenCalled()
var callData = apmServer.sendEvents.calls.mostRecent()
var eventData = callData.args[0][0]
expect(eventData.errors.context.tags.testLabelKey).toBe(
'testLabelValue'
)
callData.returnValue.then(
() => {
// Wait before ending the test to make sure the result are processed by the agent.
Expand Down