error-reporter.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // Conditionally enable Sentry based on whether the DSN token is set
  2. import getMeta from '../utils/meta'
  3. import OError from '@overleaf/o-error'
  4. import { debugConsole } from '@/utils/debugging'
  5. const {
  6. sentryAllowedOriginRegex,
  7. sentryDsn,
  8. sentryEnvironment,
  9. sentryRelease,
  10. } = getMeta('ol-ExposedSettings')
  11. const reporterPromise = sentryDsn ? sentryReporter() : nullReporter()
  12. function sentryReporter() {
  13. return (
  14. import(/* webpackMode: "eager" */ '@sentry/browser')
  15. .then(Sentry => {
  16. let eventCount = 0
  17. Sentry.init({
  18. dsn: sentryDsn,
  19. environment: sentryEnvironment,
  20. release: sentryRelease,
  21. autoSessionTracking: false,
  22. // Ignore errors unless they come from our origins
  23. // Adapted from: https://docs.sentry.io/platforms/javascript/configuration/filtering/#decluttering-sentry
  24. allowUrls: [new RegExp(sentryAllowedOriginRegex)],
  25. ignoreErrors: [
  26. // Ignore very noisy error
  27. 'SecurityError: Permission denied to access property "pathname" on cross-origin object',
  28. // Ignore unhandled error that is "expected" - see https://github.com/overleaf/issues/issues/3321
  29. /^Missing PDF/,
  30. // Ignore "expected" error from aborted fetch - see https://github.com/overleaf/issues/issues/3321
  31. /^AbortError/,
  32. // Ignore spurious error from Ace internals - see https://github.com/overleaf/issues/issues/3321
  33. 'ResizeObserver loop limit exceeded',
  34. 'ResizeObserver loop completed with undelivered notifications.',
  35. // Microsoft Outlook SafeLink crawler
  36. // https://forum.sentry.io/t/unhandledrejection-non-error-promise-rejection-captured-with-value/14062
  37. /Non-Error promise rejection captured with value: Object Not Found Matching Id/,
  38. // Ignore CM6 error until upgraded
  39. "Cannot read properties of undefined (reading 'length')",
  40. // Ignore Angular digest iteration limit - see https://github.com/overleaf/internal/issues/15750
  41. '10 $digest() iterations reached',
  42. // Ignore a frequent unhandled promise rejection
  43. /Non-Error promise rejection captured with keys: currentTarget, detail, isTrusted, target/,
  44. /Non-Error promise rejection captured with keys: message, status/,
  45. ],
  46. denyUrls: [
  47. // Chrome extensions
  48. /extensions\//i,
  49. /^chrome:\/\//i,
  50. ],
  51. beforeSend(event) {
  52. // Limit number of events sent to Sentry to 100 events "per page load",
  53. // (i.e. the cap will be reset if the page is reloaded). This prevent
  54. // hitting their server-side event cap.
  55. eventCount++
  56. if (eventCount > 100) {
  57. return null // Block the event from sending
  58. }
  59. // Do not send events related to third party code (extensions)
  60. if (
  61. (event.extra?.arguments as { type: string }[] | undefined)?.[0]
  62. ?.type === 'UNSTABLE_editor:extensions'
  63. ) {
  64. return null // Block the event from sending
  65. }
  66. return event
  67. },
  68. })
  69. Sentry.setUser({ id: getMeta('ol-user_id') })
  70. const splitTestAssignments = getMeta('ol-splitTestVariants')
  71. if (splitTestAssignments) {
  72. for (const [name, value] of Object.entries(splitTestAssignments)) {
  73. // Ensure Sentry tag name is within the 32-character limit
  74. Sentry.setTag(`ol.${name}`.slice(0, 32), value.toString())
  75. }
  76. }
  77. return Sentry
  78. })
  79. // If Sentry fails to load, use the null reporter instead
  80. .catch(error => {
  81. debugConsole.error(error)
  82. return nullReporter()
  83. })
  84. )
  85. }
  86. function nullReporter() {
  87. return Promise.resolve({
  88. captureException: debugConsole.error,
  89. captureMessage: debugConsole.error,
  90. })
  91. }
  92. // https://develop.sentry.dev/sdk/data-model/event-payloads/contexts/
  93. // https://docs.sentry.io/platforms/javascript/enriching-events/context/#passing-context-directly
  94. type Options = {
  95. tags?: Record<string, any>
  96. extra?: Record<string, any>
  97. }
  98. export function captureException(err: Error, options?: Options) {
  99. options = options || {}
  100. const extra = Object.assign(OError.getFullInfo(err), options.extra || {})
  101. const fullStack = OError.getFullStack(err)
  102. if (err.stack !== fullStack) {
  103. // Attach tracebacks from OError.tag() and OError.cause.
  104. extra.fullStack = fullStack
  105. }
  106. reporterPromise.then(reporter =>
  107. reporter.captureException(err, {
  108. ...options,
  109. extra,
  110. })
  111. )
  112. }