eventbuilder.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import { getClient } from '@sentry/core';
  2. import { addExceptionMechanism, resolvedSyncPromise, isErrorEvent, isDOMError, isDOMException, addExceptionTypeValue, isError, isPlainObject, isEvent, isParameterizedString, normalizeToSize, extractExceptionKeysForMessage } from '@sentry/utils';
  3. /**
  4. * This function creates an exception from a JavaScript Error
  5. */
  6. function exceptionFromError(stackParser, ex) {
  7. // Get the frames first since Opera can lose the stack if we touch anything else first
  8. const frames = parseStackFrames(stackParser, ex);
  9. const exception = {
  10. type: ex && ex.name,
  11. value: extractMessage(ex),
  12. };
  13. if (frames.length) {
  14. exception.stacktrace = { frames };
  15. }
  16. if (exception.type === undefined && exception.value === '') {
  17. exception.value = 'Unrecoverable error caught';
  18. }
  19. return exception;
  20. }
  21. /**
  22. * @hidden
  23. */
  24. function eventFromPlainObject(
  25. stackParser,
  26. exception,
  27. syntheticException,
  28. isUnhandledRejection,
  29. ) {
  30. const client = getClient();
  31. const normalizeDepth = client && client.getOptions().normalizeDepth;
  32. const event = {
  33. exception: {
  34. values: [
  35. {
  36. type: isEvent(exception) ? exception.constructor.name : isUnhandledRejection ? 'UnhandledRejection' : 'Error',
  37. value: getNonErrorObjectExceptionValue(exception, { isUnhandledRejection }),
  38. },
  39. ],
  40. },
  41. extra: {
  42. __serialized__: normalizeToSize(exception, normalizeDepth),
  43. },
  44. };
  45. if (syntheticException) {
  46. const frames = parseStackFrames(stackParser, syntheticException);
  47. if (frames.length) {
  48. // event.exception.values[0] has been set above
  49. (event.exception ).values[0].stacktrace = { frames };
  50. }
  51. }
  52. return event;
  53. }
  54. /**
  55. * @hidden
  56. */
  57. function eventFromError(stackParser, ex) {
  58. return {
  59. exception: {
  60. values: [exceptionFromError(stackParser, ex)],
  61. },
  62. };
  63. }
  64. /** Parses stack frames from an error */
  65. function parseStackFrames(
  66. stackParser,
  67. ex,
  68. ) {
  69. // Access and store the stacktrace property before doing ANYTHING
  70. // else to it because Opera is not very good at providing it
  71. // reliably in other circumstances.
  72. const stacktrace = ex.stacktrace || ex.stack || '';
  73. const popSize = getPopSize(ex);
  74. try {
  75. return stackParser(stacktrace, popSize);
  76. } catch (e) {
  77. // no-empty
  78. }
  79. return [];
  80. }
  81. // Based on our own mapping pattern - https://github.com/getsentry/sentry/blob/9f08305e09866c8bd6d0c24f5b0aabdd7dd6c59c/src/sentry/lang/javascript/errormapping.py#L83-L108
  82. const reactMinifiedRegexp = /Minified React error #\d+;/i;
  83. function getPopSize(ex) {
  84. if (ex) {
  85. if (typeof ex.framesToPop === 'number') {
  86. return ex.framesToPop;
  87. }
  88. if (reactMinifiedRegexp.test(ex.message)) {
  89. return 1;
  90. }
  91. }
  92. return 0;
  93. }
  94. /**
  95. * There are cases where stacktrace.message is an Event object
  96. * https://github.com/getsentry/sentry-javascript/issues/1949
  97. * In this specific case we try to extract stacktrace.message.error.message
  98. */
  99. function extractMessage(ex) {
  100. const message = ex && ex.message;
  101. if (!message) {
  102. return 'No error message';
  103. }
  104. if (message.error && typeof message.error.message === 'string') {
  105. return message.error.message;
  106. }
  107. return message;
  108. }
  109. /**
  110. * Creates an {@link Event} from all inputs to `captureException` and non-primitive inputs to `captureMessage`.
  111. * @hidden
  112. */
  113. function eventFromException(
  114. stackParser,
  115. exception,
  116. hint,
  117. attachStacktrace,
  118. ) {
  119. const syntheticException = (hint && hint.syntheticException) || undefined;
  120. const event = eventFromUnknownInput(stackParser, exception, syntheticException, attachStacktrace);
  121. addExceptionMechanism(event); // defaults to { type: 'generic', handled: true }
  122. event.level = 'error';
  123. if (hint && hint.event_id) {
  124. event.event_id = hint.event_id;
  125. }
  126. return resolvedSyncPromise(event);
  127. }
  128. /**
  129. * Builds and Event from a Message
  130. * @hidden
  131. */
  132. function eventFromMessage(
  133. stackParser,
  134. message,
  135. // eslint-disable-next-line deprecation/deprecation
  136. level = 'info',
  137. hint,
  138. attachStacktrace,
  139. ) {
  140. const syntheticException = (hint && hint.syntheticException) || undefined;
  141. const event = eventFromString(stackParser, message, syntheticException, attachStacktrace);
  142. event.level = level;
  143. if (hint && hint.event_id) {
  144. event.event_id = hint.event_id;
  145. }
  146. return resolvedSyncPromise(event);
  147. }
  148. /**
  149. * @hidden
  150. */
  151. function eventFromUnknownInput(
  152. stackParser,
  153. exception,
  154. syntheticException,
  155. attachStacktrace,
  156. isUnhandledRejection,
  157. ) {
  158. let event;
  159. if (isErrorEvent(exception ) && (exception ).error) {
  160. // If it is an ErrorEvent with `error` property, extract it to get actual Error
  161. const errorEvent = exception ;
  162. return eventFromError(stackParser, errorEvent.error );
  163. }
  164. // If it is a `DOMError` (which is a legacy API, but still supported in some browsers) then we just extract the name
  165. // and message, as it doesn't provide anything else. According to the spec, all `DOMExceptions` should also be
  166. // `Error`s, but that's not the case in IE11, so in that case we treat it the same as we do a `DOMError`.
  167. //
  168. // https://developer.mozilla.org/en-US/docs/Web/API/DOMError
  169. // https://developer.mozilla.org/en-US/docs/Web/API/DOMException
  170. // https://webidl.spec.whatwg.org/#es-DOMException-specialness
  171. if (isDOMError(exception) || isDOMException(exception )) {
  172. const domException = exception ;
  173. if ('stack' in (exception )) {
  174. event = eventFromError(stackParser, exception );
  175. } else {
  176. const name = domException.name || (isDOMError(domException) ? 'DOMError' : 'DOMException');
  177. const message = domException.message ? `${name}: ${domException.message}` : name;
  178. event = eventFromString(stackParser, message, syntheticException, attachStacktrace);
  179. addExceptionTypeValue(event, message);
  180. }
  181. if ('code' in domException) {
  182. // eslint-disable-next-line deprecation/deprecation
  183. event.tags = { ...event.tags, 'DOMException.code': `${domException.code}` };
  184. }
  185. return event;
  186. }
  187. if (isError(exception)) {
  188. // we have a real Error object, do nothing
  189. return eventFromError(stackParser, exception);
  190. }
  191. if (isPlainObject(exception) || isEvent(exception)) {
  192. // If it's a plain object or an instance of `Event` (the built-in JS kind, not this SDK's `Event` type), serialize
  193. // it manually. This will allow us to group events based on top-level keys which is much better than creating a new
  194. // group on any key/value change.
  195. const objectException = exception ;
  196. event = eventFromPlainObject(stackParser, objectException, syntheticException, isUnhandledRejection);
  197. addExceptionMechanism(event, {
  198. synthetic: true,
  199. });
  200. return event;
  201. }
  202. // If none of previous checks were valid, then it means that it's not:
  203. // - an instance of DOMError
  204. // - an instance of DOMException
  205. // - an instance of Event
  206. // - an instance of Error
  207. // - a valid ErrorEvent (one with an error property)
  208. // - a plain Object
  209. //
  210. // So bail out and capture it as a simple message:
  211. event = eventFromString(stackParser, exception , syntheticException, attachStacktrace);
  212. addExceptionTypeValue(event, `${exception}`, undefined);
  213. addExceptionMechanism(event, {
  214. synthetic: true,
  215. });
  216. return event;
  217. }
  218. /**
  219. * @hidden
  220. */
  221. function eventFromString(
  222. stackParser,
  223. message,
  224. syntheticException,
  225. attachStacktrace,
  226. ) {
  227. const event = {};
  228. if (attachStacktrace && syntheticException) {
  229. const frames = parseStackFrames(stackParser, syntheticException);
  230. if (frames.length) {
  231. event.exception = {
  232. values: [{ value: message, stacktrace: { frames } }],
  233. };
  234. }
  235. }
  236. if (isParameterizedString(message)) {
  237. const { __sentry_template_string__, __sentry_template_values__ } = message;
  238. event.logentry = {
  239. message: __sentry_template_string__,
  240. params: __sentry_template_values__,
  241. };
  242. return event;
  243. }
  244. event.message = message;
  245. return event;
  246. }
  247. function getNonErrorObjectExceptionValue(
  248. exception,
  249. { isUnhandledRejection },
  250. ) {
  251. const keys = extractExceptionKeysForMessage(exception);
  252. const captureType = isUnhandledRejection ? 'promise rejection' : 'exception';
  253. // Some ErrorEvent instances do not have an `error` property, which is why they are not handled before
  254. // We still want to try to get a decent message for these cases
  255. if (isErrorEvent(exception)) {
  256. return `Event \`ErrorEvent\` captured as ${captureType} with message \`${exception.message}\``;
  257. }
  258. if (isEvent(exception)) {
  259. const className = getObjectClassName(exception);
  260. return `Event \`${className}\` (type=${exception.type}) captured as ${captureType}`;
  261. }
  262. return `Object captured as ${captureType} with keys: ${keys}`;
  263. }
  264. function getObjectClassName(obj) {
  265. try {
  266. const prototype = Object.getPrototypeOf(obj);
  267. return prototype ? prototype.constructor.name : undefined;
  268. } catch (e) {
  269. // ignore errors here
  270. }
  271. }
  272. export { eventFromError, eventFromException, eventFromMessage, eventFromPlainObject, eventFromString, eventFromUnknownInput, exceptionFromError, parseStackFrames };
  273. //# sourceMappingURL=eventbuilder.js.map