events.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import {
  2. BaseGroup,
  3. EntryException,
  4. EntryThreads,
  5. EventMetadata,
  6. EventOrGroupType,
  7. GroupTombstone,
  8. IssueCategory,
  9. TreeLabelPart,
  10. } from 'sentry/types';
  11. import {Event} from 'sentry/types/event';
  12. import {isMobilePlatform, isNativePlatform} from 'sentry/utils/platform';
  13. function isTombstone(maybe: BaseGroup | Event | GroupTombstone): maybe is GroupTombstone {
  14. return !maybe.hasOwnProperty('type');
  15. }
  16. /**
  17. * Extract the display message from an event.
  18. */
  19. export function getMessage(
  20. event: Event | BaseGroup | GroupTombstone
  21. ): string | undefined {
  22. if (isTombstone(event)) {
  23. return event.culprit || '';
  24. }
  25. const {metadata, type, culprit} = event;
  26. switch (type) {
  27. case EventOrGroupType.ERROR:
  28. case EventOrGroupType.TRANSACTION:
  29. return metadata.value;
  30. case EventOrGroupType.CSP:
  31. return metadata.message;
  32. case EventOrGroupType.EXPECTCT:
  33. case EventOrGroupType.EXPECTSTAPLE:
  34. case EventOrGroupType.HPKP:
  35. return '';
  36. default:
  37. return culprit || '';
  38. }
  39. }
  40. /**
  41. * Get the location from an event.
  42. */
  43. export function getLocation(event: Event | BaseGroup | GroupTombstone) {
  44. if (isTombstone(event)) {
  45. return undefined;
  46. }
  47. if (event.type === EventOrGroupType.ERROR && isNativePlatform(event.platform)) {
  48. return event.metadata.filename || undefined;
  49. }
  50. return undefined;
  51. }
  52. export function getTreeLabelPartDetails(part: TreeLabelPart) {
  53. // Note: This function also exists in Python in eventtypes/base.py, to make
  54. // porting efforts simpler it's recommended to keep both variants
  55. // structurally similar.
  56. if (typeof part === 'string') {
  57. return part;
  58. }
  59. const label = part?.function || part?.package || part?.filebase || part?.type;
  60. const classbase = part?.classbase;
  61. if (classbase) {
  62. return label ? `${classbase}.${label}` : classbase;
  63. }
  64. return label || '<unknown>';
  65. }
  66. function computeTitleWithTreeLabel(metadata: EventMetadata) {
  67. const {type, current_tree_label, finest_tree_label} = metadata;
  68. const treeLabel = current_tree_label || finest_tree_label;
  69. const formattedTreeLabel = treeLabel
  70. ? treeLabel.map(labelPart => getTreeLabelPartDetails(labelPart)).join(' | ')
  71. : undefined;
  72. if (!type) {
  73. return {
  74. title: formattedTreeLabel || metadata.function || '<unknown>',
  75. treeLabel,
  76. };
  77. }
  78. if (!formattedTreeLabel) {
  79. return {title: type, treeLabel: undefined};
  80. }
  81. return {
  82. title: `${type} | ${formattedTreeLabel}`,
  83. treeLabel: [{type}, ...(treeLabel ?? [])],
  84. };
  85. }
  86. export function getTitle(
  87. event: Event | BaseGroup,
  88. features: string[] = [],
  89. grouping = false
  90. ) {
  91. const {metadata, type, culprit, title} = event;
  92. const customTitle =
  93. features.includes('custom-event-title') && metadata?.title
  94. ? metadata.title
  95. : undefined;
  96. switch (type) {
  97. case EventOrGroupType.ERROR: {
  98. if (customTitle) {
  99. return {
  100. title: customTitle,
  101. subtitle: culprit,
  102. treeLabel: undefined,
  103. };
  104. }
  105. const displayTitleWithTreeLabel =
  106. features.includes('grouping-title-ui') &&
  107. (grouping ||
  108. isNativePlatform(event.platform) ||
  109. isMobilePlatform(event.platform));
  110. if (displayTitleWithTreeLabel) {
  111. return {
  112. subtitle: culprit,
  113. ...computeTitleWithTreeLabel(metadata),
  114. };
  115. }
  116. return {
  117. subtitle: culprit,
  118. title: metadata.type || metadata.function || '<unknown>',
  119. treeLabel: undefined,
  120. };
  121. }
  122. case EventOrGroupType.CSP:
  123. return {
  124. title: customTitle ?? metadata.directive ?? '',
  125. subtitle: metadata.uri ?? '',
  126. treeLabel: undefined,
  127. };
  128. case EventOrGroupType.EXPECTCT:
  129. case EventOrGroupType.EXPECTSTAPLE:
  130. case EventOrGroupType.HPKP:
  131. // Due to a regression some reports did not have message persisted
  132. // (https://github.com/getsentry/sentry/pull/19794) so we need to fall
  133. // back to the computed title for these.
  134. return {
  135. title: customTitle ?? (metadata.message || title),
  136. subtitle: metadata.origin ?? '',
  137. treeLabel: undefined,
  138. };
  139. case EventOrGroupType.DEFAULT:
  140. return {
  141. title: customTitle ?? metadata.title ?? '',
  142. subtitle: '',
  143. treeLabel: undefined,
  144. };
  145. case EventOrGroupType.TRANSACTION:
  146. const isPerfIssue = event.issueCategory === IssueCategory.PERFORMANCE;
  147. return {
  148. title: isPerfIssue ? metadata.title : customTitle ?? title,
  149. subtitle: isPerfIssue ? culprit : '',
  150. treeLabel: undefined,
  151. };
  152. default:
  153. return {
  154. title: customTitle ?? title,
  155. subtitle: '',
  156. treeLabel: undefined,
  157. };
  158. }
  159. }
  160. /**
  161. * Returns a short eventId with only 8 characters
  162. */
  163. export function getShortEventId(eventId: string) {
  164. return eventId.substring(0, 8);
  165. }
  166. /**
  167. * Returns a comma delineated list of errors
  168. */
  169. function getEventErrorString(event: Event) {
  170. return event.errors?.map(error => error.type).join(',') || '';
  171. }
  172. function hasTrace(event: Event) {
  173. if (event.type !== 'error') {
  174. return false;
  175. }
  176. return !!event.contexts?.trace;
  177. }
  178. /**
  179. * Function to determine if an event has source maps
  180. */
  181. function eventHasSourceMaps(event: Event) {
  182. return event.entries?.some(entry => {
  183. return (
  184. entry.type === 'exception' &&
  185. entry.data.values?.some(value => !!value.rawStacktrace && !!value.stacktrace)
  186. );
  187. });
  188. }
  189. function getExceptionEntries(event: Event) {
  190. return event.entries?.filter(entry => entry.type === 'exception') as EntryException[];
  191. }
  192. function getNumberOfStackFrames(event: Event) {
  193. const entries = getExceptionEntries(event);
  194. // for each entry, go through each frame and get the max
  195. const frameLengths =
  196. entries?.map(entry =>
  197. (entry.data.values || []).reduce((best, exception) => {
  198. // find the max number of frames in this entry
  199. const frameCount = exception.stacktrace?.frames?.length || 0;
  200. return Math.max(best, frameCount);
  201. }, 0)
  202. ) || [];
  203. if (!frameLengths.length) {
  204. return 0;
  205. }
  206. return Math.max(...frameLengths);
  207. }
  208. function getNumberOfInAppStackFrames(event: Event) {
  209. const entries = getExceptionEntries(event);
  210. // for each entry, go through each frame
  211. const frameLengths =
  212. entries?.map(entry =>
  213. (entry.data.values || []).reduce((best, exception) => {
  214. // find the max number of frames in this entry
  215. const frames = exception.stacktrace?.frames?.filter(f => f.inApp) || [];
  216. return Math.max(best, frames.length);
  217. }, 0)
  218. ) || [];
  219. if (!frameLengths.length) {
  220. return 0;
  221. }
  222. return Math.max(...frameLengths);
  223. }
  224. function getNumberOfThreadsWithNames(event: Event) {
  225. const threadLengths =
  226. (
  227. (event.entries?.filter(entry => entry.type === 'threads') || []) as EntryThreads[]
  228. ).map(entry => entry.data?.values?.filter(thread => !!thread.name).length || 0) || [];
  229. if (!threadLengths.length) {
  230. return 0;
  231. }
  232. return Math.max(...threadLengths);
  233. }
  234. export function getAnalyicsDataForEvent(event?: Event) {
  235. return {
  236. event_id: event?.eventID || '-1',
  237. num_commits: event?.release?.commitCount || 0,
  238. num_stack_frames: event ? getNumberOfStackFrames(event) : 0,
  239. num_in_app_stack_frames: event ? getNumberOfInAppStackFrames(event) : 0,
  240. num_threads_with_names: event ? getNumberOfThreadsWithNames(event) : 0,
  241. event_platform: event?.platform,
  242. event_type: event?.type,
  243. has_release: !!event?.release,
  244. has_source_maps: event ? eventHasSourceMaps(event) : false,
  245. has_trace: event ? hasTrace(event) : false,
  246. has_commit: !!event?.release?.lastCommit,
  247. event_errors: event ? getEventErrorString(event) : '',
  248. sdk_name: event?.sdk?.name,
  249. sdk_version: event?.sdk?.version,
  250. };
  251. }