instrument.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import { logger, getFunctionName } from '@sentry/utils';
  2. import { DEBUG_BUILD } from '../common/debug-build.js';
  3. import { onCLS } from './web-vitals/getCLS.js';
  4. import { onFID } from './web-vitals/getFID.js';
  5. import { onLCP } from './web-vitals/getLCP.js';
  6. import { observe } from './web-vitals/lib/observe.js';
  7. const handlers = {};
  8. const instrumented = {};
  9. let _previousCls;
  10. let _previousFid;
  11. let _previousLcp;
  12. /**
  13. * Add a callback that will be triggered when a CLS metric is available.
  14. * Returns a cleanup callback which can be called to remove the instrumentation handler.
  15. *
  16. * Pass `stopOnCallback = true` to stop listening for CLS when the cleanup callback is called.
  17. * This will lead to the CLS being finalized and frozen.
  18. */
  19. function addClsInstrumentationHandler(
  20. callback,
  21. stopOnCallback = false,
  22. ) {
  23. return addMetricObserver('cls', callback, instrumentCls, _previousCls, stopOnCallback);
  24. }
  25. /**
  26. * Add a callback that will be triggered when a LCP metric is available.
  27. * Returns a cleanup callback which can be called to remove the instrumentation handler.
  28. *
  29. * Pass `stopOnCallback = true` to stop listening for LCP when the cleanup callback is called.
  30. * This will lead to the LCP being finalized and frozen.
  31. */
  32. function addLcpInstrumentationHandler(
  33. callback,
  34. stopOnCallback = false,
  35. ) {
  36. return addMetricObserver('lcp', callback, instrumentLcp, _previousLcp, stopOnCallback);
  37. }
  38. /**
  39. * Add a callback that will be triggered when a FID metric is available.
  40. * Returns a cleanup callback which can be called to remove the instrumentation handler.
  41. */
  42. function addFidInstrumentationHandler(callback) {
  43. return addMetricObserver('fid', callback, instrumentFid, _previousFid);
  44. }
  45. /**
  46. * Add a callback that will be triggered when a performance observer is triggered,
  47. * and receives the entries of the observer.
  48. * Returns a cleanup callback which can be called to remove the instrumentation handler.
  49. */
  50. function addPerformanceInstrumentationHandler(
  51. type,
  52. callback,
  53. ) {
  54. addHandler(type, callback);
  55. if (!instrumented[type]) {
  56. instrumentPerformanceObserver(type);
  57. instrumented[type] = true;
  58. }
  59. return getCleanupCallback(type, callback);
  60. }
  61. /** Trigger all handlers of a given type. */
  62. function triggerHandlers(type, data) {
  63. const typeHandlers = handlers[type];
  64. if (!typeHandlers || !typeHandlers.length) {
  65. return;
  66. }
  67. for (const handler of typeHandlers) {
  68. try {
  69. handler(data);
  70. } catch (e) {
  71. DEBUG_BUILD &&
  72. logger.error(
  73. `Error while triggering instrumentation handler.\nType: ${type}\nName: ${getFunctionName(handler)}\nError:`,
  74. e,
  75. );
  76. }
  77. }
  78. }
  79. function instrumentCls() {
  80. return onCLS(metric => {
  81. triggerHandlers('cls', {
  82. metric,
  83. });
  84. _previousCls = metric;
  85. });
  86. }
  87. function instrumentFid() {
  88. return onFID(metric => {
  89. triggerHandlers('fid', {
  90. metric,
  91. });
  92. _previousFid = metric;
  93. });
  94. }
  95. function instrumentLcp() {
  96. return onLCP(metric => {
  97. triggerHandlers('lcp', {
  98. metric,
  99. });
  100. _previousLcp = metric;
  101. });
  102. }
  103. function addMetricObserver(
  104. type,
  105. callback,
  106. instrumentFn,
  107. previousValue,
  108. stopOnCallback = false,
  109. ) {
  110. addHandler(type, callback);
  111. let stopListening;
  112. if (!instrumented[type]) {
  113. stopListening = instrumentFn();
  114. instrumented[type] = true;
  115. }
  116. if (previousValue) {
  117. callback({ metric: previousValue });
  118. }
  119. return getCleanupCallback(type, callback, stopOnCallback ? stopListening : undefined);
  120. }
  121. function instrumentPerformanceObserver(type) {
  122. const options = {};
  123. // Special per-type options we want to use
  124. if (type === 'event') {
  125. options.durationThreshold = 0;
  126. }
  127. observe(
  128. type,
  129. entries => {
  130. triggerHandlers(type, { entries });
  131. },
  132. options,
  133. );
  134. }
  135. function addHandler(type, handler) {
  136. handlers[type] = handlers[type] || [];
  137. (handlers[type] ).push(handler);
  138. }
  139. // Get a callback which can be called to remove the instrumentation handler
  140. function getCleanupCallback(
  141. type,
  142. callback,
  143. stopListening,
  144. ) {
  145. return () => {
  146. if (stopListening) {
  147. stopListening();
  148. }
  149. const typeHandlers = handlers[type];
  150. if (!typeHandlers) {
  151. return;
  152. }
  153. const index = typeHandlers.indexOf(callback);
  154. if (index !== -1) {
  155. typeHandlers.splice(index, 1);
  156. }
  157. };
  158. }
  159. export { addClsInstrumentationHandler, addFidInstrumentationHandler, addLcpInstrumentationHandler, addPerformanceInstrumentationHandler };
  160. //# sourceMappingURL=instrument.js.map