Skip to content

Commit fe631bf

Browse files
authored
feat: Breadcrumbs hint implementation (#1505)
1 parent caea4f8 commit fe631bf

File tree

9 files changed

+143
-73
lines changed

9 files changed

+143
-73
lines changed

packages/browser/src/integrations/breadcrumbs.ts

+57-29
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,20 @@ export interface SentryWrappedXMLHttpRequest extends XMLHttpRequest {
3131
function addSentryBreadcrumb(serializedData: string): void {
3232
// There's always something that can go wrong with deserialization...
3333
try {
34-
const data: { [key: string]: any } = deserialize(serializedData);
35-
const exception = data.exception && data.exception.values && data.exception.values[0];
36-
37-
getCurrentHub().addBreadcrumb({
38-
category: 'sentry',
39-
event_id: data.event_id,
40-
level: data.level || Severity.fromString('error'),
41-
message: exception ? `${exception.type ? `${exception.type}: ` : ''}${exception.value}` : data.message,
42-
});
34+
const event: { [key: string]: any } = deserialize(serializedData);
35+
const exception = event.exception && event.exception.values && event.exception.values[0];
36+
37+
getCurrentHub().addBreadcrumb(
38+
{
39+
category: 'sentry',
40+
event_id: event.event_id,
41+
level: event.level || Severity.fromString('error'),
42+
message: exception ? `${exception.type ? `${exception.type}: ` : ''}${exception.value}` : event.message,
43+
},
44+
{
45+
event,
46+
},
47+
);
4348
} catch (_oO) {
4449
logger.error('Error while adding sentry type breadcrumb');
4550
}
@@ -98,17 +103,20 @@ export class Breadcrumbs implements Integration {
98103
}
99104

100105
// What is wrong with you TypeScript...
101-
const crumb = ({
106+
const breadcrumbData = ({
102107
category: 'beacon',
103108
data,
104109
type: 'http',
105110
} as any) as { [key: string]: any };
106111

107112
if (!result) {
108-
crumb.level = Severity.Error;
113+
breadcrumbData.level = Severity.Error;
109114
}
110115

111-
getCurrentHub().addBreadcrumb(crumb);
116+
getCurrentHub().addBreadcrumb(breadcrumbData, {
117+
input: args,
118+
result,
119+
});
112120

113121
return result;
114122
};
@@ -153,7 +161,10 @@ export class Breadcrumbs implements Integration {
153161
}
154162
}
155163

156-
getCurrentHub().addBreadcrumb(breadcrumbData);
164+
getCurrentHub().addBreadcrumb(breadcrumbData, {
165+
input: args,
166+
level,
167+
});
157168

158169
// this fails for some browsers. :(
159170
if (originalConsoleLevel) {
@@ -223,20 +234,32 @@ export class Breadcrumbs implements Integration {
223234
.apply(global, args)
224235
.then((response: Response) => {
225236
fetchData.status_code = response.status;
226-
getCurrentHub().addBreadcrumb({
227-
category: 'fetch',
228-
data: fetchData,
229-
type: 'http',
230-
});
237+
getCurrentHub().addBreadcrumb(
238+
{
239+
category: 'fetch',
240+
data: fetchData,
241+
type: 'http',
242+
},
243+
{
244+
input: args,
245+
response,
246+
},
247+
);
231248
return response;
232249
})
233250
.catch((error: Error) => {
234-
getCurrentHub().addBreadcrumb({
235-
category: 'fetch',
236-
data: fetchData,
237-
level: Severity.Error,
238-
type: 'http',
239-
});
251+
getCurrentHub().addBreadcrumb(
252+
{
253+
category: 'fetch',
254+
data: fetchData,
255+
level: Severity.Error,
256+
type: 'http',
257+
},
258+
{
259+
error,
260+
input: args,
261+
},
262+
);
240263

241264
throw error;
242265
});
@@ -385,11 +408,16 @@ export class Breadcrumbs implements Integration {
385408
} catch (e) {
386409
/* do nothing */
387410
}
388-
getCurrentHub().addBreadcrumb({
389-
category: 'xhr',
390-
data: xhr.__sentry_xhr__,
391-
type: 'http',
392-
});
411+
getCurrentHub().addBreadcrumb(
412+
{
413+
category: 'xhr',
414+
data: xhr.__sentry_xhr__,
415+
type: 'http',
416+
},
417+
{
418+
xhr,
419+
},
420+
);
393421
}
394422
}
395423

packages/browser/src/integrations/helpers.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,16 @@ export function breadcrumbEventHandler(eventName: string): (event: Event) => voi
135135
target = '<unknown>';
136136
}
137137

138-
getCurrentHub().addBreadcrumb({
139-
category: `ui.${eventName}`, // e.g. ui.click, ui.input
140-
message: target,
141-
});
138+
getCurrentHub().addBreadcrumb(
139+
{
140+
category: `ui.${eventName}`, // e.g. ui.click, ui.input
141+
message: target,
142+
},
143+
{
144+
event,
145+
name: eventName,
146+
},
147+
);
142148
};
143149
}
144150

packages/core/src/base.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { Scope } from '@sentry/hub';
2-
import { Breadcrumb, SentryEvent, SentryEventHint, SentryResponse, Severity, Status } from '@sentry/types';
2+
import {
3+
Breadcrumb,
4+
SentryBreadcrumbHint,
5+
SentryEvent,
6+
SentryEventHint,
7+
SentryResponse,
8+
Severity,
9+
Status,
10+
} from '@sentry/types';
311
import { uuid4 } from '@sentry/utils/misc';
412
import { truncate } from '@sentry/utils/string';
513
import { Dsn } from './dsn';
@@ -144,7 +152,7 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
144152
/**
145153
* @inheritDoc
146154
*/
147-
public async addBreadcrumb(breadcrumb: Breadcrumb, scope?: Scope): Promise<void> {
155+
public async addBreadcrumb(breadcrumb: Breadcrumb, hint?: SentryBreadcrumbHint, scope?: Scope): Promise<void> {
148156
const { beforeBreadcrumb, maxBreadcrumbs = DEFAULT_BREADCRUMBS } = this.getOptions();
149157

150158
if (maxBreadcrumbs <= 0) {
@@ -153,7 +161,7 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
153161

154162
const timestamp = new Date().getTime() / 1000;
155163
const mergedBreadcrumb = { timestamp, ...breadcrumb };
156-
const finalBreadcrumb = beforeBreadcrumb ? beforeBreadcrumb(mergedBreadcrumb) : mergedBreadcrumb;
164+
const finalBreadcrumb = beforeBreadcrumb ? beforeBreadcrumb(mergedBreadcrumb, hint) : mergedBreadcrumb;
157165

158166
if (finalBreadcrumb === null) {
159167
return;

packages/core/src/interfaces.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Breadcrumb,
44
Integration,
55
Repo,
6+
SentryBreadcrumbHint,
67
SentryEvent,
78
SentryEventHint,
89
SentryResponse,
@@ -99,7 +100,7 @@ export interface Options {
99100
* Returning null will case the event to be dropped.
100101
*
101102
* @param event The error or message event generated by the SDK.
102-
* @param hint May contain additional informartion about the original exception.
103+
* @param hint May contain additional information about the original exception.
103104
* @returns A new event that will be sent | null.
104105
*/
105106
beforeSend?(event: SentryEvent, hint?: SentryEventHint): SentryEvent | null;
@@ -115,7 +116,7 @@ export interface Options {
115116
* @param breadcrumb The breadcrumb as created by the SDK.
116117
* @returns The breadcrumb that will be added | null.
117118
*/
118-
beforeBreadcrumb?(breadcrumb: Breadcrumb): Breadcrumb | null;
119+
beforeBreadcrumb?(breadcrumb: Breadcrumb, hint?: SentryBreadcrumbHint): Breadcrumb | null;
119120
}
120121

121122
/**
@@ -150,7 +151,7 @@ export interface Client<O extends Options = Options> {
150151
* Captures an exception event and sends it to Sentry.
151152
*
152153
* @param exception An exception-like object.
153-
* @param hint May contain additional informartion about the original exception.
154+
* @param hint May contain additional information about the original exception.
154155
* @param scope An optional scope containing event metadata.
155156
* @returns SentryResponse status and event
156157
*/
@@ -161,7 +162,7 @@ export interface Client<O extends Options = Options> {
161162
*
162163
* @param message The message to send to Sentry.
163164
* @param level Define the level of the message.
164-
* @param hint May contain additional informartion about the original exception.
165+
* @param hint May contain additional information about the original exception.
165166
* @param scope An optional scope containing event metadata.
166167
* @returns SentryResponse status and event
167168
*/
@@ -171,7 +172,7 @@ export interface Client<O extends Options = Options> {
171172
* Captures a manually created event and sends it to Sentry.
172173
*
173174
* @param event The event to send to Sentry.
174-
* @param hint May contain additional informartion about the original exception.
175+
* @param hint May contain additional information about the original exception.
175176
* @param scope An optional scope containing event metadata.
176177
* @returns SentryResponse status and event
177178
*/
@@ -185,9 +186,10 @@ export interface Client<O extends Options = Options> {
185186
* of breadcrumbs, use {@link Options.maxBreadcrumbs}.
186187
*
187188
* @param breadcrumb The breadcrumb to record.
189+
* @param hint May contain additional information about the original breadcrumb.
188190
* @param scope An optional scope to store this breadcrumb in.
189191
*/
190-
addBreadcrumb(breadcrumb: Breadcrumb, scope?: Scope): void;
192+
addBreadcrumb(breadcrumb: Breadcrumb, hint?: SentryBreadcrumbHint, scope?: Scope): void;
191193

192194
/** Returns the current Dsn. */
193195
getDsn(): Dsn | undefined;

packages/core/test/lib/base.test.ts

+18-9
Original file line numberDiff line numberDiff line change
@@ -84,23 +84,23 @@ describe('BaseClient', () => {
8484
const client = new TestClient({});
8585
const scope = new Scope();
8686
scope.addBreadcrumb({ message: 'hello' }, 100);
87-
await client.addBreadcrumb({ message: 'world' }, scope);
87+
await client.addBreadcrumb({ message: 'world' }, undefined, scope);
8888
expect(scope.getBreadcrumbs()[1].message).toBe('world');
8989
});
9090

9191
test('adds a timestamp to new breadcrumbs', async () => {
9292
const client = new TestClient({});
9393
const scope = new Scope();
9494
scope.addBreadcrumb({ message: 'hello' }, 100);
95-
await client.addBreadcrumb({ message: 'world' }, scope);
95+
await client.addBreadcrumb({ message: 'world' }, undefined, scope);
9696
expect(scope.getBreadcrumbs()[1].timestamp).toBeGreaterThan(1);
9797
});
9898

9999
test('discards breadcrumbs beyond maxBreadcrumbs', async () => {
100100
const client = new TestClient({ maxBreadcrumbs: 1 });
101101
const scope = new Scope();
102102
scope.addBreadcrumb({ message: 'hello' }, 100);
103-
await client.addBreadcrumb({ message: 'world' }, scope);
103+
await client.addBreadcrumb({ message: 'world' }, undefined, scope);
104104
expect(scope.getBreadcrumbs().length).toBe(1);
105105
expect(scope.getBreadcrumbs()[0].message).toBe('world');
106106
});
@@ -109,8 +109,8 @@ describe('BaseClient', () => {
109109
const client = new TestClient({});
110110
const scope = new Scope();
111111
await Promise.all([
112-
client.addBreadcrumb({ message: 'hello' }, scope),
113-
client.addBreadcrumb({ message: 'world' }, scope),
112+
client.addBreadcrumb({ message: 'hello' }, undefined, scope),
113+
client.addBreadcrumb({ message: 'world' }, undefined, scope),
114114
]);
115115
expect(scope.getBreadcrumbs()).toHaveLength(2);
116116
});
@@ -119,25 +119,34 @@ describe('BaseClient', () => {
119119
const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb);
120120
const client = new TestClient({ beforeBreadcrumb });
121121
const scope = new Scope();
122-
await client.addBreadcrumb({ message: 'hello' }, scope);
122+
await client.addBreadcrumb({ message: 'hello' }, undefined, scope);
123123
expect(scope.getBreadcrumbs()[0].message).toBe('hello');
124124
});
125125

126126
test('calls beforeBreadcrumb and uses the new one', async () => {
127127
const beforeBreadcrumb = jest.fn(() => ({ message: 'changed' }));
128128
const client = new TestClient({ beforeBreadcrumb });
129129
const scope = new Scope();
130-
await client.addBreadcrumb({ message: 'hello' }, scope);
130+
await client.addBreadcrumb({ message: 'hello' }, undefined, scope);
131131
expect(scope.getBreadcrumbs()[0].message).toBe('changed');
132132
});
133133

134-
test('calls shouldAddBreadcrumb and discards the breadcrumb', async () => {
134+
test('calls beforeBreadcrumb and discards the breadcrumb when returned null', async () => {
135135
const beforeBreadcrumb = jest.fn(() => null);
136136
const client = new TestClient({ beforeBreadcrumb });
137137
const scope = new Scope();
138-
await client.addBreadcrumb({ message: 'hello' }, scope);
138+
await client.addBreadcrumb({ message: 'hello' }, undefined, scope);
139139
expect(scope.getBreadcrumbs().length).toBe(0);
140140
});
141+
142+
test('calls beforeBreadcrumb gets an access to a hint as a second argument', async () => {
143+
const beforeBreadcrumb = jest.fn((breadcrumb, hint) => ({ ...breadcrumb, data: hint.data }));
144+
const client = new TestClient({ beforeBreadcrumb });
145+
const scope = new Scope();
146+
await client.addBreadcrumb({ message: 'hello' }, { data: 'someRandomThing' }, scope);
147+
expect(scope.getBreadcrumbs()[0].message).toBe('hello');
148+
expect(scope.getBreadcrumbs()[0].data).toBe('someRandomThing');
149+
});
141150
});
142151

143152
describe('captures', () => {

packages/hub/src/hub.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Breadcrumb, SentryEvent, SentryEventHint, Severity } from '@sentry/types';
1+
import { Breadcrumb, SentryBreadcrumbHint, SentryEvent, SentryEventHint, Severity } from '@sentry/types';
22
import { uuid4 } from '@sentry/utils/misc';
33
import { Layer } from './interfaces';
44
import { Scope } from './scope';
@@ -168,7 +168,7 @@ export class Hub {
168168
* Captures an exception event and sends it to Sentry.
169169
*
170170
* @param exception An exception-like object.
171-
* @param hint May contain additional informartion about the original exception.
171+
* @param hint May contain additional information about the original exception.
172172
* @returns The generated eventId.
173173
*/
174174
public captureException(exception: any, hint?: SentryEventHint): string {
@@ -185,7 +185,7 @@ export class Hub {
185185
*
186186
* @param message The message to send to Sentry.
187187
* @param level Define the level of the message.
188-
* @param hint May contain additional informartion about the original exception.
188+
* @param hint May contain additional information about the original exception.
189189
* @returns The generated eventId.
190190
*/
191191
public captureMessage(message: string, level?: Severity, hint?: SentryEventHint): string {
@@ -201,7 +201,7 @@ export class Hub {
201201
* Captures a manually created event and sends it to Sentry.
202202
*
203203
* @param event The event to send to Sentry.
204-
* @param hint May contain additional informartion about the original exception.
204+
* @param hint May contain additional information about the original exception.
205205
*/
206206
public captureEvent(event: SentryEvent, hint?: SentryEventHint): string {
207207
const eventId = (this._lastEventId = uuid4());
@@ -228,9 +228,10 @@ export class Hub {
228228
* user's actions prior to an error or crash.
229229
*
230230
* @param breadcrumb The breadcrumb to record.
231+
* @param hint May contain additional information about the original breadcrumb.
231232
*/
232-
public addBreadcrumb(breadcrumb: Breadcrumb): void {
233-
this.invokeClient('addBreadcrumb', breadcrumb);
233+
public addBreadcrumb(breadcrumb: Breadcrumb, hint?: SentryBreadcrumbHint): void {
234+
this.invokeClient('addBreadcrumb', breadcrumb, { ...hint });
234235
}
235236

236237
/**

0 commit comments

Comments
 (0)