package.build.npm.esm.eventbuilder.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of browser Show documentation
Show all versions of browser Show documentation
Official Sentry SDK for browsers
import { getClient } from '@sentry/core';
import { addExceptionMechanism, resolvedSyncPromise, isErrorEvent, isDOMError, isDOMException, addExceptionTypeValue, isError, isPlainObject, isEvent, isParameterizedString, normalizeToSize, extractExceptionKeysForMessage } from '@sentry/utils';
/**
* This function creates an exception from a JavaScript Error
*/
function exceptionFromError(stackParser, ex) {
// Get the frames first since Opera can lose the stack if we touch anything else first
const frames = parseStackFrames(stackParser, ex);
const exception = {
type: ex && ex.name,
value: extractMessage(ex),
};
if (frames.length) {
exception.stacktrace = { frames };
}
if (exception.type === undefined && exception.value === '') {
exception.value = 'Unrecoverable error caught';
}
return exception;
}
function eventFromPlainObject(
stackParser,
exception,
syntheticException,
isUnhandledRejection,
) {
const client = getClient();
const normalizeDepth = client && client.getOptions().normalizeDepth;
// If we can, we extract an exception from the object properties
const errorFromProp = getErrorPropertyFromObject(exception);
const extra = {
__serialized__: normalizeToSize(exception, normalizeDepth),
};
if (errorFromProp) {
return {
exception: {
values: [exceptionFromError(stackParser, errorFromProp)],
},
extra,
};
}
const event = {
exception: {
values: [
{
type: isEvent(exception) ? exception.constructor.name : isUnhandledRejection ? 'UnhandledRejection' : 'Error',
value: getNonErrorObjectExceptionValue(exception, { isUnhandledRejection }),
} ,
],
},
extra,
} ;
if (syntheticException) {
const frames = parseStackFrames(stackParser, syntheticException);
if (frames.length) {
// event.exception.values[0] has been set above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
event.exception.values[0].stacktrace = { frames };
}
}
return event;
}
function eventFromError(stackParser, ex) {
return {
exception: {
values: [exceptionFromError(stackParser, ex)],
},
};
}
/** Parses stack frames from an error */
function parseStackFrames(
stackParser,
ex,
) {
// Access and store the stacktrace property before doing ANYTHING
// else to it because Opera is not very good at providing it
// reliably in other circumstances.
const stacktrace = ex.stacktrace || ex.stack || '';
const skipLines = getSkipFirstStackStringLines(ex);
const framesToPop = getPopFirstTopFrames(ex);
try {
return stackParser(stacktrace, skipLines, framesToPop);
} catch (e) {
// no-empty
}
return [];
}
// Based on our own mapping pattern - https://github.com/getsentry/sentry/blob/9f08305e09866c8bd6d0c24f5b0aabdd7dd6c59c/src/sentry/lang/javascript/errormapping.py#L83-L108
const reactMinifiedRegexp = /Minified React error #\d+;/i;
/**
* Certain known React errors contain links that would be falsely
* parsed as frames. This function check for these errors and
* returns number of the stack string lines to skip.
*/
function getSkipFirstStackStringLines(ex) {
if (ex && reactMinifiedRegexp.test(ex.message)) {
return 1;
}
return 0;
}
/**
* If error has `framesToPop` property, it means that the
* creator tells us the first x frames will be useless
* and should be discarded. Typically error from wrapper function
* which don't point to the actual location in the developer's code.
*
* Example: https://github.com/zertosh/invariant/blob/master/invariant.js#L46
*/
function getPopFirstTopFrames(ex) {
if (typeof ex.framesToPop === 'number') {
return ex.framesToPop;
}
return 0;
}
/**
* There are cases where stacktrace.message is an Event object
* https://github.com/getsentry/sentry-javascript/issues/1949
* In this specific case we try to extract stacktrace.message.error.message
*/
function extractMessage(ex) {
const message = ex && ex.message;
if (!message) {
return 'No error message';
}
if (message.error && typeof message.error.message === 'string') {
return message.error.message;
}
return message;
}
/**
* Creates an {@link Event} from all inputs to `captureException` and non-primitive inputs to `captureMessage`.
* @hidden
*/
function eventFromException(
stackParser,
exception,
hint,
attachStacktrace,
) {
const syntheticException = (hint && hint.syntheticException) || undefined;
const event = eventFromUnknownInput(stackParser, exception, syntheticException, attachStacktrace);
addExceptionMechanism(event); // defaults to { type: 'generic', handled: true }
event.level = 'error';
if (hint && hint.event_id) {
event.event_id = hint.event_id;
}
return resolvedSyncPromise(event);
}
/**
* Builds and Event from a Message
* @hidden
*/
function eventFromMessage(
stackParser,
message,
level = 'info',
hint,
attachStacktrace,
) {
const syntheticException = (hint && hint.syntheticException) || undefined;
const event = eventFromString(stackParser, message, syntheticException, attachStacktrace);
event.level = level;
if (hint && hint.event_id) {
event.event_id = hint.event_id;
}
return resolvedSyncPromise(event);
}
/**
* @hidden
*/
function eventFromUnknownInput(
stackParser,
exception,
syntheticException,
attachStacktrace,
isUnhandledRejection,
) {
let event;
if (isErrorEvent(exception ) && (exception ).error) {
// If it is an ErrorEvent with `error` property, extract it to get actual Error
const errorEvent = exception ;
return eventFromError(stackParser, errorEvent.error );
}
// If it is a `DOMError` (which is a legacy API, but still supported in some browsers) then we just extract the name
// and message, as it doesn't provide anything else. According to the spec, all `DOMExceptions` should also be
// `Error`s, but that's not the case in IE11, so in that case we treat it the same as we do a `DOMError`.
//
// https://developer.mozilla.org/en-US/docs/Web/API/DOMError
// https://developer.mozilla.org/en-US/docs/Web/API/DOMException
// https://webidl.spec.whatwg.org/#es-DOMException-specialness
if (isDOMError(exception) || isDOMException(exception )) {
const domException = exception ;
if ('stack' in (exception )) {
event = eventFromError(stackParser, exception );
} else {
const name = domException.name || (isDOMError(domException) ? 'DOMError' : 'DOMException');
const message = domException.message ? `${name}: ${domException.message}` : name;
event = eventFromString(stackParser, message, syntheticException, attachStacktrace);
addExceptionTypeValue(event, message);
}
if ('code' in domException) {
// eslint-disable-next-line deprecation/deprecation
event.tags = { ...event.tags, 'DOMException.code': `${domException.code}` };
}
return event;
}
if (isError(exception)) {
// we have a real Error object, do nothing
return eventFromError(stackParser, exception);
}
if (isPlainObject(exception) || isEvent(exception)) {
// If it's a plain object or an instance of `Event` (the built-in JS kind, not this SDK's `Event` type), serialize
// it manually. This will allow us to group events based on top-level keys which is much better than creating a new
// group on any key/value change.
const objectException = exception ;
event = eventFromPlainObject(stackParser, objectException, syntheticException, isUnhandledRejection);
addExceptionMechanism(event, {
synthetic: true,
});
return event;
}
// If none of previous checks were valid, then it means that it's not:
// - an instance of DOMError
// - an instance of DOMException
// - an instance of Event
// - an instance of Error
// - a valid ErrorEvent (one with an error property)
// - a plain Object
//
// So bail out and capture it as a simple message:
event = eventFromString(stackParser, exception , syntheticException, attachStacktrace);
addExceptionTypeValue(event, `${exception}`, undefined);
addExceptionMechanism(event, {
synthetic: true,
});
return event;
}
function eventFromString(
stackParser,
message,
syntheticException,
attachStacktrace,
) {
const event = {};
if (attachStacktrace && syntheticException) {
const frames = parseStackFrames(stackParser, syntheticException);
if (frames.length) {
event.exception = {
values: [{ value: message, stacktrace: { frames } }],
};
}
}
if (isParameterizedString(message)) {
const { __sentry_template_string__, __sentry_template_values__ } = message;
event.logentry = {
message: __sentry_template_string__,
params: __sentry_template_values__,
};
return event;
}
event.message = message;
return event;
}
function getNonErrorObjectExceptionValue(
exception,
{ isUnhandledRejection },
) {
const keys = extractExceptionKeysForMessage(exception);
const captureType = isUnhandledRejection ? 'promise rejection' : 'exception';
// Some ErrorEvent instances do not have an `error` property, which is why they are not handled before
// We still want to try to get a decent message for these cases
if (isErrorEvent(exception)) {
return `Event \`ErrorEvent\` captured as ${captureType} with message \`${exception.message}\``;
}
if (isEvent(exception)) {
const className = getObjectClassName(exception);
return `Event \`${className}\` (type=${exception.type}) captured as ${captureType}`;
}
return `Object captured as ${captureType} with keys: ${keys}`;
}
function getObjectClassName(obj) {
try {
const prototype = Object.getPrototypeOf(obj);
return prototype ? prototype.constructor.name : undefined;
} catch (e) {
// ignore errors here
}
}
/** If a plain object has a property that is an `Error`, return this error. */
function getErrorPropertyFromObject(obj) {
for (const prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
const value = obj[prop];
if (value instanceof Error) {
return value;
}
}
}
return undefined;
}
export { eventFromException, eventFromMessage, eventFromUnknownInput, exceptionFromError };
//# sourceMappingURL=eventbuilder.js.map