From 06d6bd87971b22dcaba99b03e1f885158c7dd66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Mon, 18 Jan 2021 15:20:07 +0100 Subject: [PATCH] feat: Allow for attaching metadata and pass it to the API and transports (#3177) * feat: Allow for attaching metadata and pass it to the API and transports --- packages/angular/src/errorhandler.ts | 19 ---- packages/angular/src/index.ts | 2 + packages/angular/src/sdk.ts | 19 ++++ packages/browser/package.json | 1 - packages/browser/src/backend.ts | 1 + packages/browser/src/client.ts | 14 --- packages/browser/src/exports.ts | 3 +- packages/browser/src/sdk.ts | 14 ++- packages/browser/src/transports/base.ts | 2 +- packages/browser/src/version.ts | 2 +- packages/core/package.json | 3 +- packages/core/src/api.ts | 11 ++- packages/core/src/index.ts | 1 + packages/core/src/request.ts | 42 ++++++++- packages/core/src/version.ts | 1 + packages/core/test/lib/request.test.ts | 92 +++++++++++++++++--- packages/ember/addon/index.ts | 35 +++----- packages/gatsby/gatsby-browser.js | 27 +++--- packages/gatsby/test/gatsby-browser.test.ts | 34 ++------ packages/node/package.json | 1 - packages/node/src/backend.ts | 1 + packages/node/src/client.ts | 15 ---- packages/node/src/index.ts | 3 +- packages/node/src/sdk.ts | 14 ++- packages/node/src/transports/base.ts | 6 +- packages/node/src/version.ts | 2 +- packages/react/src/index.ts | 31 +------ packages/react/src/sdk.ts | 19 ++++ packages/serverless/src/awslambda.ts | 16 +++- packages/serverless/src/gcpfunction/index.ts | 16 +++- packages/serverless/src/utils.ts | 28 ++---- packages/serverless/test/awslambda.test.ts | 87 ++++++------------ packages/serverless/test/gcpfunction.test.ts | 87 ++++++------------ packages/types/src/index.ts | 1 + packages/types/src/options.ts | 21 +++-- packages/types/src/sdkinfo.ts | 1 - packages/types/src/sdkmetadata.ts | 5 ++ packages/types/src/transport.ts | 6 ++ packages/vue/src/eventprocessor.ts | 25 ------ packages/vue/src/sdk.ts | 35 ++++---- 40 files changed, 368 insertions(+), 375 deletions(-) create mode 100644 packages/angular/src/sdk.ts create mode 100644 packages/core/src/version.ts create mode 100644 packages/react/src/sdk.ts create mode 100644 packages/types/src/sdkmetadata.ts delete mode 100644 packages/vue/src/eventprocessor.ts diff --git a/packages/angular/src/errorhandler.ts b/packages/angular/src/errorhandler.ts index 5b608d9bc4d0..b8a672956011 100644 --- a/packages/angular/src/errorhandler.ts +++ b/packages/angular/src/errorhandler.ts @@ -29,25 +29,6 @@ class SentryErrorHandler implements AngularErrorHandler { logErrors: true, ...options, }; - - Sentry.configureScope(scope => { - scope.addEventProcessor(event => { - event.sdk = { - ...event.sdk, - name: 'sentry.javascript.angular', - packages: [ - ...((event.sdk && event.sdk.packages) || []), - { - name: 'npm:@sentry/angular', - version: Sentry.SDK_VERSION, - }, - ], - version: Sentry.SDK_VERSION, - }; - - return event; - }); - }); } /** diff --git a/packages/angular/src/index.ts b/packages/angular/src/index.ts index b746af148c1f..19c411fbc8d0 100644 --- a/packages/angular/src/index.ts +++ b/packages/angular/src/index.ts @@ -1,4 +1,6 @@ export * from '@sentry/browser'; + +export { init } from './sdk'; export { createErrorHandler, ErrorHandlerOptions } from './errorhandler'; export { getActiveTransaction, diff --git a/packages/angular/src/sdk.ts b/packages/angular/src/sdk.ts new file mode 100644 index 000000000000..09267d1f2082 --- /dev/null +++ b/packages/angular/src/sdk.ts @@ -0,0 +1,19 @@ +import { BrowserOptions, init as browserInit, SDK_VERSION } from '@sentry/browser'; + +/** + * Inits the Angular SDK + */ +export function init(options: BrowserOptions): void { + options._metadata = options._metadata || {}; + options._metadata.sdk = { + name: 'sentry.javascript.angular', + packages: [ + { + name: 'npm:@sentry/angular', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; + browserInit(options); +} diff --git a/packages/browser/package.json b/packages/browser/package.json index c90bbfca83b2..eab3161c7632 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -81,7 +81,6 @@ "size:check": "run-p size:check:es5 size:check:es6", "size:check:es5": "cat build/bundle.min.js | gzip -9 | wc -c | awk '{$1=$1/1024; print \"ES5: \",$1,\"kB\";}'", "size:check:es6": "cat build/bundle.es6.min.js | gzip -9 | wc -c | awk '{$1=$1/1024; print \"ES6: \",$1,\"kB\";}'", - "version": "node ../../scripts/versionbump.js src/version.ts", "pack": "npm pack" }, "volta": { diff --git a/packages/browser/src/backend.ts b/packages/browser/src/backend.ts index 499d37125a1d..db4b83adfdf7 100644 --- a/packages/browser/src/backend.ts +++ b/packages/browser/src/backend.ts @@ -67,6 +67,7 @@ export class BrowserBackend extends BaseBackend { const transportOptions = { ...this._options.transportOptions, dsn: this._options.dsn, + _metadata: this._options._metadata, }; if (this._options.transport) { diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index ac8cd3281419..35595f41abfb 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -5,7 +5,6 @@ import { getGlobalObject, logger } from '@sentry/utils'; import { BrowserBackend, BrowserOptions } from './backend'; import { injectReportDialog, ReportDialogOptions } from './helpers'; import { Breadcrumbs } from './integrations'; -import { SDK_NAME, SDK_VERSION } from './version'; /** * The Sentry Browser SDK Client. @@ -51,19 +50,6 @@ export class BrowserClient extends BaseClient { */ protected _prepareEvent(event: Event, scope?: Scope, hint?: EventHint): PromiseLike { event.platform = event.platform || 'javascript'; - event.sdk = { - ...event.sdk, - name: SDK_NAME, - packages: [ - ...((event.sdk && event.sdk.packages) || []), - { - name: 'npm:@sentry/browser', - version: SDK_VERSION, - }, - ], - version: SDK_VERSION, - }; - return super._prepareEvent(event, scope, hint); } diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index cf9e531bdc58..cbe5c0c4f262 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -28,6 +28,7 @@ export { makeMain, Scope, startTransaction, + SDK_VERSION, setContext, setExtra, setExtras, @@ -42,4 +43,4 @@ export { BrowserClient } from './client'; export { injectReportDialog, ReportDialogOptions } from './helpers'; export { eventFromException, eventFromMessage } from './eventbuilder'; export { defaultIntegrations, forceLoad, init, lastEventId, onLoad, showReportDialog, flush, close, wrap } from './sdk'; -export { SDK_NAME, SDK_VERSION } from './version'; +export { SDK_NAME } from './version'; diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 30a7f7a12b03..d46e0253e4c1 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -1,4 +1,4 @@ -import { getCurrentHub, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; +import { getCurrentHub, initAndBind, Integrations as CoreIntegrations, SDK_VERSION } from '@sentry/core'; import { getGlobalObject, SyncPromise } from '@sentry/utils'; import { BrowserOptions } from './backend'; @@ -88,6 +88,18 @@ export function init(options: BrowserOptions = {}): void { options.autoSessionTracking = false; } + options._metadata = options._metadata || {}; + options._metadata.sdk = { + name: 'sentry.javascript.browser', + packages: [ + { + name: 'npm:@sentry/browser', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; + initAndBind(BrowserClient, options); if (options.autoSessionTracking) { diff --git a/packages/browser/src/transports/base.ts b/packages/browser/src/transports/base.ts index fa18d5274a50..09eb8b058bfd 100644 --- a/packages/browser/src/transports/base.ts +++ b/packages/browser/src/transports/base.ts @@ -26,7 +26,7 @@ export abstract class BaseTransport implements Transport { protected readonly _rateLimits: Record = {}; public constructor(public options: TransportOptions) { - this._api = new API(this.options.dsn); + this._api = new API(options.dsn, options._metadata); // eslint-disable-next-line deprecation/deprecation this.url = this._api.getStoreEndpointWithUrlEncodedAuth(); } diff --git a/packages/browser/src/version.ts b/packages/browser/src/version.ts index e22122255bcd..462be137a6ca 100644 --- a/packages/browser/src/version.ts +++ b/packages/browser/src/version.ts @@ -1,2 +1,2 @@ +// TODO: Remove in the next major release and rely only on @sentry/core SDK_VERSION and SdkInfo metadata export const SDK_NAME = 'sentry.javascript.browser'; -export const SDK_VERSION = '5.30.0'; diff --git a/packages/core/package.json b/packages/core/package.json index 4c5e2030b40f..6ccbdf5928fc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -48,7 +48,8 @@ "fix:eslint": "eslint . --format stylish --fix", "test": "jest", "test:watch": "jest --watch", - "pack": "npm pack" + "pack": "npm pack", + "version": "node ../../scripts/versionbump.js src/version.ts" }, "volta": { "extends": "../../package.json" diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index 257ccb929571..4dadd5539ba3 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -1,14 +1,18 @@ -import { DsnLike } from '@sentry/types'; +import { DsnLike, SdkMetadata } from '@sentry/types'; import { Dsn, urlEncode } from '@sentry/utils'; const SENTRY_API_VERSION = '7'; -/** Helper class to provide urls to different Sentry endpoints. */ +/** + * Helper class to provide urls, headers and metadata that can be used to form + * different types of requests to Sentry endpoints. + * Supports both envelopes and regular event requests. + **/ export class API { /** The internally used Dsn object. */ private readonly _dsnObject: Dsn; /** Create a new instance of API */ - public constructor(public dsn: DsnLike) { + public constructor(public dsn: DsnLike, public metadata: SdkMetadata = {}) { this._dsnObject = new Dsn(dsn); } @@ -59,6 +63,7 @@ export class API { * This is needed for node and the old /store endpoint in sentry */ public getRequestHeaders(clientName: string, clientVersion: string): { [key: string]: string } { + // CHANGE THIS to use metadata but keep clientName and clientVersion compatible const dsn = this._dsnObject; const header = [`Sentry sentry_version=${SENTRY_API_VERSION}`]; header.push(`sentry_client=${clientName}/${clientVersion}`); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6a5e1c6a586f..5fe8450de3ed 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -20,6 +20,7 @@ export { BackendClass, BaseBackend } from './basebackend'; export { eventToSentryRequest, sessionToSentryRequest } from './request'; export { initAndBind, ClientClass } from './sdk'; export { NoopTransport } from './transports/noop'; +export { SDK_VERSION } from './version'; import * as Integrations from './integrations'; diff --git a/packages/core/src/request.ts b/packages/core/src/request.ts index 14084d942c33..5a2e966f48f8 100644 --- a/packages/core/src/request.ts +++ b/packages/core/src/request.ts @@ -1,11 +1,42 @@ -import { Event, SentryRequest, Session } from '@sentry/types'; +import { Event, SdkInfo, SentryRequest, Session } from '@sentry/types'; import { API } from './api'; +/** Extract sdk info from from the API metadata */ +function getSdkMetadataForEnvelopeHeader(api: API): SdkInfo | undefined { + if (!api.metadata || !api.metadata.sdk) { + return; + } + const { name, version } = api.metadata.sdk; + return { name, version }; +} + +/** + * Apply SdkInfo (name, version, packages, integrations) to the corresponding event key. + * Merge with existing data if any. + **/ +function enhanceEventWithSdkInfo(event: Event, sdkInfo?: SdkInfo): Event { + if (!sdkInfo) { + return event; + } + + event.sdk = event.sdk || { + name: sdkInfo.name, + version: sdkInfo.version, + }; + event.sdk.name = event.sdk.name || sdkInfo.name; + event.sdk.version = event.sdk.version || sdkInfo.version; + event.sdk.integrations = [...(event.sdk.integrations || []), ...(sdkInfo.integrations || [])]; + event.sdk.packages = [...(event.sdk.packages || []), ...(sdkInfo.packages || [])]; + return event; +} + /** Creates a SentryRequest from an event. */ export function sessionToSentryRequest(session: Session, api: API): SentryRequest { + const sdkInfo = getSdkMetadataForEnvelopeHeader(api); const envelopeHeaders = JSON.stringify({ sent_at: new Date().toISOString(), + ...(sdkInfo && { sdk: sdkInfo }), }); const itemHeaders = JSON.stringify({ type: 'session', @@ -24,11 +55,13 @@ export function eventToSentryRequest(event: Event, api: API): SentryRequest { const { __sentry_samplingMethod: samplingMethod, __sentry_sampleRate: sampleRate, ...otherTags } = event.tags || {}; event.tags = otherTags; - const useEnvelope = event.type === 'transaction'; + const sdkInfo = getSdkMetadataForEnvelopeHeader(api); + const eventType = event.type || 'event'; + const useEnvelope = eventType === 'transaction'; const req: SentryRequest = { - body: JSON.stringify(event), - type: event.type || 'event', + body: JSON.stringify(sdkInfo ? enhanceEventWithSdkInfo(event, api.metadata.sdk) : event), + type: eventType, url: useEnvelope ? api.getEnvelopeEndpointWithUrlEncodedAuth() : api.getStoreEndpointWithUrlEncodedAuth(), }; @@ -42,6 +75,7 @@ export function eventToSentryRequest(event: Event, api: API): SentryRequest { const envelopeHeaders = JSON.stringify({ event_id: event.event_id, sent_at: new Date().toISOString(), + ...(sdkInfo && { sdk: sdkInfo }), }); const itemHeaders = JSON.stringify({ type: event.type, diff --git a/packages/core/src/version.ts b/packages/core/src/version.ts new file mode 100644 index 000000000000..8a207150eec0 --- /dev/null +++ b/packages/core/src/version.ts @@ -0,0 +1 @@ +export const SDK_VERSION = '5.30.0'; diff --git a/packages/core/test/lib/request.test.ts b/packages/core/test/lib/request.test.ts index be71d72cc8b8..4e19cf5680a8 100644 --- a/packages/core/test/lib/request.test.ts +++ b/packages/core/test/lib/request.test.ts @@ -4,17 +4,29 @@ import { API } from '../../src/api'; import { eventToSentryRequest } from '../../src/request'; describe('eventToSentryRequest', () => { - const api = new API('https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012'); - const event: Event = { - contexts: { trace: { trace_id: '1231201211212012', span_id: '12261980', op: 'pageload' } }, - environment: 'dogpark', - event_id: '0908201304152013', - release: 'off.leash.park', - spans: [], - transaction: '/dogs/are/great/', - type: 'transaction', - user: { id: '1121', username: 'CharlieDog', ip_address: '11.21.20.12' }, - }; + let api: API; + let event: Event; + + beforeEach(() => { + api = new API('https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012', { + sdk: { + integrations: ['AWSLambda'], + name: 'sentry.javascript.browser', + version: `12.31.12`, + packages: [{ name: 'npm:@sentry/browser', version: `6.6.6` }], + }, + }); + event = { + contexts: { trace: { trace_id: '1231201211212012', span_id: '12261980', op: 'pageload' } }, + environment: 'dogpark', + event_id: '0908201304152013', + release: 'off.leash.park', + spans: [], + transaction: '/dogs/are/great/', + type: 'transaction', + user: { id: '1121', username: 'CharlieDog', ip_address: '11.21.20.12' }, + }; + }); [ { method: TransactionSamplingMethod.Rate, rate: '0.1121', dog: 'Charlie' }, @@ -30,7 +42,7 @@ describe('eventToSentryRequest', () => { // TODO kmclb - once tag types are loosened, don't need to cast to string here event.tags = { __sentry_samplingMethod: String(method), __sentry_sampleRate: String(rate), dog }; - const result = eventToSentryRequest(event as Event, api); + const result = eventToSentryRequest(event, api); const [envelopeHeaderString, itemHeaderString, eventString] = result.body.split('\n'); @@ -53,4 +65,60 @@ describe('eventToSentryRequest', () => { expect('dog' in envelope.event.tags).toBe(true); }); }); + + it('adds sdk info to envelope header', () => { + const result = eventToSentryRequest(event, api); + + const envelopeHeaderString = result.body.split('\n')[0]; + const parsedHeader = JSON.parse(envelopeHeaderString); + + expect(parsedHeader).toEqual( + expect.objectContaining({ sdk: { name: 'sentry.javascript.browser', version: '12.31.12' } }), + ); + }); + + it('adds sdk info to event body', () => { + const result = eventToSentryRequest(event, api); + + const eventString = result.body.split('\n')[2]; + const parsedEvent = JSON.parse(eventString); + + expect(parsedEvent).toEqual( + expect.objectContaining({ + sdk: { + integrations: ['AWSLambda'], + name: 'sentry.javascript.browser', + version: `12.31.12`, + packages: [{ name: 'npm:@sentry/browser', version: `6.6.6` }], + }, + }), + ); + }); + + it('merges existing sdk info if one is present on the event body', () => { + event.sdk = { + integrations: ['Clojure'], + name: 'foo', + packages: [{ name: 'npm:@sentry/clj', version: `6.6.6` }], + version: '1337', + }; + const result = eventToSentryRequest(event, api); + + const eventString = result.body.split('\n')[2]; + const parsedEvent = JSON.parse(eventString); + + expect(parsedEvent).toEqual( + expect.objectContaining({ + sdk: { + integrations: ['Clojure', 'AWSLambda'], + name: 'foo', + packages: [ + { name: 'npm:@sentry/clj', version: `6.6.6` }, + { name: 'npm:@sentry/browser', version: `6.6.6` }, + ], + version: '1337', + }, + }), + ); + }); }); diff --git a/packages/ember/addon/index.ts b/packages/ember/addon/index.ts index d65e13faa4d0..3f8c56f3514d 100644 --- a/packages/ember/addon/index.ts +++ b/packages/ember/addon/index.ts @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/browser'; -import { addGlobalEventProcessor, SDK_VERSION, BrowserOptions } from '@sentry/browser'; +import { SDK_VERSION, BrowserOptions } from '@sentry/browser'; import environmentConfig from 'ember-get-config'; import { macroCondition, isDevelopingApp } from '@embroider/macros'; import { next } from '@ember/runloop'; @@ -18,7 +18,17 @@ export function InitSentryForEmber(_runtimeConfig: BrowserOptions | undefined) { const initConfig = Object.assign({}, config.sentry, _runtimeConfig || {}); - createEmberEventProcessor(); + initConfig._metadata = initConfig._metadata || {}; + initConfig._metadata.sdk = { + name: 'sentry.javascript.ember', + packages: [ + { + name: 'npm:@sentry/ember', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; Sentry.init(initConfig); @@ -83,25 +93,4 @@ export const instrumentRoutePerformance = (BaseRoute: any) => { }[BaseRoute.name]; }; -function createEmberEventProcessor(): void { - if (addGlobalEventProcessor) { - addGlobalEventProcessor(event => { - event.sdk = { - ...event.sdk, - name: 'sentry.javascript.ember', - packages: [ - ...((event.sdk && event.sdk.packages) || []), - { - name: 'npm:@sentry/ember', - version: SDK_VERSION, - }, - ], - version: SDK_VERSION, - }; - - return event; - }); - } -} - export * from '@sentry/browser'; diff --git a/packages/gatsby/gatsby-browser.js b/packages/gatsby/gatsby-browser.js index 42d5f606f2c8..5d08192be56e 100644 --- a/packages/gatsby/gatsby-browser.js +++ b/packages/gatsby/gatsby-browser.js @@ -6,6 +6,18 @@ exports.onClientEntry = function(_, pluginParams) { return; } + pluginParams._metadata = pluginParams._metadata || {}; + pluginParams._metadata.sdk = { + name: 'sentry.javascript.gatsby', + packages: [ + { + name: 'npm:@sentry/gatsby', + version: Sentry.SDK_VERSION, + }, + ], + version: Sentry.SDK_VERSION, + }; + const integrations = [...(pluginParams.integrations || [])]; if (Tracing.hasTracingEnabled(pluginParams) && !integrations.some(ele => ele.name === 'BrowserTracing')) { @@ -24,20 +36,5 @@ exports.onClientEntry = function(_, pluginParams) { integrations, }); - Sentry.addGlobalEventProcessor(event => { - event.sdk = { - ...event.sdk, - name: 'sentry.javascript.gatsby', - packages: [ - ...((event.sdk && event.sdk.packages) || []), - { - name: 'npm:@sentry/gatsby', - version: Sentry.SDK_VERSION, - }, - ], - version: Sentry.SDK_VERSION, - }; - return event; - }); window.Sentry = Sentry; }; diff --git a/packages/gatsby/test/gatsby-browser.test.ts b/packages/gatsby/test/gatsby-browser.test.ts index ba83b407002f..7cfc3b61c557 100644 --- a/packages/gatsby/test/gatsby-browser.test.ts +++ b/packages/gatsby/test/gatsby-browser.test.ts @@ -7,7 +7,6 @@ const { onClientEntry } = require('../gatsby-browser'); (global as any).__SENTRY_DSN__ = 'https://examplePublicKey@o0.ingest.sentry.io/0'; let sentryInit = jest.fn(); -let sentryProcessEvent: (event: T) => T; jest.mock('@sentry/react', () => { const original = jest.requireActual('@sentry/react'); return { @@ -15,9 +14,6 @@ jest.mock('@sentry/react', () => { init: (...args: any[]) => { sentryInit(...args); }, - addGlobalEventProcessor: (callback: any) => { - sentryProcessEvent = callback; - }, }; }); @@ -36,9 +32,6 @@ describe('onClientEntry', () => { beforeEach(() => { sentryInit = jest.fn(); tracingAddExtensionMethods = jest.fn(); - - // @ts-ignore need to set as undefined - sentryProcessEvent = undefined; }); afterEach(() => { @@ -53,20 +46,7 @@ describe('onClientEntry', () => { environment: process.env.NODE_ENV, integrations: [], release: (global as any).__SENTRY_RELEASE__, - }); - }); - - it('sets window.Sentry', () => { - onClientEntry(undefined, {}); - expect((window as any).Sentry).not.toBeUndefined(); - }); - - it('adds a global event processor', () => { - onClientEntry(undefined, {}); - if (sentryProcessEvent) { - const changedEvent = sentryProcessEvent({}); - - expect(changedEvent).toEqual({ + _metadata: { sdk: { name: 'sentry.javascript.gatsby', packages: [ @@ -77,10 +57,13 @@ describe('onClientEntry', () => { ], version: expect.any(String), }, - }); - } else { - fail('process event not defined'); - } + }, + }); + }); + + it('sets window.Sentry', () => { + onClientEntry(undefined, {}); + expect((window as any).Sentry).not.toBeUndefined(); }); it('adds Tracing extension methods', () => { @@ -145,7 +128,6 @@ describe('onClientEntry', () => { onClientEntry(); expect(sentryInit).toHaveBeenCalledTimes(0); expect((window as any).Sentry).toBeUndefined(); - expect(sentryProcessEvent).toBeUndefined(); expect(tracingAddExtensionMethods).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/node/package.json b/packages/node/package.json index 195a78c34420..a97a562bccb4 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -61,7 +61,6 @@ "test:watch": "jest --watch", "test:express": "node test/manual/express-scope-separation/start.js", "test:webpack": "cd test/manual/webpack-domain/ && yarn && node npm-build.js", - "version": "node ../../scripts/versionbump.js src/version.ts", "pack": "npm pack" }, "volta": { diff --git a/packages/node/src/backend.ts b/packages/node/src/backend.ts index c264bebd1716..ad335f33eb01 100644 --- a/packages/node/src/backend.ts +++ b/packages/node/src/backend.ts @@ -140,6 +140,7 @@ export class NodeBackend extends BaseBackend { ...(this._options.httpsProxy && { httpsProxy: this._options.httpsProxy }), ...(this._options.caCerts && { caCerts: this._options.caCerts }), dsn: this._options.dsn, + _metadata: this._options._metadata, }; if (this._options.transport) { diff --git a/packages/node/src/client.ts b/packages/node/src/client.ts index bf4230416f0e..13fc0d1f2af9 100644 --- a/packages/node/src/client.ts +++ b/packages/node/src/client.ts @@ -2,7 +2,6 @@ import { BaseClient, Scope } from '@sentry/core'; import { Event, EventHint } from '@sentry/types'; import { NodeBackend, NodeOptions } from './backend'; -import { SDK_NAME, SDK_VERSION } from './version'; /** * The Sentry Node SDK Client. @@ -24,23 +23,9 @@ export class NodeClient extends BaseClient { */ protected _prepareEvent(event: Event, scope?: Scope, hint?: EventHint): PromiseLike { event.platform = event.platform || 'node'; - event.sdk = { - ...event.sdk, - name: SDK_NAME, - packages: [ - ...((event.sdk && event.sdk.packages) || []), - { - name: 'npm:@sentry/node', - version: SDK_VERSION, - }, - ], - version: SDK_VERSION, - }; - if (this.getOptions().serverName) { event.server_name = this.getOptions().serverName; } - return super._prepareEvent(event, scope, hint); } } diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 6429ce3a75c9..ef29ad75a92a 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -28,6 +28,7 @@ export { makeMain, Scope, startTransaction, + SDK_VERSION, setContext, setExtra, setExtras, @@ -40,7 +41,7 @@ export { export { NodeBackend, NodeOptions } from './backend'; export { NodeClient } from './client'; export { defaultIntegrations, init, lastEventId, flush, close } from './sdk'; -export { SDK_NAME, SDK_VERSION } from './version'; +export { SDK_NAME } from './version'; import { Integrations as CoreIntegrations } from '@sentry/core'; import { getMainCarrier } from '@sentry/hub'; diff --git a/packages/node/src/sdk.ts b/packages/node/src/sdk.ts index 9aa7c2f60858..b846a37419a2 100644 --- a/packages/node/src/sdk.ts +++ b/packages/node/src/sdk.ts @@ -1,4 +1,4 @@ -import { getCurrentHub, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; +import { getCurrentHub, initAndBind, Integrations as CoreIntegrations, SDK_VERSION } from '@sentry/core'; import { getMainCarrier, setHubOnCarrier } from '@sentry/hub'; import { getGlobalObject } from '@sentry/utils'; import * as domain from 'domain'; @@ -108,6 +108,18 @@ export function init(options: NodeOptions = {}): void { options.environment = process.env.SENTRY_ENVIRONMENT; } + options._metadata = options._metadata || {}; + options._metadata.sdk = { + name: 'sentry.javascript.node', + packages: [ + { + name: 'npm:@sentry/node', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any if ((domain as any).active) { setHubOnCarrier(getMainCarrier(), getCurrentHub()); diff --git a/packages/node/src/transports/base.ts b/packages/node/src/transports/base.ts index fb2e9f8ba230..1416e49339fe 100644 --- a/packages/node/src/transports/base.ts +++ b/packages/node/src/transports/base.ts @@ -1,4 +1,4 @@ -import { API, eventToSentryRequest } from '@sentry/core'; +import { API, eventToSentryRequest, SDK_VERSION } from '@sentry/core'; import { Event, Response, Status, Transport, TransportOptions } from '@sentry/types'; import { logger, parseRetryAfterHeader, PromiseBuffer, SentryError } from '@sentry/utils'; import * as fs from 'fs'; @@ -6,7 +6,7 @@ import * as http from 'http'; import * as https from 'https'; import * as url from 'url'; -import { SDK_NAME, SDK_VERSION } from '../version'; +import { SDK_NAME } from '../version'; /** * Internal used interface for typescript. @@ -53,7 +53,7 @@ export abstract class BaseTransport implements Transport { /** Create instance and set this.dsn */ public constructor(public options: TransportOptions) { - this._api = new API(options.dsn); + this._api = new API(options.dsn, options._metadata); } /** diff --git a/packages/node/src/version.ts b/packages/node/src/version.ts index 38b745a6835f..8cfd1070cec5 100644 --- a/packages/node/src/version.ts +++ b/packages/node/src/version.ts @@ -1,2 +1,2 @@ +// TODO: Remove in the next major release and rely only on @sentry/core SDK_VERSION and SdkMetadata export const SDK_NAME = 'sentry.javascript.node'; -export const SDK_VERSION = '5.30.0'; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 57a37862d85d..306f90e4f943 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,37 +1,8 @@ -import { addGlobalEventProcessor, SDK_VERSION } from '@sentry/browser'; - -/** - * A global side effect that makes sure Sentry events that user - * `@sentry/react` will correctly have Sentry events associated - * with it. - */ -function createReactEventProcessor(): void { - if (addGlobalEventProcessor) { - addGlobalEventProcessor(event => { - event.sdk = { - ...event.sdk, - name: 'sentry.javascript.react', - packages: [ - ...((event.sdk && event.sdk.packages) || []), - { - name: 'npm:@sentry/react', - version: SDK_VERSION, - }, - ], - version: SDK_VERSION, - }; - - return event; - }); - } -} - export * from '@sentry/browser'; +export { init } from './sdk'; export { Profiler, withProfiler, useProfiler } from './profiler'; export { ErrorBoundary, withErrorBoundary } from './errorboundary'; export { createReduxEnhancer } from './redux'; export { reactRouterV3Instrumentation } from './reactrouterv3'; export { reactRouterV4Instrumentation, reactRouterV5Instrumentation, withSentryRouting } from './reactrouter'; - -createReactEventProcessor(); diff --git a/packages/react/src/sdk.ts b/packages/react/src/sdk.ts new file mode 100644 index 000000000000..823b8ef47a42 --- /dev/null +++ b/packages/react/src/sdk.ts @@ -0,0 +1,19 @@ +import { BrowserOptions, init as browserInit, SDK_VERSION } from '@sentry/browser'; + +/** + * Inits the React SDK + */ +export function init(options: BrowserOptions): void { + options._metadata = options._metadata || {}; + options._metadata.sdk = { + name: 'sentry.javascript.react', + packages: [ + { + name: 'npm:@sentry/react', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; + browserInit(options); +} diff --git a/packages/serverless/src/awslambda.ts b/packages/serverless/src/awslambda.ts index 599ac3ae4656..0c915cd19e25 100644 --- a/packages/serverless/src/awslambda.ts +++ b/packages/serverless/src/awslambda.ts @@ -56,8 +56,22 @@ export function init(options: Sentry.NodeOptions = {}): void { if (options.defaultIntegrations === undefined) { options.defaultIntegrations = defaultIntegrations; } + + options._metadata = options._metadata || {}; + options._metadata.sdk = { + name: 'sentry.javascript.serverless', + integrations: ['AWSLambda'], + packages: [ + { + name: 'npm:@sentry/serverless', + version: Sentry.SDK_VERSION, + }, + ], + version: Sentry.SDK_VERSION, + }; + Sentry.init(options); - Sentry.addGlobalEventProcessor(serverlessEventProcessor('AWSLambda')); + Sentry.addGlobalEventProcessor(serverlessEventProcessor); } /** */ diff --git a/packages/serverless/src/gcpfunction/index.ts b/packages/serverless/src/gcpfunction/index.ts index 3132e7d8caab..2cd8a0bec73f 100644 --- a/packages/serverless/src/gcpfunction/index.ts +++ b/packages/serverless/src/gcpfunction/index.ts @@ -22,6 +22,20 @@ export function init(options: Sentry.NodeOptions = {}): void { if (options.defaultIntegrations === undefined) { options.defaultIntegrations = defaultIntegrations; } + + options._metadata = options._metadata || {}; + options._metadata.sdk = { + name: 'sentry.javascript.serverless', + integrations: ['GCPFunction'], + packages: [ + { + name: 'npm:@sentry/serverless', + version: Sentry.SDK_VERSION, + }, + ], + version: Sentry.SDK_VERSION, + }; + Sentry.init(options); - Sentry.addGlobalEventProcessor(serverlessEventProcessor('GCPFunction')); + Sentry.addGlobalEventProcessor(serverlessEventProcessor); } diff --git a/packages/serverless/src/utils.ts b/packages/serverless/src/utils.ts index 1f4db9453ffe..6ea78a6cfc4d 100644 --- a/packages/serverless/src/utils.ts +++ b/packages/serverless/src/utils.ts @@ -1,4 +1,4 @@ -import { Event, SDK_VERSION } from '@sentry/node'; +import { Event } from '@sentry/node'; import { addExceptionMechanism } from '@sentry/utils'; import * as domain from 'domain'; @@ -9,28 +9,12 @@ import * as domain from 'domain'; * @param event Event * @param integration Name of the serverless integration ('AWSLambda', 'GCPFunction', etc) */ -export function serverlessEventProcessor(integration: string): (event: Event) => Event { - return event => { - event.sdk = { - ...event.sdk, - name: 'sentry.javascript.serverless', - integrations: [...((event.sdk && event.sdk.integrations) || []), integration], - packages: [ - ...((event.sdk && event.sdk.packages) || []), - { - name: 'npm:@sentry/serverless', - version: SDK_VERSION, - }, - ], - version: SDK_VERSION, - }; - - addExceptionMechanism(event, { - handled: false, - }); +export function serverlessEventProcessor(event: Event): Event { + addExceptionMechanism(event, { + handled: false, + }); - return event; - }; + return event; } /** diff --git a/packages/serverless/test/awslambda.test.ts b/packages/serverless/test/awslambda.test.ts index e80b79f2515d..003e357fca59 100644 --- a/packages/serverless/test/awslambda.test.ts +++ b/packages/serverless/test/awslambda.test.ts @@ -341,66 +341,40 @@ describe('AWSLambda', () => { }); describe('init()', () => { - test('enhance event with SDK info and correct mechanism value', async () => { - expect.assertions(1); - - const eventWithSomeData = { - exception: { - values: [{}], - }, - sdk: { - integrations: ['SomeIntegration'], - packages: [ - { - name: 'some:@random/package', - version: '1337', - }, - ], - }, - }; - // @ts-ignore see "Why @ts-ignore" note - Sentry.addGlobalEventProcessor.mockImplementationOnce(cb => cb(eventWithSomeData)); - Sentry.AWSLambda.init({ defaultIntegrations: [] }); - expect(eventWithSomeData).toEqual({ - exception: { - values: [ - { - mechanism: { - handled: false, - }, - }, - ], - }, - sdk: { - name: 'sentry.javascript.serverless', - integrations: ['SomeIntegration', 'AWSLambda'], - packages: [ - { - name: 'some:@random/package', - version: '1337', - }, - { - name: 'npm:@sentry/serverless', + test('calls Sentry.init with correct sdk info metadata', () => { + Sentry.AWSLambda.init({}); + + expect(Sentry.init).toBeCalledWith( + expect.objectContaining({ + _metadata: { + sdk: { + name: 'sentry.javascript.serverless', + integrations: ['AWSLambda'], + packages: [ + { + name: 'npm:@sentry/serverless', + version: '6.6.6', + }, + ], version: '6.6.6', }, - ], - version: '6.6.6', - }, - }); + }, + }), + ); }); - test('populates missing SDK info and mechanism', async () => { - expect.assertions(1); - - const eventWithoutAnyData: Event = { + test('enhance event with correct mechanism value', () => { + const eventWithSomeData = { exception: { values: [{}], }, }; + // @ts-ignore see "Why @ts-ignore" note - Sentry.addGlobalEventProcessor.mockImplementationOnce(cb => cb(eventWithoutAnyData)); - Sentry.AWSLambda.init({ defaultIntegrations: [] }); - expect(eventWithoutAnyData).toEqual({ + Sentry.addGlobalEventProcessor.mockImplementationOnce(cb => cb(eventWithSomeData)); + Sentry.AWSLambda.init({}); + + expect(eventWithSomeData).toEqual({ exception: { values: [ { @@ -410,17 +384,6 @@ describe('AWSLambda', () => { }, ], }, - sdk: { - name: 'sentry.javascript.serverless', - integrations: ['AWSLambda'], - packages: [ - { - name: 'npm:@sentry/serverless', - version: '6.6.6', - }, - ], - version: '6.6.6', - }, }); }); }); diff --git a/packages/serverless/test/gcpfunction.test.ts b/packages/serverless/test/gcpfunction.test.ts index 0f7903df3240..b11a5b0b99dc 100644 --- a/packages/serverless/test/gcpfunction.test.ts +++ b/packages/serverless/test/gcpfunction.test.ts @@ -361,66 +361,40 @@ describe('GCPFunction', () => { }); describe('init()', () => { - test('enhance event with SDK info and correct mechanism value', async () => { - expect.assertions(1); - - const eventWithSomeData = { - exception: { - values: [{}], - }, - sdk: { - integrations: ['SomeIntegration'], - packages: [ - { - name: 'some:@random/package', - version: '1337', - }, - ], - }, - }; - // @ts-ignore see "Why @ts-ignore" note - Sentry.addGlobalEventProcessor.mockImplementationOnce(cb => cb(eventWithSomeData)); - Sentry.AWSLambda.init({ defaultIntegrations: [] }); - expect(eventWithSomeData).toEqual({ - exception: { - values: [ - { - mechanism: { - handled: false, - }, - }, - ], - }, - sdk: { - name: 'sentry.javascript.serverless', - integrations: ['SomeIntegration', 'AWSLambda'], - packages: [ - { - name: 'some:@random/package', - version: '1337', - }, - { - name: 'npm:@sentry/serverless', + test('calls Sentry.init with correct sdk info metadata', () => { + Sentry.GCPFunction.init({}); + + expect(Sentry.init).toBeCalledWith( + expect.objectContaining({ + _metadata: { + sdk: { + name: 'sentry.javascript.serverless', + integrations: ['GCPFunction'], + packages: [ + { + name: 'npm:@sentry/serverless', + version: '6.6.6', + }, + ], version: '6.6.6', }, - ], - version: '6.6.6', - }, - }); + }, + }), + ); }); - test('populates missing SDK info and mechanism', async () => { - expect.assertions(1); - - const eventWithoutAnyData: Event = { + test('enhance event with correct mechanism value', () => { + const eventWithSomeData = { exception: { values: [{}], }, }; + // @ts-ignore see "Why @ts-ignore" note - Sentry.addGlobalEventProcessor.mockImplementationOnce(cb => cb(eventWithoutAnyData)); - Sentry.AWSLambda.init({ defaultIntegrations: [] }); - expect(eventWithoutAnyData).toEqual({ + Sentry.addGlobalEventProcessor.mockImplementationOnce(cb => cb(eventWithSomeData)); + Sentry.GCPFunction.init({}); + + expect(eventWithSomeData).toEqual({ exception: { values: [ { @@ -430,17 +404,6 @@ describe('GCPFunction', () => { }, ], }, - sdk: { - name: 'sentry.javascript.serverless', - integrations: ['AWSLambda'], - packages: [ - { - name: 'npm:@sentry/serverless', - version: '6.6.6', - }, - ], - version: '6.6.6', - }, }); }); }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index fc288847b41a..d334d16b14e1 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -19,6 +19,7 @@ export { Response } from './response'; export { Runtime } from './runtime'; export { CaptureContext, Scope, ScopeContext } from './scope'; export { SdkInfo } from './sdkinfo'; +export { SdkMetadata } from './sdkmetadata'; export { Session, SessionContext, SessionStatus } from './session'; export { Severity } from './severity'; export { Span, SpanContext } from './span'; diff --git a/packages/types/src/options.ts b/packages/types/src/options.ts index c9035e5eb133..413c39e81ac4 100644 --- a/packages/types/src/options.ts +++ b/packages/types/src/options.ts @@ -2,6 +2,7 @@ import { Breadcrumb, BreadcrumbHint } from './breadcrumb'; import { Event, EventHint } from './event'; import { Integration } from './integration'; import { LogLevel } from './loglevel'; +import { SdkMetadata } from './sdkmetadata'; import { SamplingContext } from './transaction'; import { Transport, TransportClass, TransportOptions } from './transport'; @@ -106,13 +107,6 @@ export interface Options { */ shutdownTimeout?: number; - /** - * Options which are in beta, or otherwise not guaranteed to be stable. - */ - _experiments?: { - [key: string]: any; - }; - /** * Sample rate to determine trace sampling. * @@ -124,6 +118,19 @@ export interface Options { */ tracesSampleRate?: number; + /** + * Set of metadata about the SDK that can be internally used to enhance envelopes and events, + * and provide additional data about every request. + * */ + _metadata?: SdkMetadata; + + /** + * Options which are in beta, or otherwise not guaranteed to be stable. + */ + _experiments?: { + [key: string]: any; + }; + /** * Function to compute tracing sample rate dynamically and filter unwanted traces. * diff --git a/packages/types/src/sdkinfo.ts b/packages/types/src/sdkinfo.ts index 45500cad5482..14bb3c331509 100644 --- a/packages/types/src/sdkinfo.ts +++ b/packages/types/src/sdkinfo.ts @@ -1,6 +1,5 @@ import { Package } from './package'; -/** JSDoc */ export interface SdkInfo { name: string; version: string; diff --git a/packages/types/src/sdkmetadata.ts b/packages/types/src/sdkmetadata.ts new file mode 100644 index 000000000000..d65e989cb4c1 --- /dev/null +++ b/packages/types/src/sdkmetadata.ts @@ -0,0 +1,5 @@ +import { SdkInfo } from './sdkinfo'; + +export interface SdkMetadata { + sdk?: SdkInfo; +} diff --git a/packages/types/src/transport.ts b/packages/types/src/transport.ts index cfe318b997c7..3987768f32fd 100644 --- a/packages/types/src/transport.ts +++ b/packages/types/src/transport.ts @@ -1,6 +1,7 @@ import { DsnLike } from './dsn'; import { Event } from './event'; import { Response } from './response'; +import { SdkMetadata } from './sdkmetadata'; import { Session } from './session'; /** Transport used sending data to Sentry */ @@ -44,4 +45,9 @@ export interface TransportOptions { caCerts?: string; /** Fetch API init parameters */ fetchParameters?: { [key: string]: string }; + /** + * Set of metadata about the SDK that can be internally used to enhance envelopes and events, + * and provide additional data about every request. + * */ + _metadata?: SdkMetadata; } diff --git a/packages/vue/src/eventprocessor.ts b/packages/vue/src/eventprocessor.ts deleted file mode 100644 index 9a0f55b7ff47..000000000000 --- a/packages/vue/src/eventprocessor.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { addGlobalEventProcessor, SDK_VERSION } from '@sentry/browser'; - -/** - * A global side effect that makes sure Sentry events that user - * `@sentry/react` will correctly have Sentry events associated - * with it. - */ -export function createVueEventProcessor(): void { - addGlobalEventProcessor(event => { - event.sdk = { - ...event.sdk, - name: 'sentry.javascript.vue', - packages: [ - ...((event.sdk && event.sdk.packages) || []), - { - name: 'npm:@sentry/vue', - version: SDK_VERSION, - }, - ], - version: SDK_VERSION, - }; - - return event; - }); -} diff --git a/packages/vue/src/sdk.ts b/packages/vue/src/sdk.ts index 824b5eb37307..0655cdd86c7a 100644 --- a/packages/vue/src/sdk.ts +++ b/packages/vue/src/sdk.ts @@ -1,12 +1,8 @@ -/* eslint-disable max-lines */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { BrowserClient, BrowserOptions, defaultIntegrations, getCurrentHub } from '@sentry/browser'; -import { initAndBind } from '@sentry/core'; +/* eslint-disable max-lines, @typescript-eslint/no-explicit-any */ +import { BrowserOptions, getCurrentHub, init as browserInit, SDK_VERSION } from '@sentry/browser'; import { Span, Transaction } from '@sentry/types'; import { basename, getGlobalObject, logger, timestampWithMs } from '@sentry/utils'; -import { createVueEventProcessor } from './eventprocessor'; - export interface VueOptions extends BrowserOptions { /** Vue instance to be used inside the integration */ Vue?: VueInstance; @@ -129,17 +125,6 @@ interface TracingOptions { export function init( options: Partial & { tracingOptions: Partial }> = {}, ): void { - if (options.defaultIntegrations === undefined) { - options.defaultIntegrations = defaultIntegrations; - } - if (options.release === undefined) { - const window = getGlobalObject(); - // This supports the variable that sentry-webpack-plugin injects - if (window.SENTRY_RELEASE && window.SENTRY_RELEASE.id) { - options.release = window.SENTRY_RELEASE.id; - } - } - const finalOptions = { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access Vue: getGlobalObject().Vue as VueInstance, @@ -155,7 +140,19 @@ export function init( }, } as VueOptions; - initAndBind(BrowserClient, finalOptions); + finalOptions._metadata = finalOptions._metadata || {}; + finalOptions._metadata.sdk = { + name: 'sentry.javascript.vue', + packages: [ + { + name: 'npm:@sentry/vue', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; + + browserInit(finalOptions); if (finalOptions.Vue === undefined) { logger.warn('No Vue instance was provided. Also there is no Vue instance on the `window` object.'); logger.warn('We will only capture global unhandled errors.'); @@ -163,8 +160,6 @@ export function init( const vueHelper = new VueHelper(finalOptions); vueHelper.setup(); } - - createVueEventProcessor(); } /** JSDoc */