Skip to content

feat: Integrations global handling #1646

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 17, 2018
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

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

## 4.1.1

Expand Down
1 change: 1 addition & 0 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"rollup-plugin-license": "^0.6.0",
"rollup-plugin-node-resolve": "^3.3.0",
"rollup-plugin-npm": "^2.0.0",
"rollup-plugin-shim": "^1.0.0",
"rollup-plugin-typescript2": "^0.13.0",
"rollup-plugin-uglify": "^3.0.0",
"sinon": "^5.0.3",
Expand Down
4 changes: 4 additions & 0 deletions packages/browser/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import uglify from 'rollup-plugin-uglify';
import resolve from 'rollup-plugin-node-resolve';
import typescript from 'rollup-plugin-typescript2';
import license from 'rollup-plugin-license';
import shim from 'rollup-plugin-shim';

const commitHash = require('child_process')
.execSync('git rev-parse --short HEAD', { encoding: 'utf-8' })
Expand All @@ -17,6 +18,9 @@ const bundleConfig = {
},
context: 'window',
plugins: [
shim({
domain: `export default {}`,
}),
typescript({
tsconfig: 'tsconfig.build.json',
tsconfigOverride: { compilerOptions: { declaration: false } },
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/backend.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BaseBackend, logger, Options, SentryError } from '@sentry/core';
import { BaseBackend, Options, SentryError } from '@sentry/core';
import { SentryEvent, SentryEventHint, SentryResponse, Severity, Status } from '@sentry/types';
import { isDOMError, isDOMException, isError, isErrorEvent, isPlainObject } from '@sentry/utils/is';
import { logger } from '@sentry/utils/logger';
import { supportsBeacon, supportsFetch } from '@sentry/utils/supports';
import { eventFromPlainObject, eventFromStacktrace, prepareFramesForEvent } from './parsers';
import { computeStackTrace } from './tracekit';
Expand Down
142 changes: 87 additions & 55 deletions packages/browser/src/integrations/breadcrumbs.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { API, getCurrentHub, logger } from '@sentry/core';
import { Integration, Severity } from '@sentry/types';
import { API, getCurrentHub } from '@sentry/core';
import { Breadcrumb, Integration, SentryBreadcrumbHint, Severity } from '@sentry/types';
import { isFunction, isString } from '@sentry/utils/is';
import { logger } from '@sentry/utils/logger';
import { getEventDescription, getGlobalObject, parseUrl } from '@sentry/utils/misc';
import { deserialize, fill } from '@sentry/utils/object';
import { includes, safeJoin } from '@sentry/utils/string';
import { supportsBeacon, supportsHistory, supportsNativeFetch } from '@sentry/utils/supports';
import { BrowserOptions } from '../backend';
import { BrowserClient } from '../client';
import { breadcrumbEventHandler, keypressEventHandler, wrap } from './helpers';

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

/** JSDoc */
function addSentryBreadcrumb(serializedData: string): void {
// There's always something that can go wrong with deserialization...
try {
const event: { [key: string]: any } = deserialize(serializedData);

getCurrentHub().addBreadcrumb(
{
category: 'sentry',
event_id: event.event_id,
level: event.level || Severity.fromString('error'),
message: getEventDescription(event),
},
{
event,
},
);
} catch (_oO) {
logger.error('Error while adding sentry type breadcrumb');
}
}

/** JSDoc */
interface BreadcrumbIntegrations {
beacon?: boolean;
Expand All @@ -61,6 +40,11 @@ export class Breadcrumbs implements Integration {
*/
public name: string = 'Breadcrumbs';

/**
* @inheritDoc
*/
public static id: string = 'Breadcrumbs';

/** JSDoc */
private readonly options: BreadcrumbIntegrations;

Expand All @@ -81,7 +65,7 @@ export class Breadcrumbs implements Integration {
}

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

// if Sentry key appears in URL, don't capture it as a request
// but rather as our own 'sentry' type breadcrumb
if (options.filterUrl && includes(url, options.filterUrl)) {
addSentryBreadcrumb(data);
return result;
const client = getCurrentHub().getClient() as BrowserClient;
const dsn = client && client.getDsn();
if (dsn) {
const filterUrl = new API(dsn).getStoreEndpoint();
// if Sentry key appears in URL, don't capture it as a request
// but rather as our own 'sentry' type breadcrumb
if (filterUrl && includes(url, filterUrl)) {
addSentryBreadcrumb(data);
return result;
}
}

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

getCurrentHub().addBreadcrumb(breadcrumbData, {
Breadcrumbs.addBreadcrumb(breadcrumbData, {
input: args,
result,
});
Expand Down Expand Up @@ -156,7 +145,7 @@ export class Breadcrumbs implements Integration {
}
}

getCurrentHub().addBreadcrumb(breadcrumbData, {
Breadcrumbs.addBreadcrumb(breadcrumbData, {
input: args,
level,
});
Expand All @@ -182,7 +171,7 @@ export class Breadcrumbs implements Integration {
}

/** JSDoc */
private instrumentFetch(options: { filterUrl?: string }): void {
private instrumentFetch(): void {
if (!supportsNativeFetch()) {
return;
}
Expand All @@ -208,13 +197,18 @@ export class Breadcrumbs implements Integration {
method = args[1].method;
}

// if Sentry key appears in URL, don't capture it as a request
// but rather as our own 'sentry' type breadcrumb
if (options.filterUrl && includes(url, options.filterUrl)) {
if (method === 'POST' && args[1] && args[1].body) {
addSentryBreadcrumb(args[1].body);
const client = getCurrentHub().getClient() as BrowserClient;
const dsn = client && client.getDsn();
if (dsn) {
const filterUrl = new API(dsn).getStoreEndpoint();
// if Sentry key appears in URL, don't capture it as a request
// but rather as our own 'sentry' type breadcrumb
if (filterUrl && includes(url, filterUrl)) {
if (method === 'POST' && args[1] && args[1].body) {
addSentryBreadcrumb(args[1].body);
}
return originalFetch.apply(global, args);
}
return originalFetch.apply(global, args);
}

const fetchData: {
Expand All @@ -230,7 +224,7 @@ export class Breadcrumbs implements Integration {
.apply(global, args)
.then((response: Response) => {
fetchData.status_code = response.status;
getCurrentHub().addBreadcrumb(
Breadcrumbs.addBreadcrumb(
{
category: 'fetch',
data: fetchData,
Expand All @@ -244,7 +238,7 @@ export class Breadcrumbs implements Integration {
return response;
})
.catch((error: Error) => {
getCurrentHub().addBreadcrumb(
Breadcrumbs.addBreadcrumb(
{
category: 'fetch',
data: fetchData,
Expand Down Expand Up @@ -295,7 +289,7 @@ export class Breadcrumbs implements Integration {
from = parsedFrom.relative;
}

getCurrentHub().addBreadcrumb({
Breadcrumbs.addBreadcrumb({
category: 'navigation',
data: {
from,
Expand Down Expand Up @@ -334,7 +328,7 @@ export class Breadcrumbs implements Integration {
}

/** JSDoc */
private instrumentXHR(options: { filterUrl?: string }): void {
private instrumentXHR(): void {
if (!('XMLHttpRequest' in global)) {
return;
}
Expand Down Expand Up @@ -369,11 +363,18 @@ export class Breadcrumbs implements Integration {
method: args[0],
url: args[1],
};
// if Sentry key appears in URL, don't capture it as a request
// but rather as our own 'sentry' type breadcrumb
if (isString(url) && (options.filterUrl && includes(url, options.filterUrl))) {
this.__sentry_own_request__ = true;

const client = getCurrentHub().getClient() as BrowserClient;
const dsn = client && client.getDsn();
if (dsn) {
const filterUrl = new API(dsn).getStoreEndpoint();
// if Sentry key appears in URL, don't capture it as a request
// but rather as our own 'sentry' type breadcrumb
if (isString(url) && (filterUrl && includes(url, filterUrl))) {
this.__sentry_own_request__ = true;
}
}

return originalOpen.apply(this, args);
},
);
Expand Down Expand Up @@ -404,7 +405,7 @@ export class Breadcrumbs implements Integration {
} catch (e) {
/* do nothing */
}
getCurrentHub().addBreadcrumb(
Breadcrumbs.addBreadcrumb(
{
category: 'xhr',
data: xhr.__sentry_xhr__,
Expand Down Expand Up @@ -447,6 +448,18 @@ export class Breadcrumbs implements Integration {
},
);
}

/**
* Helper that checks if integration is enabled on the client.
* @param breadcrumb Breadcrumb
* @param hint SentryBreadcrumbHint
*/
public static addBreadcrumb(breadcrumb: Breadcrumb, hint?: SentryBreadcrumbHint): void {
if (getCurrentHub().getIntegration(Breadcrumbs)) {
getCurrentHub().addBreadcrumb(breadcrumb, hint);
}
}

/**
* Instrument browser built-ins w/ breadcrumb capturing
* - Console API
Expand All @@ -455,26 +468,45 @@ export class Breadcrumbs implements Integration {
* - Fetch API
* - History API
*/
public install(options: BrowserOptions = {}): void {
const filterUrl = options.dsn && new API(options.dsn).getStoreEndpoint();

public setupOnce(): void {
if (this.options.console) {
this.instrumentConsole();
}
if (this.options.dom) {
this.instrumentDOM();
}
if (this.options.xhr) {
this.instrumentXHR({ filterUrl });
this.instrumentXHR();
}
if (this.options.fetch) {
this.instrumentFetch({ filterUrl });
this.instrumentFetch();
}
if (this.options.beacon) {
this.instrumentBeacon({ filterUrl });
this.instrumentBeacon();
}
if (this.options.history) {
this.instrumentHistory();
}
}
}

/** JSDoc */
function addSentryBreadcrumb(serializedData: string): void {
// There's always something that can go wrong with deserialization...
try {
const event: { [key: string]: any } = deserialize(serializedData);
Breadcrumbs.addBreadcrumb(
{
category: 'sentry',
event_id: event.event_id,
level: event.level || Severity.fromString('error'),
message: getEventDescription(event),
},
{
event,
},
);
} catch (_oO) {
logger.error('Error while adding sentry type breadcrumb');
}
}
15 changes: 12 additions & 3 deletions packages/browser/src/integrations/globalhandlers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getCurrentHub, logger } from '@sentry/core';
import { getCurrentHub } from '@sentry/core';
import { Integration, SentryEvent } from '@sentry/types';
import { logger } from '@sentry/utils/logger';
import { eventFromStacktrace } from '../parsers';
import {
installGlobalHandler,
Expand All @@ -22,6 +23,11 @@ export class GlobalHandlers implements Integration {
*/
public name: string = 'GlobalHandlers';

/**
* @inheritDoc
*/
public static id: string = 'GlobalHandlers';

/** JSDoc */
private readonly options: GlobalHandlersIntegrations;

Expand All @@ -36,7 +42,7 @@ export class GlobalHandlers implements Integration {
/**
* @inheritDoc
*/
public install(): void {
public setupOnce(): void {
subscribe((stack: TraceKitStackTrace, _: boolean, error: Error) => {
// TODO: use stack.context to get a valuable information from TraceKit, eg.
// [
Expand All @@ -55,7 +61,10 @@ export class GlobalHandlers implements Integration {
if (shouldIgnoreOnError()) {
return;
}
getCurrentHub().captureEvent(this.eventFromGlobalHandler(stack), { originalException: error, data: { stack } });
const self = getCurrentHub().getIntegration(GlobalHandlers);
if (self) {
getCurrentHub().captureEvent(self.eventFromGlobalHandler(stack), { originalException: error, data: { stack } });
}
});

if (this.options.onerror) {
Expand Down
17 changes: 14 additions & 3 deletions packages/browser/src/integrations/linkederrors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { configureScope } from '@sentry/core';
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core';
import { Integration, SentryEvent, SentryEventHint, SentryException } from '@sentry/types';
import { exceptionFromStacktrace } from '../parsers';
import { computeStackTrace } from '../tracekit';
Expand All @@ -20,6 +20,11 @@ export class LinkedErrors implements Integration {
*/
public readonly name: string = 'LinkedErrors';

/**
* @inheritDoc
*/
public static id: string = 'LinkedErrors';

/**
* @inheritDoc
*/
Expand All @@ -41,8 +46,14 @@ export class LinkedErrors implements Integration {
/**
* @inheritDoc
*/
public install(): void {
configureScope(scope => scope.addEventProcessor(this.handler.bind(this)));
public setupOnce(): void {
addGlobalEventProcessor(async (event: SentryEvent, hint?: SentryEventHint) => {
const self = getCurrentHub().getIntegration(LinkedErrors);
if (self) {
return self.handler(event, hint);
}
return event;
});
}

/**
Expand Down
Loading