prepareEvent.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. const utils = require('@sentry/utils');
  3. const constants = require('../constants.js');
  4. const eventProcessors = require('../eventProcessors.js');
  5. const scope = require('../scope.js');
  6. const applyScopeDataToEvent = require('./applyScopeDataToEvent.js');
  7. const spanUtils = require('./spanUtils.js');
  8. /**
  9. * This type makes sure that we get either a CaptureContext, OR an EventHint.
  10. * It does not allow mixing them, which could lead to unexpected outcomes, e.g. this is disallowed:
  11. * { user: { id: '123' }, mechanism: { handled: false } }
  12. */
  13. /**
  14. * Adds common information to events.
  15. *
  16. * The information includes release and environment from `options`,
  17. * breadcrumbs and context (extra, tags and user) from the scope.
  18. *
  19. * Information that is already present in the event is never overwritten. For
  20. * nested objects, such as the context, keys are merged.
  21. *
  22. * Note: This also triggers callbacks for `addGlobalEventProcessor`, but not `beforeSend`.
  23. *
  24. * @param event The original event.
  25. * @param hint May contain additional information about the original exception.
  26. * @param scope A scope containing event metadata.
  27. * @returns A new event with more information.
  28. * @hidden
  29. */
  30. function prepareEvent(
  31. options,
  32. event,
  33. hint,
  34. scope$1,
  35. client,
  36. isolationScope,
  37. ) {
  38. const { normalizeDepth = 3, normalizeMaxBreadth = 1000 } = options;
  39. const prepared = {
  40. ...event,
  41. event_id: event.event_id || hint.event_id || utils.uuid4(),
  42. timestamp: event.timestamp || utils.dateTimestampInSeconds(),
  43. };
  44. const integrations = hint.integrations || options.integrations.map(i => i.name);
  45. applyClientOptions(prepared, options);
  46. applyIntegrationsMetadata(prepared, integrations);
  47. // Only put debug IDs onto frames for error events.
  48. if (event.type === undefined) {
  49. applyDebugIds(prepared, options.stackParser);
  50. }
  51. // If we have scope given to us, use it as the base for further modifications.
  52. // This allows us to prevent unnecessary copying of data if `captureContext` is not provided.
  53. const finalScope = getFinalScope(scope$1, hint.captureContext);
  54. if (hint.mechanism) {
  55. utils.addExceptionMechanism(prepared, hint.mechanism);
  56. }
  57. const clientEventProcessors = client && client.getEventProcessors ? client.getEventProcessors() : [];
  58. // This should be the last thing called, since we want that
  59. // {@link Hub.addEventProcessor} gets the finished prepared event.
  60. // Merge scope data together
  61. const data = scope.getGlobalScope().getScopeData();
  62. if (isolationScope) {
  63. const isolationData = isolationScope.getScopeData();
  64. applyScopeDataToEvent.mergeScopeData(data, isolationData);
  65. }
  66. if (finalScope) {
  67. const finalScopeData = finalScope.getScopeData();
  68. applyScopeDataToEvent.mergeScopeData(data, finalScopeData);
  69. }
  70. const attachments = [...(hint.attachments || []), ...data.attachments];
  71. if (attachments.length) {
  72. hint.attachments = attachments;
  73. }
  74. applyScopeDataToEvent.applyScopeDataToEvent(prepared, data);
  75. // TODO (v8): Update this order to be: Global > Client > Scope
  76. const eventProcessors$1 = [
  77. ...clientEventProcessors,
  78. // eslint-disable-next-line deprecation/deprecation
  79. ...eventProcessors.getGlobalEventProcessors(),
  80. // Run scope event processors _after_ all other processors
  81. ...data.eventProcessors,
  82. ];
  83. const result = eventProcessors.notifyEventProcessors(eventProcessors$1, prepared, hint);
  84. return result.then(evt => {
  85. if (evt) {
  86. // We apply the debug_meta field only after all event processors have ran, so that if any event processors modified
  87. // file names (e.g.the RewriteFrames integration) the filename -> debug ID relationship isn't destroyed.
  88. // This should not cause any PII issues, since we're only moving data that is already on the event and not adding
  89. // any new data
  90. applyDebugMeta(evt);
  91. }
  92. if (typeof normalizeDepth === 'number' && normalizeDepth > 0) {
  93. return normalizeEvent(evt, normalizeDepth, normalizeMaxBreadth);
  94. }
  95. return evt;
  96. });
  97. }
  98. /**
  99. * Enhances event using the client configuration.
  100. * It takes care of all "static" values like environment, release and `dist`,
  101. * as well as truncating overly long values.
  102. * @param event event instance to be enhanced
  103. */
  104. function applyClientOptions(event, options) {
  105. const { environment, release, dist, maxValueLength = 250 } = options;
  106. if (!('environment' in event)) {
  107. event.environment = 'environment' in options ? environment : constants.DEFAULT_ENVIRONMENT;
  108. }
  109. if (event.release === undefined && release !== undefined) {
  110. event.release = release;
  111. }
  112. if (event.dist === undefined && dist !== undefined) {
  113. event.dist = dist;
  114. }
  115. if (event.message) {
  116. event.message = utils.truncate(event.message, maxValueLength);
  117. }
  118. const exception = event.exception && event.exception.values && event.exception.values[0];
  119. if (exception && exception.value) {
  120. exception.value = utils.truncate(exception.value, maxValueLength);
  121. }
  122. const request = event.request;
  123. if (request && request.url) {
  124. request.url = utils.truncate(request.url, maxValueLength);
  125. }
  126. }
  127. const debugIdStackParserCache = new WeakMap();
  128. /**
  129. * Puts debug IDs into the stack frames of an error event.
  130. */
  131. function applyDebugIds(event, stackParser) {
  132. const debugIdMap = utils.GLOBAL_OBJ._sentryDebugIds;
  133. if (!debugIdMap) {
  134. return;
  135. }
  136. let debugIdStackFramesCache;
  137. const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
  138. if (cachedDebugIdStackFrameCache) {
  139. debugIdStackFramesCache = cachedDebugIdStackFrameCache;
  140. } else {
  141. debugIdStackFramesCache = new Map();
  142. debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
  143. }
  144. // Build a map of filename -> debug_id
  145. const filenameDebugIdMap = Object.keys(debugIdMap).reduce((acc, debugIdStackTrace) => {
  146. let parsedStack;
  147. const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
  148. if (cachedParsedStack) {
  149. parsedStack = cachedParsedStack;
  150. } else {
  151. parsedStack = stackParser(debugIdStackTrace);
  152. debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
  153. }
  154. for (let i = parsedStack.length - 1; i >= 0; i--) {
  155. const stackFrame = parsedStack[i];
  156. if (stackFrame.filename) {
  157. acc[stackFrame.filename] = debugIdMap[debugIdStackTrace];
  158. break;
  159. }
  160. }
  161. return acc;
  162. }, {});
  163. try {
  164. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  165. event.exception.values.forEach(exception => {
  166. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  167. exception.stacktrace.frames.forEach(frame => {
  168. if (frame.filename) {
  169. frame.debug_id = filenameDebugIdMap[frame.filename];
  170. }
  171. });
  172. });
  173. } catch (e) {
  174. // To save bundle size we're just try catching here instead of checking for the existence of all the different objects.
  175. }
  176. }
  177. /**
  178. * Moves debug IDs from the stack frames of an error event into the debug_meta field.
  179. */
  180. function applyDebugMeta(event) {
  181. // Extract debug IDs and filenames from the stack frames on the event.
  182. const filenameDebugIdMap = {};
  183. try {
  184. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  185. event.exception.values.forEach(exception => {
  186. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  187. exception.stacktrace.frames.forEach(frame => {
  188. if (frame.debug_id) {
  189. if (frame.abs_path) {
  190. filenameDebugIdMap[frame.abs_path] = frame.debug_id;
  191. } else if (frame.filename) {
  192. filenameDebugIdMap[frame.filename] = frame.debug_id;
  193. }
  194. delete frame.debug_id;
  195. }
  196. });
  197. });
  198. } catch (e) {
  199. // To save bundle size we're just try catching here instead of checking for the existence of all the different objects.
  200. }
  201. if (Object.keys(filenameDebugIdMap).length === 0) {
  202. return;
  203. }
  204. // Fill debug_meta information
  205. event.debug_meta = event.debug_meta || {};
  206. event.debug_meta.images = event.debug_meta.images || [];
  207. const images = event.debug_meta.images;
  208. Object.keys(filenameDebugIdMap).forEach(filename => {
  209. images.push({
  210. type: 'sourcemap',
  211. code_file: filename,
  212. debug_id: filenameDebugIdMap[filename],
  213. });
  214. });
  215. }
  216. /**
  217. * This function adds all used integrations to the SDK info in the event.
  218. * @param event The event that will be filled with all integrations.
  219. */
  220. function applyIntegrationsMetadata(event, integrationNames) {
  221. if (integrationNames.length > 0) {
  222. event.sdk = event.sdk || {};
  223. event.sdk.integrations = [...(event.sdk.integrations || []), ...integrationNames];
  224. }
  225. }
  226. /**
  227. * Applies `normalize` function on necessary `Event` attributes to make them safe for serialization.
  228. * Normalized keys:
  229. * - `breadcrumbs.data`
  230. * - `user`
  231. * - `contexts`
  232. * - `extra`
  233. * @param event Event
  234. * @returns Normalized event
  235. */
  236. function normalizeEvent(event, depth, maxBreadth) {
  237. if (!event) {
  238. return null;
  239. }
  240. const normalized = {
  241. ...event,
  242. ...(event.breadcrumbs && {
  243. breadcrumbs: event.breadcrumbs.map(b => ({
  244. ...b,
  245. ...(b.data && {
  246. data: utils.normalize(b.data, depth, maxBreadth),
  247. }),
  248. })),
  249. }),
  250. ...(event.user && {
  251. user: utils.normalize(event.user, depth, maxBreadth),
  252. }),
  253. ...(event.contexts && {
  254. contexts: utils.normalize(event.contexts, depth, maxBreadth),
  255. }),
  256. ...(event.extra && {
  257. extra: utils.normalize(event.extra, depth, maxBreadth),
  258. }),
  259. };
  260. // event.contexts.trace stores information about a Transaction. Similarly,
  261. // event.spans[] stores information about child Spans. Given that a
  262. // Transaction is conceptually a Span, normalization should apply to both
  263. // Transactions and Spans consistently.
  264. // For now the decision is to skip normalization of Transactions and Spans,
  265. // so this block overwrites the normalized event to add back the original
  266. // Transaction information prior to normalization.
  267. if (event.contexts && event.contexts.trace && normalized.contexts) {
  268. normalized.contexts.trace = event.contexts.trace;
  269. // event.contexts.trace.data may contain circular/dangerous data so we need to normalize it
  270. if (event.contexts.trace.data) {
  271. normalized.contexts.trace.data = utils.normalize(event.contexts.trace.data, depth, maxBreadth);
  272. }
  273. }
  274. // event.spans[].data may contain circular/dangerous data so we need to normalize it
  275. if (event.spans) {
  276. normalized.spans = event.spans.map(span => {
  277. const data = spanUtils.spanToJSON(span).data;
  278. if (data) {
  279. // This is a bit weird, as we generally have `Span` instances here, but to be safe we do not assume so
  280. // eslint-disable-next-line deprecation/deprecation
  281. span.data = utils.normalize(data, depth, maxBreadth);
  282. }
  283. return span;
  284. });
  285. }
  286. return normalized;
  287. }
  288. function getFinalScope(scope$1, captureContext) {
  289. if (!captureContext) {
  290. return scope$1;
  291. }
  292. const finalScope = scope$1 ? scope$1.clone() : new scope.Scope();
  293. finalScope.update(captureContext);
  294. return finalScope;
  295. }
  296. /**
  297. * Parse either an `EventHint` directly, or convert a `CaptureContext` to an `EventHint`.
  298. * This is used to allow to update method signatures that used to accept a `CaptureContext` but should now accept an `EventHint`.
  299. */
  300. function parseEventHintOrCaptureContext(
  301. hint,
  302. ) {
  303. if (!hint) {
  304. return undefined;
  305. }
  306. // If you pass a Scope or `() => Scope` as CaptureContext, we just return this as captureContext
  307. if (hintIsScopeOrFunction(hint)) {
  308. return { captureContext: hint };
  309. }
  310. if (hintIsScopeContext(hint)) {
  311. return {
  312. captureContext: hint,
  313. };
  314. }
  315. return hint;
  316. }
  317. function hintIsScopeOrFunction(
  318. hint,
  319. ) {
  320. return hint instanceof scope.Scope || typeof hint === 'function';
  321. }
  322. const captureContextKeys = [
  323. 'user',
  324. 'level',
  325. 'extra',
  326. 'contexts',
  327. 'tags',
  328. 'fingerprint',
  329. 'requestSession',
  330. 'propagationContext',
  331. ] ;
  332. function hintIsScopeContext(hint) {
  333. return Object.keys(hint).some(key => captureContextKeys.includes(key ));
  334. }
  335. exports.applyDebugIds = applyDebugIds;
  336. exports.applyDebugMeta = applyDebugMeta;
  337. exports.parseEventHintOrCaptureContext = parseEventHintOrCaptureContext;
  338. exports.prepareEvent = prepareEvent;
  339. //# sourceMappingURL=prepareEvent.js.map