breadcrumbs.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. const core = require('@sentry/core');
  3. const utils = require('@sentry/utils');
  4. const debugBuild = require('../debug-build.js');
  5. const helpers = require('../helpers.js');
  6. /* eslint-disable max-lines */
  7. /** maxStringLength gets capped to prevent 100 breadcrumbs exceeding 1MB event payload size */
  8. const MAX_ALLOWED_STRING_LENGTH = 1024;
  9. const INTEGRATION_NAME = 'Breadcrumbs';
  10. const _breadcrumbsIntegration = ((options = {}) => {
  11. const _options = {
  12. console: true,
  13. dom: true,
  14. fetch: true,
  15. history: true,
  16. sentry: true,
  17. xhr: true,
  18. ...options,
  19. };
  20. return {
  21. name: INTEGRATION_NAME,
  22. // TODO v8: Remove this
  23. setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function
  24. setup(client) {
  25. if (_options.console) {
  26. utils.addConsoleInstrumentationHandler(_getConsoleBreadcrumbHandler(client));
  27. }
  28. if (_options.dom) {
  29. utils.addClickKeypressInstrumentationHandler(_getDomBreadcrumbHandler(client, _options.dom));
  30. }
  31. if (_options.xhr) {
  32. utils.addXhrInstrumentationHandler(_getXhrBreadcrumbHandler(client));
  33. }
  34. if (_options.fetch) {
  35. utils.addFetchInstrumentationHandler(_getFetchBreadcrumbHandler(client));
  36. }
  37. if (_options.history) {
  38. utils.addHistoryInstrumentationHandler(_getHistoryBreadcrumbHandler(client));
  39. }
  40. if (_options.sentry && client.on) {
  41. client.on('beforeSendEvent', _getSentryBreadcrumbHandler(client));
  42. }
  43. },
  44. };
  45. }) ;
  46. const breadcrumbsIntegration = core.defineIntegration(_breadcrumbsIntegration);
  47. /**
  48. * Default Breadcrumbs instrumentations
  49. *
  50. * @deprecated Use `breadcrumbsIntegration()` instead.
  51. */
  52. // eslint-disable-next-line deprecation/deprecation
  53. const Breadcrumbs = core.convertIntegrationFnToClass(INTEGRATION_NAME, breadcrumbsIntegration)
  54. ;
  55. /**
  56. * Adds a breadcrumb for Sentry events or transactions if this option is enabled.
  57. */
  58. function _getSentryBreadcrumbHandler(client) {
  59. return function addSentryBreadcrumb(event) {
  60. if (core.getClient() !== client) {
  61. return;
  62. }
  63. core.addBreadcrumb(
  64. {
  65. category: `sentry.${event.type === 'transaction' ? 'transaction' : 'event'}`,
  66. event_id: event.event_id,
  67. level: event.level,
  68. message: utils.getEventDescription(event),
  69. },
  70. {
  71. event,
  72. },
  73. );
  74. };
  75. }
  76. /**
  77. * A HOC that creaes a function that creates breadcrumbs from DOM API calls.
  78. * This is a HOC so that we get access to dom options in the closure.
  79. */
  80. function _getDomBreadcrumbHandler(
  81. client,
  82. dom,
  83. ) {
  84. return function _innerDomBreadcrumb(handlerData) {
  85. if (core.getClient() !== client) {
  86. return;
  87. }
  88. let target;
  89. let componentName;
  90. let keyAttrs = typeof dom === 'object' ? dom.serializeAttribute : undefined;
  91. let maxStringLength =
  92. typeof dom === 'object' && typeof dom.maxStringLength === 'number' ? dom.maxStringLength : undefined;
  93. if (maxStringLength && maxStringLength > MAX_ALLOWED_STRING_LENGTH) {
  94. debugBuild.DEBUG_BUILD &&
  95. utils.logger.warn(
  96. `\`dom.maxStringLength\` cannot exceed ${MAX_ALLOWED_STRING_LENGTH}, but a value of ${maxStringLength} was configured. Sentry will use ${MAX_ALLOWED_STRING_LENGTH} instead.`,
  97. );
  98. maxStringLength = MAX_ALLOWED_STRING_LENGTH;
  99. }
  100. if (typeof keyAttrs === 'string') {
  101. keyAttrs = [keyAttrs];
  102. }
  103. // Accessing event.target can throw (see getsentry/raven-js#838, #768)
  104. try {
  105. const event = handlerData.event ;
  106. const element = _isEvent(event) ? event.target : event;
  107. target = utils.htmlTreeAsString(element, { keyAttrs, maxStringLength });
  108. componentName = utils.getComponentName(element);
  109. } catch (e) {
  110. target = '<unknown>';
  111. }
  112. if (target.length === 0) {
  113. return;
  114. }
  115. const breadcrumb = {
  116. category: `ui.${handlerData.name}`,
  117. message: target,
  118. };
  119. if (componentName) {
  120. breadcrumb.data = { 'ui.component_name': componentName };
  121. }
  122. core.addBreadcrumb(breadcrumb, {
  123. event: handlerData.event,
  124. name: handlerData.name,
  125. global: handlerData.global,
  126. });
  127. };
  128. }
  129. /**
  130. * Creates breadcrumbs from console API calls
  131. */
  132. function _getConsoleBreadcrumbHandler(client) {
  133. return function _consoleBreadcrumb(handlerData) {
  134. if (core.getClient() !== client) {
  135. return;
  136. }
  137. const breadcrumb = {
  138. category: 'console',
  139. data: {
  140. arguments: handlerData.args,
  141. logger: 'console',
  142. },
  143. level: utils.severityLevelFromString(handlerData.level),
  144. message: utils.safeJoin(handlerData.args, ' '),
  145. };
  146. if (handlerData.level === 'assert') {
  147. if (handlerData.args[0] === false) {
  148. breadcrumb.message = `Assertion failed: ${utils.safeJoin(handlerData.args.slice(1), ' ') || 'console.assert'}`;
  149. breadcrumb.data.arguments = handlerData.args.slice(1);
  150. } else {
  151. // Don't capture a breadcrumb for passed assertions
  152. return;
  153. }
  154. }
  155. core.addBreadcrumb(breadcrumb, {
  156. input: handlerData.args,
  157. level: handlerData.level,
  158. });
  159. };
  160. }
  161. /**
  162. * Creates breadcrumbs from XHR API calls
  163. */
  164. function _getXhrBreadcrumbHandler(client) {
  165. return function _xhrBreadcrumb(handlerData) {
  166. if (core.getClient() !== client) {
  167. return;
  168. }
  169. const { startTimestamp, endTimestamp } = handlerData;
  170. const sentryXhrData = handlerData.xhr[utils.SENTRY_XHR_DATA_KEY];
  171. // We only capture complete, non-sentry requests
  172. if (!startTimestamp || !endTimestamp || !sentryXhrData) {
  173. return;
  174. }
  175. const { method, url, status_code, body } = sentryXhrData;
  176. const data = {
  177. method,
  178. url,
  179. status_code,
  180. };
  181. const hint = {
  182. xhr: handlerData.xhr,
  183. input: body,
  184. startTimestamp,
  185. endTimestamp,
  186. };
  187. core.addBreadcrumb(
  188. {
  189. category: 'xhr',
  190. data,
  191. type: 'http',
  192. },
  193. hint,
  194. );
  195. };
  196. }
  197. /**
  198. * Creates breadcrumbs from fetch API calls
  199. */
  200. function _getFetchBreadcrumbHandler(client) {
  201. return function _fetchBreadcrumb(handlerData) {
  202. if (core.getClient() !== client) {
  203. return;
  204. }
  205. const { startTimestamp, endTimestamp } = handlerData;
  206. // We only capture complete fetch requests
  207. if (!endTimestamp) {
  208. return;
  209. }
  210. if (handlerData.fetchData.url.match(/sentry_key/) && handlerData.fetchData.method === 'POST') {
  211. // We will not create breadcrumbs for fetch requests that contain `sentry_key` (internal sentry requests)
  212. return;
  213. }
  214. if (handlerData.error) {
  215. const data = handlerData.fetchData;
  216. const hint = {
  217. data: handlerData.error,
  218. input: handlerData.args,
  219. startTimestamp,
  220. endTimestamp,
  221. };
  222. core.addBreadcrumb(
  223. {
  224. category: 'fetch',
  225. data,
  226. level: 'error',
  227. type: 'http',
  228. },
  229. hint,
  230. );
  231. } else {
  232. const response = handlerData.response ;
  233. const data = {
  234. ...handlerData.fetchData,
  235. status_code: response && response.status,
  236. };
  237. const hint = {
  238. input: handlerData.args,
  239. response,
  240. startTimestamp,
  241. endTimestamp,
  242. };
  243. core.addBreadcrumb(
  244. {
  245. category: 'fetch',
  246. data,
  247. type: 'http',
  248. },
  249. hint,
  250. );
  251. }
  252. };
  253. }
  254. /**
  255. * Creates breadcrumbs from history API calls
  256. */
  257. function _getHistoryBreadcrumbHandler(client) {
  258. return function _historyBreadcrumb(handlerData) {
  259. if (core.getClient() !== client) {
  260. return;
  261. }
  262. let from = handlerData.from;
  263. let to = handlerData.to;
  264. const parsedLoc = utils.parseUrl(helpers.WINDOW.location.href);
  265. let parsedFrom = from ? utils.parseUrl(from) : undefined;
  266. const parsedTo = utils.parseUrl(to);
  267. // Initial pushState doesn't provide `from` information
  268. if (!parsedFrom || !parsedFrom.path) {
  269. parsedFrom = parsedLoc;
  270. }
  271. // Use only the path component of the URL if the URL matches the current
  272. // document (almost all the time when using pushState)
  273. if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host) {
  274. to = parsedTo.relative;
  275. }
  276. if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host) {
  277. from = parsedFrom.relative;
  278. }
  279. core.addBreadcrumb({
  280. category: 'navigation',
  281. data: {
  282. from,
  283. to,
  284. },
  285. });
  286. };
  287. }
  288. function _isEvent(event) {
  289. return !!event && !!(event ).target;
  290. }
  291. exports.Breadcrumbs = Breadcrumbs;
  292. exports.breadcrumbsIntegration = breadcrumbsIntegration;
  293. //# sourceMappingURL=breadcrumbs.js.map