Skip to content

Commit 81a3412

Browse files
authored
feat: Integrations global handling (#1646)
1 parent f48596e commit 81a3412

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1227
-760
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- [browser] fix: Make `addBreadcrumb` sync internally, `beforeBreadcrumb` is now only sync
66
- [browser] fix: Remove internal `console` guard in `beforeBreadcrumb`
7+
- [core]: Integrations now live on the `Client`. This means that when binding a new Client to the `Hub` the client
8+
itself can decide which integration should run.
79

810
## 4.1.1
911

packages/browser/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"rollup-plugin-license": "^0.6.0",
4343
"rollup-plugin-node-resolve": "^3.3.0",
4444
"rollup-plugin-npm": "^2.0.0",
45+
"rollup-plugin-shim": "^1.0.0",
4546
"rollup-plugin-typescript2": "^0.13.0",
4647
"rollup-plugin-uglify": "^3.0.0",
4748
"sinon": "^5.0.3",

packages/browser/rollup.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import uglify from 'rollup-plugin-uglify';
33
import resolve from 'rollup-plugin-node-resolve';
44
import typescript from 'rollup-plugin-typescript2';
55
import license from 'rollup-plugin-license';
6+
import shim from 'rollup-plugin-shim';
67

78
const commitHash = require('child_process')
89
.execSync('git rev-parse --short HEAD', { encoding: 'utf-8' })
@@ -17,6 +18,9 @@ const bundleConfig = {
1718
},
1819
context: 'window',
1920
plugins: [
21+
shim({
22+
domain: `export default {}`,
23+
}),
2024
typescript({
2125
tsconfig: 'tsconfig.build.json',
2226
tsconfigOverride: { compilerOptions: { declaration: false } },

packages/browser/src/backend.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { BaseBackend, logger, Options, SentryError } from '@sentry/core';
1+
import { BaseBackend, Options, SentryError } from '@sentry/core';
22
import { SentryEvent, SentryEventHint, SentryResponse, Severity, Status } from '@sentry/types';
33
import { isDOMError, isDOMException, isError, isErrorEvent, isPlainObject } from '@sentry/utils/is';
4+
import { logger } from '@sentry/utils/logger';
45
import { supportsBeacon, supportsFetch } from '@sentry/utils/supports';
56
import { eventFromPlainObject, eventFromStacktrace, prepareFramesForEvent } from './parsers';
67
import { computeStackTrace } from './tracekit';

packages/browser/src/integrations/breadcrumbs.ts

Lines changed: 87 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { API, getCurrentHub, logger } from '@sentry/core';
2-
import { Integration, Severity } from '@sentry/types';
1+
import { API, getCurrentHub } from '@sentry/core';
2+
import { Breadcrumb, Integration, SentryBreadcrumbHint, Severity } from '@sentry/types';
33
import { isFunction, isString } from '@sentry/utils/is';
4+
import { logger } from '@sentry/utils/logger';
45
import { getEventDescription, getGlobalObject, parseUrl } from '@sentry/utils/misc';
56
import { deserialize, fill } from '@sentry/utils/object';
67
import { includes, safeJoin } from '@sentry/utils/string';
78
import { supportsBeacon, supportsHistory, supportsNativeFetch } from '@sentry/utils/supports';
8-
import { BrowserOptions } from '../backend';
9+
import { BrowserClient } from '../client';
910
import { breadcrumbEventHandler, keypressEventHandler, wrap } from './helpers';
1011

1112
const global = getGlobalObject() as Window;
@@ -21,28 +22,6 @@ export interface SentryWrappedXMLHttpRequest extends XMLHttpRequest {
2122
};
2223
}
2324

24-
/** JSDoc */
25-
function addSentryBreadcrumb(serializedData: string): void {
26-
// There's always something that can go wrong with deserialization...
27-
try {
28-
const event: { [key: string]: any } = deserialize(serializedData);
29-
30-
getCurrentHub().addBreadcrumb(
31-
{
32-
category: 'sentry',
33-
event_id: event.event_id,
34-
level: event.level || Severity.fromString('error'),
35-
message: getEventDescription(event),
36-
},
37-
{
38-
event,
39-
},
40-
);
41-
} catch (_oO) {
42-
logger.error('Error while adding sentry type breadcrumb');
43-
}
44-
}
45-
4625
/** JSDoc */
4726
interface BreadcrumbIntegrations {
4827
beacon?: boolean;
@@ -61,6 +40,11 @@ export class Breadcrumbs implements Integration {
6140
*/
6241
public name: string = 'Breadcrumbs';
6342

43+
/**
44+
* @inheritDoc
45+
*/
46+
public static id: string = 'Breadcrumbs';
47+
6448
/** JSDoc */
6549
private readonly options: BreadcrumbIntegrations;
6650

@@ -81,7 +65,7 @@ export class Breadcrumbs implements Integration {
8165
}
8266

8367
/** JSDoc */
84-
private instrumentBeacon(options: { filterUrl?: string }): void {
68+
private instrumentBeacon(): void {
8569
if (!supportsBeacon()) {
8670
return;
8771
}
@@ -95,11 +79,16 @@ export class Breadcrumbs implements Integration {
9579
// https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API/Using_the_Beacon_API
9680
const result = originalBeaconFunction.apply(this, args);
9781

98-
// if Sentry key appears in URL, don't capture it as a request
99-
// but rather as our own 'sentry' type breadcrumb
100-
if (options.filterUrl && includes(url, options.filterUrl)) {
101-
addSentryBreadcrumb(data);
102-
return result;
82+
const client = getCurrentHub().getClient() as BrowserClient;
83+
const dsn = client && client.getDsn();
84+
if (dsn) {
85+
const filterUrl = new API(dsn).getStoreEndpoint();
86+
// if Sentry key appears in URL, don't capture it as a request
87+
// but rather as our own 'sentry' type breadcrumb
88+
if (filterUrl && includes(url, filterUrl)) {
89+
addSentryBreadcrumb(data);
90+
return result;
91+
}
10392
}
10493

10594
// What is wrong with you TypeScript...
@@ -113,7 +102,7 @@ export class Breadcrumbs implements Integration {
113102
breadcrumbData.level = Severity.Error;
114103
}
115104

116-
getCurrentHub().addBreadcrumb(breadcrumbData, {
105+
Breadcrumbs.addBreadcrumb(breadcrumbData, {
117106
input: args,
118107
result,
119108
});
@@ -156,7 +145,7 @@ export class Breadcrumbs implements Integration {
156145
}
157146
}
158147

159-
getCurrentHub().addBreadcrumb(breadcrumbData, {
148+
Breadcrumbs.addBreadcrumb(breadcrumbData, {
160149
input: args,
161150
level,
162151
});
@@ -182,7 +171,7 @@ export class Breadcrumbs implements Integration {
182171
}
183172

184173
/** JSDoc */
185-
private instrumentFetch(options: { filterUrl?: string }): void {
174+
private instrumentFetch(): void {
186175
if (!supportsNativeFetch()) {
187176
return;
188177
}
@@ -208,13 +197,18 @@ export class Breadcrumbs implements Integration {
208197
method = args[1].method;
209198
}
210199

211-
// if Sentry key appears in URL, don't capture it as a request
212-
// but rather as our own 'sentry' type breadcrumb
213-
if (options.filterUrl && includes(url, options.filterUrl)) {
214-
if (method === 'POST' && args[1] && args[1].body) {
215-
addSentryBreadcrumb(args[1].body);
200+
const client = getCurrentHub().getClient() as BrowserClient;
201+
const dsn = client && client.getDsn();
202+
if (dsn) {
203+
const filterUrl = new API(dsn).getStoreEndpoint();
204+
// if Sentry key appears in URL, don't capture it as a request
205+
// but rather as our own 'sentry' type breadcrumb
206+
if (filterUrl && includes(url, filterUrl)) {
207+
if (method === 'POST' && args[1] && args[1].body) {
208+
addSentryBreadcrumb(args[1].body);
209+
}
210+
return originalFetch.apply(global, args);
216211
}
217-
return originalFetch.apply(global, args);
218212
}
219213

220214
const fetchData: {
@@ -230,7 +224,7 @@ export class Breadcrumbs implements Integration {
230224
.apply(global, args)
231225
.then((response: Response) => {
232226
fetchData.status_code = response.status;
233-
getCurrentHub().addBreadcrumb(
227+
Breadcrumbs.addBreadcrumb(
234228
{
235229
category: 'fetch',
236230
data: fetchData,
@@ -244,7 +238,7 @@ export class Breadcrumbs implements Integration {
244238
return response;
245239
})
246240
.catch((error: Error) => {
247-
getCurrentHub().addBreadcrumb(
241+
Breadcrumbs.addBreadcrumb(
248242
{
249243
category: 'fetch',
250244
data: fetchData,
@@ -295,7 +289,7 @@ export class Breadcrumbs implements Integration {
295289
from = parsedFrom.relative;
296290
}
297291

298-
getCurrentHub().addBreadcrumb({
292+
Breadcrumbs.addBreadcrumb({
299293
category: 'navigation',
300294
data: {
301295
from,
@@ -334,7 +328,7 @@ export class Breadcrumbs implements Integration {
334328
}
335329

336330
/** JSDoc */
337-
private instrumentXHR(options: { filterUrl?: string }): void {
331+
private instrumentXHR(): void {
338332
if (!('XMLHttpRequest' in global)) {
339333
return;
340334
}
@@ -369,11 +363,18 @@ export class Breadcrumbs implements Integration {
369363
method: args[0],
370364
url: args[1],
371365
};
372-
// if Sentry key appears in URL, don't capture it as a request
373-
// but rather as our own 'sentry' type breadcrumb
374-
if (isString(url) && (options.filterUrl && includes(url, options.filterUrl))) {
375-
this.__sentry_own_request__ = true;
366+
367+
const client = getCurrentHub().getClient() as BrowserClient;
368+
const dsn = client && client.getDsn();
369+
if (dsn) {
370+
const filterUrl = new API(dsn).getStoreEndpoint();
371+
// if Sentry key appears in URL, don't capture it as a request
372+
// but rather as our own 'sentry' type breadcrumb
373+
if (isString(url) && (filterUrl && includes(url, filterUrl))) {
374+
this.__sentry_own_request__ = true;
375+
}
376376
}
377+
377378
return originalOpen.apply(this, args);
378379
},
379380
);
@@ -404,7 +405,7 @@ export class Breadcrumbs implements Integration {
404405
} catch (e) {
405406
/* do nothing */
406407
}
407-
getCurrentHub().addBreadcrumb(
408+
Breadcrumbs.addBreadcrumb(
408409
{
409410
category: 'xhr',
410411
data: xhr.__sentry_xhr__,
@@ -447,6 +448,18 @@ export class Breadcrumbs implements Integration {
447448
},
448449
);
449450
}
451+
452+
/**
453+
* Helper that checks if integration is enabled on the client.
454+
* @param breadcrumb Breadcrumb
455+
* @param hint SentryBreadcrumbHint
456+
*/
457+
public static addBreadcrumb(breadcrumb: Breadcrumb, hint?: SentryBreadcrumbHint): void {
458+
if (getCurrentHub().getIntegration(Breadcrumbs)) {
459+
getCurrentHub().addBreadcrumb(breadcrumb, hint);
460+
}
461+
}
462+
450463
/**
451464
* Instrument browser built-ins w/ breadcrumb capturing
452465
* - Console API
@@ -455,26 +468,45 @@ export class Breadcrumbs implements Integration {
455468
* - Fetch API
456469
* - History API
457470
*/
458-
public install(options: BrowserOptions = {}): void {
459-
const filterUrl = options.dsn && new API(options.dsn).getStoreEndpoint();
460-
471+
public setupOnce(): void {
461472
if (this.options.console) {
462473
this.instrumentConsole();
463474
}
464475
if (this.options.dom) {
465476
this.instrumentDOM();
466477
}
467478
if (this.options.xhr) {
468-
this.instrumentXHR({ filterUrl });
479+
this.instrumentXHR();
469480
}
470481
if (this.options.fetch) {
471-
this.instrumentFetch({ filterUrl });
482+
this.instrumentFetch();
472483
}
473484
if (this.options.beacon) {
474-
this.instrumentBeacon({ filterUrl });
485+
this.instrumentBeacon();
475486
}
476487
if (this.options.history) {
477488
this.instrumentHistory();
478489
}
479490
}
480491
}
492+
493+
/** JSDoc */
494+
function addSentryBreadcrumb(serializedData: string): void {
495+
// There's always something that can go wrong with deserialization...
496+
try {
497+
const event: { [key: string]: any } = deserialize(serializedData);
498+
Breadcrumbs.addBreadcrumb(
499+
{
500+
category: 'sentry',
501+
event_id: event.event_id,
502+
level: event.level || Severity.fromString('error'),
503+
message: getEventDescription(event),
504+
},
505+
{
506+
event,
507+
},
508+
);
509+
} catch (_oO) {
510+
logger.error('Error while adding sentry type breadcrumb');
511+
}
512+
}

packages/browser/src/integrations/globalhandlers.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { getCurrentHub, logger } from '@sentry/core';
1+
import { getCurrentHub } from '@sentry/core';
22
import { Integration, SentryEvent } from '@sentry/types';
3+
import { logger } from '@sentry/utils/logger';
34
import { eventFromStacktrace } from '../parsers';
45
import {
56
installGlobalHandler,
@@ -22,6 +23,11 @@ export class GlobalHandlers implements Integration {
2223
*/
2324
public name: string = 'GlobalHandlers';
2425

26+
/**
27+
* @inheritDoc
28+
*/
29+
public static id: string = 'GlobalHandlers';
30+
2531
/** JSDoc */
2632
private readonly options: GlobalHandlersIntegrations;
2733

@@ -36,7 +42,7 @@ export class GlobalHandlers implements Integration {
3642
/**
3743
* @inheritDoc
3844
*/
39-
public install(): void {
45+
public setupOnce(): void {
4046
subscribe((stack: TraceKitStackTrace, _: boolean, error: Error) => {
4147
// TODO: use stack.context to get a valuable information from TraceKit, eg.
4248
// [
@@ -55,7 +61,10 @@ export class GlobalHandlers implements Integration {
5561
if (shouldIgnoreOnError()) {
5662
return;
5763
}
58-
getCurrentHub().captureEvent(this.eventFromGlobalHandler(stack), { originalException: error, data: { stack } });
64+
const self = getCurrentHub().getIntegration(GlobalHandlers);
65+
if (self) {
66+
getCurrentHub().captureEvent(self.eventFromGlobalHandler(stack), { originalException: error, data: { stack } });
67+
}
5968
});
6069

6170
if (this.options.onerror) {

packages/browser/src/integrations/linkederrors.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { configureScope } from '@sentry/core';
1+
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core';
22
import { Integration, SentryEvent, SentryEventHint, SentryException } from '@sentry/types';
33
import { exceptionFromStacktrace } from '../parsers';
44
import { computeStackTrace } from '../tracekit';
@@ -20,6 +20,11 @@ export class LinkedErrors implements Integration {
2020
*/
2121
public readonly name: string = 'LinkedErrors';
2222

23+
/**
24+
* @inheritDoc
25+
*/
26+
public static id: string = 'LinkedErrors';
27+
2328
/**
2429
* @inheritDoc
2530
*/
@@ -41,8 +46,14 @@ export class LinkedErrors implements Integration {
4146
/**
4247
* @inheritDoc
4348
*/
44-
public install(): void {
45-
configureScope(scope => scope.addEventProcessor(this.handler.bind(this)));
49+
public setupOnce(): void {
50+
addGlobalEventProcessor(async (event: SentryEvent, hint?: SentryEventHint) => {
51+
const self = getCurrentHub().getIntegration(LinkedErrors);
52+
if (self) {
53+
return self.handler(event, hint);
54+
}
55+
return event;
56+
});
4657
}
4758

4859
/**

0 commit comments

Comments
 (0)