Skip to content

Commit 3d1ce71

Browse files
feat(rum-core): add support for adding labels to captured errors (#1594)
1 parent fd19202 commit 3d1ce71

File tree

6 files changed

+61
-14
lines changed

6 files changed

+61
-14
lines changed

docs/reference/agent-api.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,15 @@ Use this method to get the current active transaction. If there is no active tra
215215
## `apm.captureError()` [capture-error]
216216

217217
```js
218-
apm.captureError(error)
218+
apm.captureError(error, options)
219219
```
220220

221221
Arguments:
222222

223223
* `error` - An instance of `Error`.
224+
* `options` - The following options are supported:
225+
226+
* `labels` - Add additional context with labels, these labels will be added to the error along with the labels from the current transaction.
224227

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

packages/rum-core/src/error-logging/error-logging.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
*/
2525

2626
import { createStackTraces, filterInvalidFrames } from './stack-trace'
27-
import { generateRandomId, merge, extend } from '../common/utils'
27+
import { generateRandomId, merge, extend, setLabel } from '../common/utils'
2828
import { getPageContext } from '../common/context'
2929
import { truncateModel, ERROR_MODEL } from '../common/truncate'
3030
import stackParser from 'error-stack-parser'
@@ -77,7 +77,7 @@ class ErrorLogging {
7777
/**
7878
* errorEvent = { message, filename, lineno, colno, error }
7979
*/
80-
createErrorDataModel(errorEvent) {
80+
createErrorDataModel(errorEvent, opts) {
8181
const frames = createStackTraces(stackParser, errorEvent)
8282
const filteredFrames = filterInvalidFrames(frames)
8383

@@ -100,6 +100,11 @@ class ErrorLogging {
100100
errorContext.custom = customProperties
101101
}
102102
}
103+
if (opts && opts.labels) {
104+
var keys = Object.keys(opts.labels)
105+
errorContext.tags = {}
106+
keys.forEach(k => setLabel(k, opts.labels[k], errorContext.tags))
107+
}
103108

104109
if (!errorType) {
105110
/**
@@ -151,11 +156,11 @@ class ErrorLogging {
151156
return truncateModel(ERROR_MODEL, errorObject)
152157
}
153158

154-
logErrorEvent(errorEvent) {
159+
logErrorEvent(errorEvent, opts) {
155160
if (typeof errorEvent === 'undefined') {
156161
return
157162
}
158-
var errorObject = this.createErrorDataModel(errorEvent)
163+
var errorObject = this.createErrorDataModel(errorEvent, opts)
159164
if (typeof errorObject.exception.message === 'undefined') {
160165
return
161166
}
@@ -194,14 +199,14 @@ class ErrorLogging {
194199
this.logErrorEvent(errorEvent)
195200
}
196201

197-
logError(messageOrError) {
202+
logError(messageOrError, opts) {
198203
let errorEvent = {}
199204
if (typeof messageOrError === 'string') {
200205
errorEvent.message = messageOrError
201206
} else {
202207
errorEvent.error = messageOrError
203208
}
204-
return this.logErrorEvent(errorEvent)
209+
return this.logErrorEvent(errorEvent, opts)
205210
}
206211

207212
_parseRejectReason(reason) {

packages/rum-core/test/error-logging/error-logging.spec.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,23 @@ describe('ErrorLogging', function () {
7373
try {
7474
throw new Error('unittest error')
7575
} catch (error) {
76+
const opts = {
77+
labels: { testLabelKey: 'testLabelValue' }
78+
}
7679
error.test = 'hamid'
7780
error.aDate = new Date('2017-01-12T00:00:00.000Z')
7881
var obj = { test: 'test' }
7982
obj.obj = obj
8083
error.anObject = obj
8184
error.aFunction = function noop() {}
8285
error.null = null
83-
errorLogging.logErrorEvent({ error })
86+
errorLogging.logErrorEvent({ error }, opts)
8487
const events = getEvents()
8588
expect(events.length).toBe(1)
8689
const errorData = events[0][ERRORS]
8790
expect(errorData.context.custom.test).toBe('hamid')
8891
expect(errorData.context.custom.aDate).toBe('2017-01-12T00:00:00.000Z') // toISOString()
92+
expect(errorData.context.tags).toEqual({ testLabelKey: 'testLabelValue' })
8993
expect(errorData.context.custom.anObject).toBeUndefined()
9094
expect(errorData.context.custom.aFunction).toBeUndefined()
9195
expect(errorData.context.custom.null).toBeUndefined()
@@ -172,7 +176,10 @@ describe('ErrorLogging', function () {
172176
const errorEvent = {
173177
error: new Error(testErrorMessage)
174178
}
175-
const errorData = errorLogging.createErrorDataModel(errorEvent)
179+
const opts = {
180+
labels: { testLabelKey: 'testLabelValue' }
181+
}
182+
const errorData = errorLogging.createErrorDataModel(errorEvent, opts)
176183
expect(errorData.context).toEqual(
177184
jasmine.objectContaining({
178185
page: {
@@ -184,7 +191,8 @@ describe('ErrorLogging', function () {
184191
foo: 'bar',
185192
bar: 20
186193
},
187-
user: { id: 12, username: 'test' }
194+
user: { id: 12, username: 'test' },
195+
tags: { testLabelKey: 'testLabelValue' }
188196
})
189197
)
190198
transaction.end()
@@ -312,6 +320,27 @@ describe('ErrorLogging', function () {
312320
}
313321
})
314322

323+
it('should add error with context to queue', function () {
324+
apmServer.init()
325+
configService.setConfig({
326+
serviceName: 'serviceName'
327+
})
328+
spyOn(apmServer, 'sendEvents')
329+
try {
330+
throw new Error('error with context')
331+
} catch (error) {
332+
const opts = {
333+
labels: { testLabelKey: 'testLabelValue' }
334+
}
335+
errorLogging.logError('test error', opts)
336+
expect(apmServer.sendEvents).not.toHaveBeenCalled()
337+
expect(apmServer.queue.items.length).toBe(1)
338+
expect(apmServer.queue.items[0].errors.context).toEqual(
339+
jasmine.objectContaining({ tags: { testLabelKey: 'testLabelValue' } })
340+
)
341+
}
342+
})
343+
315344
it('should capture unhandled rejection events', done => {
316345
apmServer.init()
317346
configService.setConfig({

packages/rum/src/apm-base.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,10 @@ export default class ApmBase {
316316
}
317317
}
318318

319-
captureError(error) {
319+
captureError(error, opts) {
320320
if (this.isEnabled()) {
321321
var errorLogging = this.serviceFactory.getService(ERROR_LOGGING)
322-
return errorLogging.logError(error)
322+
return errorLogging.logError(error, opts)
323323
}
324324
}
325325

packages/rum/src/index.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ declare module '@elastic/apm-rum' {
110110
}) => boolean
111111
}
112112

113+
export interface ErrorOptions {
114+
labels: Labels
115+
}
116+
113117
type Init = (options?: AgentConfigOptions) => ApmBase
114118
const init: Init
115119

@@ -142,7 +146,7 @@ declare module '@elastic/apm-rum' {
142146
options?: SpanOptions
143147
): Span | undefined
144148
getCurrentTransaction(): Transaction | undefined
145-
captureError(error: Error | string): void
149+
captureError(error: Error | string, opts?: ErrorOptions): void
146150
addFilter(fn: FilterFn): void
147151
}
148152
const apmBase: ApmBase

packages/rum/test/specs/index.spec.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,20 @@ describe('index', function () {
7474
try {
7575
throw new Error('ApmBase test error')
7676
} catch (error) {
77-
apmBase.captureError(error)
77+
apmBase.captureError(error, {
78+
labels: { testLabelKey: 'testLabelValue' }
79+
})
7880
expect(apmServer.sendEvents).not.toHaveBeenCalled()
7981

8082
if (isPlatformSupported()) {
8183
expect(apmServer.queue.items.length).toBe(1)
8284
setTimeout(() => {
8385
expect(apmServer.sendEvents).toHaveBeenCalled()
8486
var callData = apmServer.sendEvents.calls.mostRecent()
87+
var eventData = callData.args[0][0]
88+
expect(eventData.errors.context.tags.testLabelKey).toBe(
89+
'testLabelValue'
90+
)
8591
callData.returnValue.then(
8692
() => {
8793
// Wait before ending the test to make sure the result are processed by the agent.

0 commit comments

Comments
 (0)