diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /shared/logger/src/errorkit | |
init commit
Diffstat (limited to 'shared/logger/src/errorkit')
| -rw-r--r-- | shared/logger/src/errorkit/errorkit-logger.ts | 93 | ||||
| -rw-r--r-- | shared/logger/src/errorkit/errorkit.ts | 108 |
2 files changed, 201 insertions, 0 deletions
diff --git a/shared/logger/src/errorkit/errorkit-logger.ts b/shared/logger/src/errorkit/errorkit-logger.ts new file mode 100644 index 0000000..1290c41 --- /dev/null +++ b/shared/logger/src/errorkit/errorkit-logger.ts @@ -0,0 +1,93 @@ +import type { ErrorHub, ValueOf } from './types'; +import type { LoggerFactory, Logger } from '../types'; + +/** + * Determines the level of logs to send to sentry. + * + */ +export const ERROR_REPORT_LEVEL = { + error: 'error', + error_warn: 'error_warn', +} as const; + +type ReportLevel = ValueOf<typeof ERROR_REPORT_LEVEL>; + +export class ErrorKitLoggerFactory implements LoggerFactory { + private readonly errorKit: ErrorHub; + private readonly reportLevel: ReportLevel; + constructor(errorKit: ErrorHub, reportLevel?: ReportLevel) { + this.errorKit = errorKit; + this.reportLevel = reportLevel ?? ERROR_REPORT_LEVEL.error; + } + loggerFor(name: string): Logger { + return new ErrorKitLogger(name, this.errorKit, this.reportLevel); + } +} + +interface HasToString { + toString(): string; +} + +export class ErrorKitLogger implements Logger { + private readonly name: string; + private readonly errorKit: ErrorHub; + private readonly reportLevel: ReportLevel; + constructor(name: string, errorKit: ErrorHub, reportLevel: ReportLevel) { + this.name = name; + this.errorKit = errorKit; + this.reportLevel = reportLevel; + } + + private stringifyConsoleArgs(...args: unknown[]): string { + return args.reduce((acc: string, val: unknown) => { + let tempVal: HasToString; + switch (true) { + case val instanceof Error: { + tempVal = (val as unknown as InstanceType<typeof Error>) + .message; + break; + } + case typeof val === 'object': { + try { + tempVal = JSON.stringify(val); + } catch (e) { + tempVal = `failed to stringify ${val}`; + } + break; + } + case typeof val === 'undefined' || val === null: { + tempVal = `${val}`; + break; + } + default: { + tempVal = val as HasToString; + } + } + + return `${acc} ${tempVal.toString()}`; + }, `[${this.name}]`) as string; + } + + debug(..._args: unknown[]): string { + return ''; + } + info(..._args: unknown[]): string { + return ''; + } + warn(...args: unknown[]): string { + if (this.reportLevel === ERROR_REPORT_LEVEL.error_warn) { + this.errorKit.captureMessage(this.stringifyConsoleArgs(...args)); + } + return ''; + } + error(...args: unknown[]): string { + const errors = args.filter((item) => item instanceof Error) as Error[]; + const message = this.stringifyConsoleArgs(...args); + + const error = errors.length === 0 ? new Error(message) : errors[0]; + error.message = message; + + this.errorKit.captureException(error); + return ''; + } +} diff --git a/shared/logger/src/errorkit/errorkit.ts b/shared/logger/src/errorkit/errorkit.ts new file mode 100644 index 0000000..dd40e26 --- /dev/null +++ b/shared/logger/src/errorkit/errorkit.ts @@ -0,0 +1,108 @@ +import { Severity } from '@sentry/types'; +import type { Logger, LoggerFactory } from '../types'; +import type { + captureException, + captureMessage, + addBreadcrumb, + ErrorHub, + ErrorKitConfig, +} from './types'; + +type PartialSentryModule = { + captureException: typeof captureException; + captureMessage: typeof captureMessage; + addBreadcrumb: typeof addBreadcrumb; +}; + +export type ErrorKitInstance = InstanceType<typeof ErrorKit>; + +export const setupErrorKit = ( + config: ErrorKitConfig, + loggerFactory: LoggerFactory, +): ErrorKitInstance | undefined => { + if (typeof window === 'undefined') return; + const log = loggerFactory.loggerFor('errorkit'); + const isMultiDev = window.location.href.includes('multidev'); + const BUILD_ENV = process.env.NODE_ENV; + const isErrorKitEnabled = BUILD_ENV === 'production' && !isMultiDev; + + const initializeErrorKit = + async (): Promise<PartialSentryModule | null> => { + let sentry: PartialSentryModule | null = null; + + if (isErrorKitEnabled) { + try { + const { createSentryConfig } = await import( + '@amp-metrics/sentrykit' + ); + const Sentry = await import('@sentry/browser'); + Sentry.init(createSentryConfig(config)); + + sentry = { + addBreadcrumb: Sentry.addBreadcrumb, + captureException: Sentry.captureException, + captureMessage: Sentry.captureMessage, + }; + } catch (e) { + log.error('something went wrong setting up errorKit', e); + } + } + + return sentry; + }; + + return new ErrorKit(initializeErrorKit(), log, isErrorKitEnabled); +}; + +class ErrorKit implements ErrorHub { + private readonly sentry: Promise<PartialSentryModule | null>; + private readonly logger: Logger; + private readonly isErrorKitEnabled: boolean; + constructor( + sentry: Promise<PartialSentryModule | null>, + log: Logger, + isErrorKitEnabled: boolean, + ) { + this.sentry = sentry; + this.logger = log; + this.isErrorKitEnabled = isErrorKitEnabled; + + if (!isErrorKitEnabled) { + log.debug('errorkit is disabled'); + } + } + + async captureMessage(message: string) { + if (!this.isErrorKitEnabled) return; + const sentry = await this.sentry; + + if (sentry) { + sentry.addBreadcrumb({ + category: 'log.warn', + level: Severity.Warning, + }); + sentry.captureMessage(message, Severity.Warning); + } else { + this.logger.warn(`${message} was not sent to errorKit`); + } + } + + async captureException(exception: Error) { + if (!this.isErrorKitEnabled) return; + const sentry = await this.sentry; + + if (sentry) { + sentry.addBreadcrumb({ + type: 'error', + category: 'error', + level: Severity.Error, + }); + sentry.captureException(exception); + } else { + this.logger.warn( + `The following exception was not sent to errorKit:`, + exception, + ); + } + } +} |
