Skip to content

Commit

Permalink
feat: Allow for attaching metadata and pass it to the API and transpo…
Browse files Browse the repository at this point in the history
…rts (#3177)

* feat: Allow for attaching metadata and pass it to the API and transports
  • Loading branch information
kamilogorek authored Jan 18, 2021
1 parent 7095822 commit 06d6bd8
Show file tree
Hide file tree
Showing 40 changed files with 368 additions and 375 deletions.
19 changes: 0 additions & 19 deletions packages/angular/src/errorhandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
});
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/angular/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from '@sentry/browser';

export { init } from './sdk';
export { createErrorHandler, ErrorHandlerOptions } from './errorhandler';
export {
getActiveTransaction,
Expand Down
19 changes: 19 additions & 0 deletions packages/angular/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -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);
}
1 change: 0 additions & 1 deletion packages/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class BrowserBackend extends BaseBackend<BrowserOptions> {
const transportOptions = {
...this._options.transportOptions,
dsn: this._options.dsn,
_metadata: this._options._metadata,
};

if (this._options.transport) {
Expand Down
14 changes: 0 additions & 14 deletions packages/browser/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -51,19 +50,6 @@ export class BrowserClient extends BaseClient<BrowserBackend, BrowserOptions> {
*/
protected _prepareEvent(event: Event, scope?: Scope, hint?: EventHint): PromiseLike<Event | null> {
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);
}

Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export {
makeMain,
Scope,
startTransaction,
SDK_VERSION,
setContext,
setExtra,
setExtras,
Expand All @@ -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';
14 changes: 13 additions & 1 deletion packages/browser/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/transports/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export abstract class BaseTransport implements Transport {
protected readonly _rateLimits: Record<string, Date> = {};

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();
}
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/version.ts
Original file line number Diff line number Diff line change
@@ -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';
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/api.ts
Original file line number Diff line number Diff line change
@@ -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);
}

Expand Down Expand Up @@ -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}`);
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
42 changes: 38 additions & 4 deletions packages/core/src/request.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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(),
};

Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SDK_VERSION = '5.30.0';
92 changes: 80 additions & 12 deletions packages/core/test/lib/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand All @@ -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');

Expand All @@ -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',
},
}),
);
});
});
Loading

0 comments on commit 06d6bd8

Please sign in to comment.