Skip to content

feat(core): Expose prepareEvent util function #6517

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 1 commit into from
Jan 3, 2023
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: 1 addition & 1 deletion packages/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"size:check": "run-p size:check:es5 size:check:es6",
"size:check:es5": "cat build/bundles/bundle.min.js | gzip -9 | wc -c | awk '{$1=$1/1024; print \"ES5: \",$1,\"kB\";}'",
"size:check:es6": "cat build/bundles/bundle.es6.min.js | gzip -9 | wc -c | awk '{$1=$1/1024; print \"ES6: \",$1,\"kB\";}'",
"test": "run-s test:unit",
"test": "yarn test:unit",
"test:unit": "jest",
"test:integration": "test/integration/run.js",
"test:integration:checkbrowsers": "node scripts/checkbrowsers.js",
Expand Down
13 changes: 13 additions & 0 deletions packages/browser/test/unit/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,19 @@ describe('SentryBrowser', () => {
captureEvent({ message: 'event' });
});

it('should set `platform` on events', done => {
const options = getDefaultBrowserClientOptions({
beforeSend: (event: Event): Event | null => {
expect(event.platform).toBe('javascript');
done();
return event;
},
dsn,
});
getCurrentHub().bindClient(new BrowserClient(options));
captureEvent({ message: 'event' });
});

it('should not dedupe an event on bound client', async () => {
const localBeforeSend = jest.fn();
const options = getDefaultBrowserClientOptions({
Expand Down
167 changes: 3 additions & 164 deletions packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,23 @@ import {
addItemToEnvelope,
checkOrSetAlreadyCaught,
createAttachmentEnvelopeItem,
dateTimestampInSeconds,
isPlainObject,
isPrimitive,
isThenable,
logger,
makeDsn,
normalize,
rejectedSyncPromise,
resolvedSyncPromise,
SentryError,
SyncPromise,
truncate,
uuid4,
} from '@sentry/utils';

import { getEnvelopeEndpointWithUrlEncodedAuth } from './api';
import { createEventEnvelope, createSessionEnvelope } from './envelope';
import { IntegrationIndex, setupIntegrations } from './integration';
import { Scope } from './scope';
import { updateSession } from './session';
import { prepareEvent } from './utils/prepareEvent';

const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured.";

Expand Down Expand Up @@ -322,7 +319,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
// Note: we use `event` in replay, where we overwrite this hook.

if (this._options.sendClientReports) {
// We want to track each category (error, transaction, session) separately
// We want to track each category (error, transaction, session, replay_event) separately
// but still keep the distinction between different type of outcomes.
// We could use nested maps, but it's much easier to read and type this way.
// A correct type for map-based implementation if we want to go that route
Expand Down Expand Up @@ -419,166 +416,8 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
* @returns A new event with more information.
*/
protected _prepareEvent(event: Event, hint: EventHint, scope?: Scope): PromiseLike<Event | null> {
const { normalizeDepth = 3, normalizeMaxBreadth = 1_000 } = this.getOptions();
const prepared: Event = {
...event,
event_id: event.event_id || hint.event_id || uuid4(),
timestamp: event.timestamp || dateTimestampInSeconds(),
};

this._applyClientOptions(prepared);
this._applyIntegrationsMetadata(prepared);

// If we have scope given to us, use it as the base for further modifications.
// This allows us to prevent unnecessary copying of data if `captureContext` is not provided.
let finalScope = scope;
if (hint.captureContext) {
finalScope = Scope.clone(finalScope).update(hint.captureContext);
}

// We prepare the result here with a resolved Event.
let result = resolvedSyncPromise<Event | null>(prepared);

// This should be the last thing called, since we want that
// {@link Hub.addEventProcessor} gets the finished prepared event.
//
// We need to check for the existence of `finalScope.getAttachments`
// because `getAttachments` can be undefined if users are using an older version
// of `@sentry/core` that does not have the `getAttachments` method.
// See: https://github.com/getsentry/sentry-javascript/issues/5229
if (finalScope && finalScope.getAttachments) {
// Collect attachments from the hint and scope
const attachments = [...(hint.attachments || []), ...finalScope.getAttachments()];

if (attachments.length) {
hint.attachments = attachments;
}

// In case we have a hub we reassign it.
result = finalScope.applyToEvent(prepared, hint);
}

return result.then(evt => {
if (typeof normalizeDepth === 'number' && normalizeDepth > 0) {
return this._normalizeEvent(evt, normalizeDepth, normalizeMaxBreadth);
}
return evt;
});
}

/**
* Applies `normalize` function on necessary `Event` attributes to make them safe for serialization.
* Normalized keys:
* - `breadcrumbs.data`
* - `user`
* - `contexts`
* - `extra`
* @param event Event
* @returns Normalized event
*/
protected _normalizeEvent(event: Event | null, depth: number, maxBreadth: number): Event | null {
if (!event) {
return null;
}

const normalized: Event = {
...event,
...(event.breadcrumbs && {
breadcrumbs: event.breadcrumbs.map(b => ({
...b,
...(b.data && {
data: normalize(b.data, depth, maxBreadth),
}),
})),
}),
...(event.user && {
user: normalize(event.user, depth, maxBreadth),
}),
...(event.contexts && {
contexts: normalize(event.contexts, depth, maxBreadth),
}),
...(event.extra && {
extra: normalize(event.extra, depth, maxBreadth),
}),
};

// event.contexts.trace stores information about a Transaction. Similarly,
// event.spans[] stores information about child Spans. Given that a
// Transaction is conceptually a Span, normalization should apply to both
// Transactions and Spans consistently.
// For now the decision is to skip normalization of Transactions and Spans,
// so this block overwrites the normalized event to add back the original
// Transaction information prior to normalization.
if (event.contexts && event.contexts.trace && normalized.contexts) {
normalized.contexts.trace = event.contexts.trace;

// event.contexts.trace.data may contain circular/dangerous data so we need to normalize it
if (event.contexts.trace.data) {
normalized.contexts.trace.data = normalize(event.contexts.trace.data, depth, maxBreadth);
}
}

// event.spans[].data may contain circular/dangerous data so we need to normalize it
if (event.spans) {
normalized.spans = event.spans.map(span => {
// We cannot use the spread operator here because `toJSON` on `span` is non-enumerable
if (span.data) {
span.data = normalize(span.data, depth, maxBreadth);
}
return span;
});
}

return normalized;
}

/**
* Enhances event using the client configuration.
* It takes care of all "static" values like environment, release and `dist`,
* as well as truncating overly long values.
* @param event event instance to be enhanced
*/
protected _applyClientOptions(event: Event): void {
const options = this.getOptions();
const { environment, release, dist, maxValueLength = 250 } = options;

if (!('environment' in event)) {
event.environment = 'environment' in options ? environment : 'production';
}

if (event.release === undefined && release !== undefined) {
event.release = release;
}

if (event.dist === undefined && dist !== undefined) {
event.dist = dist;
}

if (event.message) {
event.message = truncate(event.message, maxValueLength);
}

const exception = event.exception && event.exception.values && event.exception.values[0];
if (exception && exception.value) {
exception.value = truncate(exception.value, maxValueLength);
}

const request = event.request;
if (request && request.url) {
request.url = truncate(request.url, maxValueLength);
}
}

/**
* This function adds all used integrations to the SDK info in the event.
* @param event The event that will be filled with all integrations.
*/
protected _applyIntegrationsMetadata(event: Event): void {
const integrationsArray = Object.keys(this._integrations);
if (integrationsArray.length > 0) {
event.sdk = event.sdk || {};
event.sdk.integrations = [...(event.sdk.integrations || []), ...integrationsArray];
}
return prepareEvent(options, event, hint, scope);
}

/**
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 @@ -27,6 +27,7 @@ export { createTransport } from './transports/base';
export { SDK_VERSION } from './version';
export { getIntegrationsToSetup } from './integration';
export { FunctionToString, InboundFilters } from './integrations';
export { prepareEvent } from './utils/prepareEvent';

import * as Integrations from './integrations';

Expand Down
Loading