/ shared / logger / src / errorkit / errorkit.ts
errorkit.ts
  1  import { Severity } from '@sentry/types';
  2  import type { Logger, LoggerFactory } from '../types';
  3  import type {
  4      captureException,
  5      captureMessage,
  6      addBreadcrumb,
  7      ErrorHub,
  8      ErrorKitConfig,
  9  } from './types';
 10  
 11  type PartialSentryModule = {
 12      captureException: typeof captureException;
 13      captureMessage: typeof captureMessage;
 14      addBreadcrumb: typeof addBreadcrumb;
 15  };
 16  
 17  export type ErrorKitInstance = InstanceType<typeof ErrorKit>;
 18  
 19  export const setupErrorKit = (
 20      config: ErrorKitConfig,
 21      loggerFactory: LoggerFactory,
 22  ): ErrorKitInstance | undefined => {
 23      if (typeof window === 'undefined') return;
 24      const log = loggerFactory.loggerFor('errorkit');
 25      const isMultiDev = window.location.href.includes('multidev');
 26      const BUILD_ENV = process.env.NODE_ENV;
 27      const isErrorKitEnabled = BUILD_ENV === 'production' && !isMultiDev;
 28  
 29      const initializeErrorKit =
 30          async (): Promise<PartialSentryModule | null> => {
 31              let sentry: PartialSentryModule | null = null;
 32  
 33              if (isErrorKitEnabled) {
 34                  try {
 35                      const { createSentryConfig } = await import(
 36                          '@amp-metrics/sentrykit'
 37                      );
 38                      const Sentry = await import('@sentry/browser');
 39                      Sentry.init(createSentryConfig(config));
 40  
 41                      sentry = {
 42                          addBreadcrumb: Sentry.addBreadcrumb,
 43                          captureException: Sentry.captureException,
 44                          captureMessage: Sentry.captureMessage,
 45                      };
 46                  } catch (e) {
 47                      log.error('something went wrong setting up errorKit', e);
 48                  }
 49              }
 50  
 51              return sentry;
 52          };
 53  
 54      return new ErrorKit(initializeErrorKit(), log, isErrorKitEnabled);
 55  };
 56  
 57  class ErrorKit implements ErrorHub {
 58      private readonly sentry: Promise<PartialSentryModule | null>;
 59      private readonly logger: Logger;
 60      private readonly isErrorKitEnabled: boolean;
 61      constructor(
 62          sentry: Promise<PartialSentryModule | null>,
 63          log: Logger,
 64          isErrorKitEnabled: boolean,
 65      ) {
 66          this.sentry = sentry;
 67          this.logger = log;
 68          this.isErrorKitEnabled = isErrorKitEnabled;
 69  
 70          if (!isErrorKitEnabled) {
 71              log.debug('errorkit is disabled');
 72          }
 73      }
 74  
 75      async captureMessage(message: string) {
 76          if (!this.isErrorKitEnabled) return;
 77          const sentry = await this.sentry;
 78  
 79          if (sentry) {
 80              sentry.addBreadcrumb({
 81                  category: 'log.warn',
 82                  level: Severity.Warning,
 83              });
 84              sentry.captureMessage(message, Severity.Warning);
 85          } else {
 86              this.logger.warn(`${message} was not sent to errorKit`);
 87          }
 88      }
 89  
 90      async captureException(exception: Error) {
 91          if (!this.isErrorKitEnabled) return;
 92          const sentry = await this.sentry;
 93  
 94          if (sentry) {
 95              sentry.addBreadcrumb({
 96                  type: 'error',
 97                  category: 'error',
 98                  level: Severity.Error,
 99              });
100              sentry.captureException(exception);
101          } else {
102              this.logger.warn(
103                  `The following exception was not sent to errorKit:`,
104                  exception,
105              );
106          }
107      }
108  }