diff --git a/.craft.yml b/.craft.yml
index 79a363521fd5..2eaad6763309 100644
--- a/.craft.yml
+++ b/.craft.yml
@@ -9,7 +9,7 @@ targets:
- name: github
includeNames: /^sentry-.*$/
- name: gcs
- includeNames: /^bundle\..*$/
+ includeNames: /*\.js.*$/
bucket: sentry-js-sdk
paths:
- path: /{{version}}/
diff --git a/MIGRATION.md b/MIGRATION.md
new file mode 100644
index 000000000000..24255a79093e
--- /dev/null
+++ b/MIGRATION.md
@@ -0,0 +1,132 @@
+# Upgrading from 4.x to 5.x
+
+In this version upgrade, there are a few breaking changes. This guide should help you update your code accordingly.
+
+## Integrations
+
+We moved optional integrations into their own package, called `@sentry/integrations`. Also, we made a few default
+integrations now optional. This is probably the biggest breaking change regarding the upgrade.
+
+Integrations that are now opt-in and were default before:
+
+- Dedupe (responsible for sending the same error only once)
+- ExtraErrorData (responsible for doing fancy magic, trying to extract data out of the error object using any
+ non-standard keys)
+
+Integrations that were pluggable/optional before, that also live in this package:
+
+- Angular (browser)
+- Debug (browser/node)
+- Ember (browser)
+- ReportingObserver (browser)
+- RewriteFrames (browser/node)
+- Transaction (browser/node)
+- Vue (browser)
+
+### How to use `@sentry/integrations`?
+
+Lets start with the approach if you install `@sentry/browser` / `@sentry/electron` with `npm` or `yarn`.
+
+Given you have a `Vue` application running, in order to use the `Vue` integration you need to do the following:
+
+With `4.x`:
+
+```js
+import * as Sentry from '@sentry/browser';
+
+Sentry.init({
+ dsn: '___PUBLIC_DSN___',
+ integrations: [
+ new Sentry.Integrations.Vue({
+ Vue,
+ attachProps: true,
+ }),
+ ],
+});
+```
+
+With `5.x` you need to install `@sentry/integrations` and change the import.
+
+```js
+import * as Sentry from '@sentry/browser';
+import * as Integrations from '@sentry/integrations';
+
+Sentry.init({
+ dsn: '___PUBLIC_DSN___',
+ integrations: [
+ new Integrations.Vue({
+ Vue,
+ attachProps: true,
+ }),
+ ],
+});
+```
+
+In case you are using the CDN version or the Loader, we provide a standalone file for every integration, you can use it
+like this:
+
+```html
+
+
+
+
+
+
+
+
+```
+
+## New Scope functions
+
+We realized how annoying it is to set a whole object using `setExtra`, that's why there are now a few new methods on the
+`Scope`.
+
+```typescript
+setTags(tags: { [key: string]: string }): this;
+setExtras(extras: { [key: string]: any }): this;
+clearBreadcrumbs(): this;
+```
+
+So you can do this now:
+
+```js
+// New in 5.x setExtras
+Sentry.withScope(scope => {
+ scope.setExtras(errorInfo);
+ Sentry.captureException(error);
+});
+
+// vs. 4.x
+Sentry.withScope(scope => {
+ Object.keys(errorInfo).forEach(key => {
+ scope.setExtra(key, errorInfo[key]);
+ });
+ Sentry.captureException(error);
+});
+```
+
+## Less Async API
+
+We removed a lot of the internal async code since in certain situations it generated a lot of memory pressure. This
+really only affects you if you where either using the `BrowserClient` or `NodeClient` directly.
+
+So all the `capture*` functions now instead of returning `Promise` return `string | undefined`. `string` in
+this case is the `event_id`, in case the event will not be sent because of filtering it will return `undefined`.
+
+## `close` vs. `flush`
+
+In `4.x` we had both `close` and `flush` on the `Client` draining the internal queue of events, helpful when you were
+using `@sentry/node` on a serverless infrastructure.
+
+Now `close` and `flush` work similar, with the difference that if you call `close` in addition to returing a `Promise`
+that you can await it also **disables** the client so it will not send any future events.
diff --git a/Makefile b/Makefile
index ca6552f8b761..2c3f5e968fe7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,3 @@
-bump:
- yarn lerna version --exact --no-git-tag-version --no-push
-.PHONY: bump
-
prepare-release:
yarn clean
yarn build
@@ -9,22 +5,6 @@ prepare-release:
yarn test
.PHONY: prepare-release
-publish-npm:
- cd packages/browser; npm publish
- cd packages/core; npm publish
- cd packages/hub; npm publish
- cd packages/integrations; npm publish
- cd packages/minimal; npm publish
- cd packages/node; npm publish
- # cd packages/types; npm publish
- # cd packages/typescript; npm publish
- cd packages/utils; npm publish
-.PHONY: publish-npm
-
-publish-cdn:
- node scripts/browser-upload-cdn.js
-.PHONY: publish-cdn
-
build-docs:
rm -rf ./docs
yarn typedoc --options ./typedoc.js
@@ -39,6 +19,3 @@ publish-docs: build-docs
git push origin gh-pages
git checkout master
.PHONY: publish-docs
-
-release: bump prepare-release publish-npm publish-cdn
-.PHONY: release
diff --git a/packages/browser/examples/app.js b/packages/browser/examples/app.js
index a01f093ea725..af5e4b6cae6b 100644
--- a/packages/browser/examples/app.js
+++ b/packages/browser/examples/app.js
@@ -5,8 +5,8 @@ class HappyIntegration {
}
setupOnce() {
- Sentry.addGlobalEventProcessor(async event => {
- const self = getCurrentHub().getIntegration(HappyIntegration);
+ Sentry.addGlobalEventProcessor(event => {
+ const self = Sentry.getCurrentHub().getIntegration(HappyIntegration);
// Run the integration ONLY when it was installed on the current Hub
if (self) {
if (event.message === 'Happy Message') {
@@ -19,7 +19,7 @@ class HappyIntegration {
}
class HappyTransport extends Sentry.Transports.BaseTransport {
- captureEvent(event) {
+ sendEvent(event) {
console.log(
`This is the place where you'd implement your own sending logic. It'd get url: ${this.url} and an event itself:`,
event,
@@ -36,11 +36,11 @@ Sentry.init({
dsn: 'https://363a337c11a64611be4845ad6e24f3ac@sentry.io/297378',
// An array of strings or regexps that'll be used to ignore specific errors based on their type/message
ignoreErrors: [/PickleRick_\d\d/, 'RangeError'],
- // // An array of strings or regexps that'll be used to ignore specific errors based on their origin url
+ // An array of strings or regexps that'll be used to ignore specific errors based on their origin url
blacklistUrls: ['external-lib.js'],
- // // An array of strings or regexps that'll be used to allow specific errors based on their origin url
+ // An array of strings or regexps that'll be used to allow specific errors based on their origin url
whitelistUrls: ['http://localhost:5000', 'https://browser.sentry-cdn'],
- // // Debug mode with valuable initialization/lifecycle informations.
+ // Debug mode with valuable initialization/lifecycle informations.
debug: true,
// Whether SDK should be enabled or not.
enabled: true,
diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts
index 5a8269b90e77..9c7f44c6cd8a 100644
--- a/packages/browser/src/index.ts
+++ b/packages/browser/src/index.ts
@@ -33,11 +33,22 @@ export { defaultIntegrations, forceLoad, init, lastEventId, onLoad, showReportDi
export { SDK_NAME, SDK_VERSION } from './version';
import { Integrations as CoreIntegrations } from '@sentry/core';
+import { getGlobalObject } from '@sentry/utils/misc';
import * as BrowserIntegrations from './integrations';
import * as Transports from './transports';
+let windowIntegrations = {};
+
+// tslint:disable: no-unsafe-any
+const _window = getGlobalObject() as any;
+if (_window.Sentry && _window.Sentry.Integrations) {
+ windowIntegrations = _window.Sentry.Integrations;
+}
+// tslint:enable: no-unsafe-any
+
const INTEGRATIONS = {
+ ...windowIntegrations,
...CoreIntegrations,
...BrowserIntegrations,
};
diff --git a/packages/browser/src/loader.js b/packages/browser/src/loader.js
index a91c5288d203..aea7e90ba115 100644
--- a/packages/browser/src/loader.js
+++ b/packages/browser/src/loader.js
@@ -150,26 +150,27 @@
}
}
- // We don't want to _window.Sentry = _window.Sentry || { ... } since we want to make sure
- // that the first Sentry "instance" is our with onLoad
- _window[_namespace] = {
- onLoad: function (callback) {
- onLoadCallbacks.push(callback);
- if (lazy && !forceLoad) {
- return;
- }
- injectSdk(onLoadCallbacks);
- },
- forceLoad: function() {
- forceLoad = true;
- if (lazy) {
- setTimeout(function() {
- injectSdk(onLoadCallbacks);
- });
- }
+ // We make sure we do not overwrite window.Sentry since there could be already integrations in there
+ _window[_namespace] = _window[_namespace] || {};
+
+ _window[_namespace].onLoad = function (callback) {
+ onLoadCallbacks.push(callback);
+ if (lazy && !forceLoad) {
+ return;
}
+ injectSdk(onLoadCallbacks);
};
+ _window[_namespace].forceLoad = function() {
+ forceLoad = true;
+ if (lazy) {
+ setTimeout(function() {
+ injectSdk(onLoadCallbacks);
+ });
+ }
+ };
+
+
[
'init',
'addBreadcrumb',
diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts
index f2981a8d1a2f..ed74871e184f 100644
--- a/packages/core/src/baseclient.ts
+++ b/packages/core/src/baseclient.ts
@@ -1,5 +1,5 @@
import { Scope } from '@sentry/hub';
-import { Client, Event, EventHint, Integration, IntegrationClass, Options, Severity } from '@sentry/types';
+import { Client, Event, EventHint, Integration, IntegrationClass, Options, SdkInfo, Severity } from '@sentry/types';
import { isPrimitive, isThenable } from '@sentry/utils/is';
import { logger } from '@sentry/utils/logger';
import { uuid4 } from '@sentry/utils/misc';
@@ -278,6 +278,8 @@ export abstract class BaseClient implement
prepared.event_id = uuid4();
}
+ this._addIntegrations(prepared.sdk);
+
// We prepare the result here with a resolved Event.
let result = SyncPromise.resolve(prepared);
@@ -291,6 +293,17 @@ export abstract class BaseClient implement
return result;
}
+ /**
+ * This function adds all used integrations to the SDK info in the event.
+ * @param sdkInfo The sdkInfo of the event that will be filled with all integrations.
+ */
+ protected _addIntegrations(sdkInfo?: SdkInfo): void {
+ const integrationsArray = Object.keys(this._integrations);
+ if (sdkInfo && integrationsArray.length > 0) {
+ sdkInfo.integrations = integrationsArray;
+ }
+ }
+
/**
* Processes an event (either error or message) and sends it to Sentry.
*
diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts
index 3c03718f9464..c2fe2a02be19 100644
--- a/packages/core/src/integration.ts
+++ b/packages/core/src/integration.ts
@@ -21,19 +21,19 @@ export function getIntegrationsToSetup(options: Options): Integration[] {
// Leave only unique default integrations, that were not overridden with provided user integrations
defaultIntegrations.forEach(defaultIntegration => {
if (
- userIntegrationsNames.indexOf(getIntegrationName(defaultIntegration)) === -1 &&
- pickedIntegrationsNames.indexOf(getIntegrationName(defaultIntegration)) === -1
+ userIntegrationsNames.indexOf(defaultIntegration.name) === -1 &&
+ pickedIntegrationsNames.indexOf(defaultIntegration.name) === -1
) {
integrations.push(defaultIntegration);
- pickedIntegrationsNames.push(getIntegrationName(defaultIntegration));
+ pickedIntegrationsNames.push(defaultIntegration.name);
}
});
// Don't add same user integration twice
userIntegrations.forEach(userIntegration => {
- if (pickedIntegrationsNames.indexOf(getIntegrationName(userIntegration)) === -1) {
+ if (pickedIntegrationsNames.indexOf(userIntegration.name) === -1) {
integrations.push(userIntegration);
- pickedIntegrationsNames.push(getIntegrationName(userIntegration));
+ pickedIntegrationsNames.push(userIntegration.name);
}
});
} else if (typeof userIntegrations === 'function') {
@@ -48,12 +48,12 @@ export function getIntegrationsToSetup(options: Options): Integration[] {
/** Setup given integration */
export function setupIntegration(integration: Integration): void {
- if (installedIntegrations.indexOf(getIntegrationName(integration)) !== -1) {
+ if (installedIntegrations.indexOf(integration.name) !== -1) {
return;
}
integration.setupOnce(addGlobalEventProcessor, getCurrentHub);
- installedIntegrations.push(getIntegrationName(integration));
- logger.log(`Integration installed: ${getIntegrationName(integration)}`);
+ installedIntegrations.push(integration.name);
+ logger.log(`Integration installed: ${integration.name}`);
}
/**
@@ -65,20 +65,8 @@ export function setupIntegration(integration: Integration): void {
export function setupIntegrations(options: O): IntegrationIndex {
const integrations: IntegrationIndex = {};
getIntegrationsToSetup(options).forEach(integration => {
- integrations[getIntegrationName(integration)] = integration;
+ integrations[integration.name] = integration;
setupIntegration(integration);
});
return integrations;
}
-
-/**
- * Returns the integration static id.
- * @param integration Integration to retrieve id
- */
-function getIntegrationName(integration: Integration): string {
- /**
- * @depracted
- */
- // tslint:disable-next-line:no-unsafe-any
- return (integration as any).constructor.id || integration.name;
-}
diff --git a/packages/integrations/package.json b/packages/integrations/package.json
index 904006ed4be4..e4e503a943b4 100644
--- a/packages/integrations/package.json
+++ b/packages/integrations/package.json
@@ -12,6 +12,10 @@
"publishConfig": {
"access": "public"
},
+ "main": "dist/index.js",
+ "module": "esm/index.js",
+ "browser": "dist/index.js",
+ "types": "dist/index.d.ts",
"dependencies": {
"@sentry/types": "5.0.0-rc.3",
"@sentry/utils": "5.0.0-rc.3"
@@ -33,7 +37,7 @@
"typescript": "^3.3.3333"
},
"scripts": {
- "build": "run-p build:es5 build:bundle",
+ "build": "run-p build:es5 build:esm build:bundle",
"build:es5": "tsc -p tsconfig.build.json",
"build:esm": "tsc -p tsconfig.esm.json",
"build:watch": "run-p build:watch:es5 build:watch:esm",
@@ -41,6 +45,7 @@
"build:watch:esm": "tsc -p tsconfig.esm.json -w --preserveWatchOutput",
"build:bundle": "rollup --config",
"clean": "rimraf dist coverage *.js *.js.map *.d.ts",
+ "link:yarn": "yarn link",
"lint": "run-s lint:prettier lint:tslint",
"lint:prettier": "prettier-check \"{src,test}/**/*.ts\"",
"lint:tslint": "tslint -t stylish -p .",
diff --git a/packages/integrations/rollup.config.js b/packages/integrations/rollup.config.js
index 11df82a481b5..e431740e6904 100644
--- a/packages/integrations/rollup.config.js
+++ b/packages/integrations/rollup.config.js
@@ -43,49 +43,47 @@ const plugins = [
commonjs(),
];
-function toPascalCase(string) {
- return `${string}`
- .replace(new RegExp(/[-_]+/, 'g'), ' ')
- .replace(new RegExp(/[^\w\s]/, 'g'), '')
- .replace(new RegExp(/\s+(.)(\w+)/, 'g'), ($1, $2, $3) => `${$2.toUpperCase() + $3.toLowerCase()}`)
- .replace(new RegExp(/\s/, 'g'), '')
- .replace(new RegExp(/\w/), s => s.toUpperCase());
-}
-
-function mergeIntoSentry(name) {
+function mergeIntoSentry() {
return `
- if (window.Sentry && window.Sentry.Integrations) {
- window.Sentry.Integrations['${name}'] = exports.${name};
- } else {
- if ((typeof __SENTRY_INTEGRATIONS_LOG === 'undefined')) {
- console.warn('Sentry.Integrations is not defined, make sure you included this script after the SDK.');
- console.warn('In case you were using the loader, we added the Integration is now available under SentryIntegrations.${name}');
- console.warn('To disable these warning set __SENTRY_INTEGRATIONS_LOG = true; somewhere before loading this script.');
- }
- window.SentryIntegrations = window.SentryIntegrations || {};
- window.SentryIntegrations['${name}'] = exports.${name};
- }
+ __window.Sentry = __window.Sentry || {};
+ __window.Sentry.Integrations = __window.Sentry.Integrations || {};
+ Object.assign(__window.Sentry.Integrations, exports);
`;
}
function allIntegrations() {
- return fs.readdirSync('./src').filter(file => file != 'modules.ts');
+ return fs.readdirSync('./src').filter(file => file != 'index.ts');
}
function loadAllIntegrations() {
- return allIntegrations().map(file => ({
- input: `src/${file}`,
- output: {
- banner: '(function (window) {',
- intro: 'var exports = {};',
- footer: '}(window));',
- outro: mergeIntoSentry(toPascalCase(file.replace('.ts', ''))),
- file: `build/${file.replace('.ts', '.js')}`,
- format: 'cjs',
- sourcemap: true,
+ const builds = [];
+ [
+ {
+ extension: '.js',
+ plugins,
},
- plugins,
- }));
+ {
+ extension: '.min.js',
+ plugins: [...plugins, terserInstance],
+ },
+ ].forEach(build => {
+ builds.push(
+ ...allIntegrations().map(file => ({
+ input: `src/${file}`,
+ output: {
+ banner: '(function (__window) {',
+ intro: 'var exports = {};',
+ outro: mergeIntoSentry(),
+ footer: '}(window));',
+ file: `build/${file.replace('.ts', build.extension)}`,
+ format: 'cjs',
+ sourcemap: true,
+ },
+ plugins: build.plugins,
+ })),
+ );
+ });
+ return builds;
}
export default loadAllIntegrations();
diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts
new file mode 100644
index 000000000000..f9410442d8e4
--- /dev/null
+++ b/packages/integrations/src/index.ts
@@ -0,0 +1,8 @@
+export { Angular } from './angular';
+export { Debug } from './debug';
+export { Ember } from './ember';
+export { ExtraErrorData } from './extraerrordata';
+export { ReportingObserver } from './reportingobserver';
+export { RewriteFrames } from './rewriteframes';
+export { Transaction } from './transaction';
+export { Vue } from './vue';
diff --git a/packages/node/src/integrations/index.ts b/packages/node/src/integrations/index.ts
index 18f64fd5b998..772c0e8f3f86 100644
--- a/packages/node/src/integrations/index.ts
+++ b/packages/node/src/integrations/index.ts
@@ -3,3 +3,4 @@ export { Http } from './http';
export { OnUncaughtException } from './onuncaughtexception';
export { OnUnhandledRejection } from './onunhandledrejection';
export { LinkedErrors } from './linkederrors';
+export { Modules } from './modules';
diff --git a/packages/integrations/src/modules.ts b/packages/node/src/integrations/modules.ts
similarity index 100%
rename from packages/integrations/src/modules.ts
rename to packages/node/src/integrations/modules.ts
diff --git a/scripts/browser-upload-cdn.js b/scripts/browser-upload-cdn.js
deleted file mode 100755
index 2e25ecca4784..000000000000
--- a/scripts/browser-upload-cdn.js
+++ /dev/null
@@ -1,94 +0,0 @@
-'use strict';
-
-const Storage = require('@google-cloud/storage');
-const path = require('path');
-const os = require('os');
-const fs = require('fs');
-
-const bundleFilesRegex = /^bundle.*\.js.*$/;
-const rootDir = path.dirname(__dirname);
-const browserDir = path.join(rootDir, 'packages', 'browser');
-const browserBuildDir = path.join(browserDir, 'build');
-
-/** Return full paths of files to upload */
-function findFiles() {
- const bundleFiles = fs
- .readdirSync(browserBuildDir)
- .filter(filename => filename.match(bundleFilesRegex))
- .map(filename => path.join(browserBuildDir, filename));
- return bundleFiles;
-}
-
-/** Upload sentry-browser bundles to a GCS bucket */
-async function uploadFiles() {
- const gcsConfigPath =
- process.env.BROWSER_GOOGLE_APPLICATION_CREDENTIALS ||
- path.join(os.homedir(), '.gcs', 'sentry-browser-sdk.json');
- console.log(`Reading GCS configuration from "${gcsConfigPath}"...`);
-
- const gcsConfig = fs.existsSync(gcsConfigPath)
- ? JSON.parse(fs.readFileSync(gcsConfigPath))
- : undefined;
-
- if (!gcsConfig) {
- console.error(
- 'Google Storage configuration (service account key) not found.\n' +
- `Place it at ${gcsConfigPath} or use the environment variable ` +
- '(BROWSER_GOOGLE_APPLICATION_CREDENTIALS) to specify the path.',
- );
- process.exit(1);
- }
-
- const projectId =
- process.env.BROWSER_GOOGLE_PROJECT_ID || gcsConfig.project_id;
- if (!projectId) {
- console.error('Google project ID not found.');
- process.exit(1);
- }
-
- const bucketName =
- process.env.BROWSER_GOOGLE_BUCKET_NAME || gcsConfig.bucket_name;
- if (!bucketName) {
- console.error('Bucket name not found in the configuration.');
- process.exit(1);
- }
-
- const bundleFiles = findFiles();
- if (!bundleFiles.length) {
- console.error('Error: no files to upload!');
- process.exit(1);
- }
-
- const browserPackageJson = path.join(browserDir, 'package.json');
- const version =
- JSON.parse(fs.readFileSync(browserPackageJson)).version || 'unreleased';
-
- const storage = new Storage({
- projectId,
- credentials: gcsConfig,
- });
-
- const bucket = storage.bucket(bucketName);
- const cacheAge = 31536000; // 1 year
-
- await Promise.all(
- bundleFiles.map(async filepath => {
- const destination = path.join(version, path.basename(filepath));
- const options = {
- gzip: true,
- destination: destination,
- metadata: {
- cacheControl: `public, max-age=${cacheAge}`,
- },
- };
- await bucket.upload(filepath, options);
- console.log(`Uploaded "${destination}"`);
- }),
- );
- console.log('Upload complete.');
-}
-
-uploadFiles().catch(error => {
- console.error('Error occurred:', error);
- process.exit(1);
-});
diff --git a/scripts/pack-and-upload.sh b/scripts/pack-and-upload.sh
index 734ad1d46f62..6b3f1b0ff54e 100755
--- a/scripts/pack-and-upload.sh
+++ b/scripts/pack-and-upload.sh
@@ -15,6 +15,8 @@ node scripts/package-and-upload-to-zeus.js
# Upload "sentry-browser" bundles
zeus upload -t "application/javascript" ./packages/browser/build/bundle*
+# Upload "integrations" bundles
+zeus upload -t "application/javascript" ./packages/integrations/build/*
# Upload docs
make build-docs
diff --git a/scripts/test.sh b/scripts/test.sh
index cbb55d0ff569..ea976b67fd9e 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -4,8 +4,8 @@ set -e
# We need this check to skip engines check for typescript-tslint-plugin package
if [[ "$(cut -d. -f1 <<< "$TRAVIS_NODE_VERSION")" -le 6 ]]; then
yarn install --ignore-engines
- yarn build --ignore="@sentry/browser"
- yarn test --ignore="@sentry/browser" # latest version of karma doesn't run on node 6
+ yarn build --ignore="@sentry/browser" --ignore="@sentry/integrations"
+ yarn test --ignore="@sentry/browser" --ignore="@sentry/integrations" # latest version of karma doesn't run on node 6
else
yarn install
yarn build