import React, { PureComponent, ComponentType, createContext } from 'react'; import * as Sentry from '@sentry/browser'; import TryCatchView from './tryCatchView'; interface Error { name: string; message: string; stack?: string; code?: string; } const noop: (e: Error) => void = () => {}; const SentryContext = createContext({ captureException: noop, }); type SentryAppProps = { appVersion: string; appEnv: string; appName: string; errorFallback?: ComponentType<{ error: Error; errorHandler?: () => void }>; // 应急Error视图, 这个属性待定 errorHandler?: () => void; // 配置errorFallback 使用 @example const errorHandle = () => window.location.reload(true) getUserId: () => string; // 获取用户id dsn: string; // 获取用户sentry的远程数据源 dsn }; type SentryAppState = { hasError: boolean; sentryInitialized: boolean; error: Error | undefined; }; type ReactErrorStackInfo = { [key: string]: any; componentStack?: any }; class SentryApp extends PureComponent<SentryAppProps, SentryAppState> { constructor(props: SentryAppProps) { super(props); this.state = { sentryInitialized: false, hasError: false, error: undefined, }; } static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error: any, errorInfo: ReactErrorStackInfo) { this.captureException(error, errorInfo); } componentDidMount() { this.setState({ sentryInitialized: true }); const { dsn } = this.props; Sentry.init({ dsn, integrations: integrations => { const newIntegrations = integrations .map(integration => { if (integration.name === 'Breadcrumbs') { return new Sentry.Integrations.Breadcrumbs({ console: false, // Log calls to `console.log`, `console.debug`, etc dom: false, // Log all click and keypress events fetch: false, // Log HTTP requests done with the Fetch API history: false, // Log calls to `history.pushState` and friends sentry: false, // Log whenever we send an event to the server xhr: false, // Log HTTP requests done with the XHR API }); } return integration; }) // 禁止 TryCatch & GlobalHandlers 每次error不产生额外的error .filter(integration => integration.name !== 'TryCatch' && integration.name !== 'GlobalHandlers'); return newIntegrations; }, }); } /** * @description 备用字段 等待后续需求 */ eventId: string = ''; captureException = (error: Error, errorInfo?: ReactErrorStackInfo) => { const { getUserId, appVersion, appName, appEnv } = this.props; const userId = getUserId() || ''; // 禁止 非integration定义error // 1、js load Error: ChunkLoadError 每次error不产生额外的error // 2、css chunk error: CSS_CHUNK_LOAD_FAILED 每次error不产生额外的error const { name, code } = error || {}; if (name === 'ChunkLoadError' || code === 'CSS_CHUNK_LOAD_FAILED') { this.setState({ error, }); return; } Sentry.withScope(scope => { if (errorInfo) { scope.setExtras(errorInfo); } scope.setTag('meijian.user.id', userId); scope.setTag('meijian.app.version', appVersion); scope.setTag('meijian.app.name', appName); scope.setTag('meijian.app.env', appEnv); this.eventId = Sentry.captureException(error); this.setState({ error, }); }); }; errorHandler = { captureException: this.captureException, }; render() { const { children, errorHandler } = this.props; const { hasError, sentryInitialized, error } = this.state; if (!sentryInitialized) { return null; } if (hasError) { return error ? <TryCatchView error={error} errorHandler={errorHandler} /> : null; } return <SentryContext.Provider value={this.errorHandler}>{children}</SentryContext.Provider>; } } export { SentryContext }; export { SentryApp }; export { SentryApp as default };