-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(core): Make beforeSend
handling defensive for different event types
#6507
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
Changes from all commits
09e1f22
ae00784
bad6070
a0a8734
fffe589
d311826
50d837e
da8d8c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import { | |
DataCategory, | ||
DsnComponents, | ||
Envelope, | ||
ErrorEvent, | ||
Event, | ||
EventDropReason, | ||
EventHint, | ||
|
@@ -15,6 +16,7 @@ import { | |
SessionAggregates, | ||
Severity, | ||
SeverityLevel, | ||
TransactionEvent, | ||
Transport, | ||
} from '@sentry/types'; | ||
import { | ||
|
@@ -627,14 +629,15 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> { | |
return rejectedSyncPromise(new SentryError('SDK not enabled, will not capture event.', 'log')); | ||
} | ||
|
||
const isTransaction = event.type === 'transaction'; | ||
const beforeSendProcessorName = isTransaction ? 'beforeSendTransaction' : 'beforeSend'; | ||
const beforeSendProcessor = options[beforeSendProcessorName]; | ||
const isTransaction = isTransactionEvent(event); | ||
const isError = isErrorEvent(event); | ||
const eventType = event.type || 'error'; | ||
const beforeSendLabel = `before send for type \`${eventType}\``; | ||
|
||
// 1.0 === 100% events are sent | ||
// 0.0 === 0% events are sent | ||
// Sampling for transaction happens somewhere else | ||
if (!isTransaction && typeof sampleRate === 'number' && Math.random() > sampleRate) { | ||
if (isError && typeof sampleRate === 'number' && Math.random() > sampleRate) { | ||
this.recordDroppedEvent('sample_rate', 'error', event); | ||
return rejectedSyncPromise( | ||
new SentryError( | ||
|
@@ -647,22 +650,22 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> { | |
return this._prepareEvent(event, hint, scope) | ||
.then(prepared => { | ||
if (prepared === null) { | ||
this.recordDroppedEvent('event_processor', event.type || 'error', event); | ||
this.recordDroppedEvent('event_processor', eventType, event); | ||
throw new SentryError('An event processor returned `null`, will not send event.', 'log'); | ||
} | ||
|
||
const isInternalException = hint.data && (hint.data as { __sentry__: boolean }).__sentry__ === true; | ||
if (isInternalException || !beforeSendProcessor) { | ||
if (isInternalException) { | ||
return prepared; | ||
} | ||
|
||
const beforeSendResult = beforeSendProcessor(prepared, hint); | ||
return _validateBeforeSendResult(beforeSendResult, beforeSendProcessorName); | ||
const result = processBeforeSend(options, prepared, hint); | ||
return _validateBeforeSendResult(result, beforeSendLabel); | ||
}) | ||
.then(processedEvent => { | ||
if (processedEvent === null) { | ||
this.recordDroppedEvent('before_send', event.type || 'error', event); | ||
throw new SentryError(`\`${beforeSendProcessorName}\` returned \`null\`, will not send event.`, 'log'); | ||
throw new SentryError(`${beforeSendLabel} returned \`null\`, will not send event.`, 'log'); | ||
} | ||
|
||
const session = scope && scope.getSession(); | ||
|
@@ -779,9 +782,9 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> { | |
*/ | ||
function _validateBeforeSendResult( | ||
beforeSendResult: PromiseLike<Event | null> | Event | null, | ||
beforeSendProcessorName: 'beforeSend' | 'beforeSendTransaction', | ||
beforeSendLabel: string, | ||
): PromiseLike<Event | null> | Event | null { | ||
const invalidValueError = `\`${beforeSendProcessorName}\` must return \`null\` or a valid event.`; | ||
const invalidValueError = `${beforeSendLabel} must return \`null\` or a valid event.`; | ||
if (isThenable(beforeSendResult)) { | ||
return beforeSendResult.then( | ||
event => { | ||
|
@@ -791,11 +794,40 @@ function _validateBeforeSendResult( | |
return event; | ||
}, | ||
e => { | ||
throw new SentryError(`\`${beforeSendProcessorName}\` rejected with ${e}`); | ||
throw new SentryError(`${beforeSendLabel} rejected with ${e}`); | ||
}, | ||
); | ||
} else if (!isPlainObject(beforeSendResult) && beforeSendResult !== null) { | ||
throw new SentryError(invalidValueError); | ||
} | ||
return beforeSendResult; | ||
} | ||
|
||
/** | ||
* Process the matching `beforeSendXXX` callback. | ||
*/ | ||
function processBeforeSend( | ||
options: ClientOptions, | ||
event: Event, | ||
hint: EventHint, | ||
): PromiseLike<Event | null> | Event | null { | ||
const { beforeSend, beforeSendTransaction } = options; | ||
|
||
if (isErrorEvent(event) && beforeSend) { | ||
return beforeSend(event, hint); | ||
} | ||
|
||
if (isTransactionEvent(event) && beforeSendTransaction) { | ||
return beforeSendTransaction(event, hint); | ||
} | ||
|
||
return event; | ||
} | ||
|
||
function isErrorEvent(event: Event): event is ErrorEvent { | ||
return event.type === undefined; | ||
} | ||
|
||
function isTransactionEvent(event: Event): event is TransactionEvent { | ||
return event.type === 'transaction'; | ||
} | ||
Comment on lines
+827
to
+833
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Random thought: We have this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, not sure, that could be confusing, as there it is a lot about built ins etc. there is actually also this in there: export function isErrorEvent(wat: unknown): boolean {
return isBuiltin(wat, 'ErrorEvent');
} but that checks something else I guess 😅 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not completely sure about this... I figured this is the most "compact" solution. Other options:
type === 'profile'
, what should this be?beforeSend${event.type ? capitzalize(event.type) : ''}
- same issue as before, this would becomebeforeSendProfile
which doesn't really exist as of now I guess.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this approach is fine