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 }