hubextensions.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  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. const utils$1 = require('./utils.js');
  7. /* eslint-disable complexity */
  8. /**
  9. * Safety wrapper for startTransaction for the unlikely case that transaction starts before tracing is imported -
  10. * if that happens we want to avoid throwing an error from profiling code.
  11. * see https://github.com/getsentry/sentry-javascript/issues/4731.
  12. *
  13. * @experimental
  14. */
  15. function onProfilingStartRouteTransaction(transaction) {
  16. if (!transaction) {
  17. if (debugBuild.DEBUG_BUILD) {
  18. utils.logger.log('[Profiling] Transaction is undefined, skipping profiling');
  19. }
  20. return transaction;
  21. }
  22. if (utils$1.shouldProfileTransaction(transaction)) {
  23. return startProfileForTransaction(transaction);
  24. }
  25. return transaction;
  26. }
  27. /**
  28. * Wraps startTransaction and stopTransaction with profiling related logic.
  29. * startProfileForTransaction is called after the call to startTransaction in order to avoid our own code from
  30. * being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.
  31. */
  32. function startProfileForTransaction(transaction) {
  33. // Start the profiler and get the profiler instance.
  34. let startTimestamp;
  35. if (utils$1.isAutomatedPageLoadTransaction(transaction)) {
  36. startTimestamp = utils.timestampInSeconds() * 1000;
  37. }
  38. const profiler = utils$1.startJSSelfProfile();
  39. // We failed to construct the profiler, fallback to original transaction.
  40. // No need to log anything as this has already been logged in startProfile.
  41. if (!profiler) {
  42. return transaction;
  43. }
  44. if (debugBuild.DEBUG_BUILD) {
  45. utils.logger.log(`[Profiling] started profiling transaction: ${core.spanToJSON(transaction).description}`);
  46. }
  47. // We create "unique" transaction names to avoid concurrent transactions with same names
  48. // from being ignored by the profiler. From here on, only this transaction name should be used when
  49. // calling the profiler methods. Note: we log the original name to the user to avoid confusion.
  50. const profileId = utils.uuid4();
  51. /**
  52. * Idempotent handler for profile stop
  53. */
  54. async function onProfileHandler() {
  55. // Check if the profile exists and return it the behavior has to be idempotent as users may call transaction.finish multiple times.
  56. if (!transaction) {
  57. return null;
  58. }
  59. // Satisfy the type checker, but profiler will always be defined here.
  60. if (!profiler) {
  61. return null;
  62. }
  63. return profiler
  64. .stop()
  65. .then((profile) => {
  66. if (maxDurationTimeoutID) {
  67. helpers.WINDOW.clearTimeout(maxDurationTimeoutID);
  68. maxDurationTimeoutID = undefined;
  69. }
  70. if (debugBuild.DEBUG_BUILD) {
  71. utils.logger.log(`[Profiling] stopped profiling of transaction: ${core.spanToJSON(transaction).description}`);
  72. }
  73. // In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile.
  74. if (!profile) {
  75. if (debugBuild.DEBUG_BUILD) {
  76. utils.logger.log(
  77. `[Profiling] profiler returned null profile for: ${core.spanToJSON(transaction).description}`,
  78. 'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started',
  79. );
  80. }
  81. return null;
  82. }
  83. utils$1.addProfileToGlobalCache(profileId, profile);
  84. return null;
  85. })
  86. .catch(error => {
  87. if (debugBuild.DEBUG_BUILD) {
  88. utils.logger.log('[Profiling] error while stopping profiler:', error);
  89. }
  90. return null;
  91. });
  92. }
  93. // Enqueue a timeout to prevent profiles from running over max duration.
  94. let maxDurationTimeoutID = helpers.WINDOW.setTimeout(() => {
  95. if (debugBuild.DEBUG_BUILD) {
  96. utils.logger.log(
  97. '[Profiling] max profile duration elapsed, stopping profiling for:',
  98. core.spanToJSON(transaction).description,
  99. );
  100. }
  101. // If the timeout exceeds, we want to stop profiling, but not finish the transaction
  102. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  103. onProfileHandler();
  104. }, utils$1.MAX_PROFILE_DURATION_MS);
  105. // We need to reference the original end call to avoid creating an infinite loop
  106. const originalEnd = transaction.end.bind(transaction);
  107. /**
  108. * Wraps startTransaction and stopTransaction with profiling related logic.
  109. * startProfiling is called after the call to startTransaction in order to avoid our own code from
  110. * being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction.
  111. */
  112. function profilingWrappedTransactionEnd() {
  113. if (!transaction) {
  114. return originalEnd();
  115. }
  116. // onProfileHandler should always return the same profile even if this is called multiple times.
  117. // Always call onProfileHandler to ensure stopProfiling is called and the timeout is cleared.
  118. void onProfileHandler().then(
  119. () => {
  120. // TODO: Can we rewrite this to use attributes?
  121. // eslint-disable-next-line deprecation/deprecation
  122. transaction.setContext('profile', { profile_id: profileId, start_timestamp: startTimestamp });
  123. originalEnd();
  124. },
  125. () => {
  126. // If onProfileHandler fails, we still want to call the original finish method.
  127. originalEnd();
  128. },
  129. );
  130. return transaction;
  131. }
  132. transaction.end = profilingWrappedTransactionEnd;
  133. return transaction;
  134. }
  135. exports.onProfilingStartRouteTransaction = onProfilingStartRouteTransaction;
  136. exports.startProfileForTransaction = startProfileForTransaction;
  137. //# sourceMappingURL=hubextensions.js.map