We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
// 常量 const TRANSACTION_ID_IN_HEADER = 'X-Transaction-ID'; const REQUEST_START_AT_IN_HEADER = 'X-Request-Start-At'; const RETRY_COUNT = 2; // 不记录请求列表(此处使用白名单过滤请求,添加后请确认是否生效) const ignoreUrlList = ['/test/not/record']; export const isRequestNeedRecord = (config: AxiosRequestConfig) => { if (!config.url) { return false; } for (const url of ignoreUrlList) { if (config.url.startsWith(url)) { return false; } } return true; }; // 注册 axios retry 逻辑 - 这个库的逻辑编写的一般,可以自行编写替换 const registerAxiosRetry = (newInstance: AxiosInstance, isExternalRequest?: boolean) => { axiosRetry(newInstance, { retries: RETRY_COUNT, shouldResetTimeout: true, retryDelay: (retryCount) => retryCount * 1000, retryCondition: async (error: Error | AxiosError) => { if (!axios.isAxiosError(error)) { return false; } const check = async () => { // 默认只 retry 5xx 和 network error if (isNetworkOrIdempotentRequestError(error)) { return true; } // retry ECONNABORTED 类型的错误 if (isConnectAbortedError(error)) { return true; } // 认证失败后,先尝试 renew token,再进行 retry if (!isExternalRequest && isAuthExpired(error)) { return await renewAuthToken(); } return false; }; /* * 此处需要包装函数用于兼容 axiosRetry 的逻辑,在 promise 类型的回调中,只有 throw error 才能终止 retry * 详见 https://github.com/softonic/axios-retry/pull/196 */ const needRetry = await check(); if (!needRetry) { throw new Error('should not retry'); } return true; }, }); }; const registerRequestInterceptor = (newInstance: AxiosInstance, isExternalRequest?: boolean) => { newInstance.interceptors.request.use( (config: AxiosRequestConfig) => { const transactionId = v4(); const startAt = dayjs().format('YYYY-MM-DD HH:mm:ss.SSS'); const authContent = 'xxx' return { ...config, headers: { ...config.headers, // add authorization info ...(!isExternalRequest ? { Authorization: `Bearer ${authContent}` } : undefined), // add transaction id [TRANSACTION_ID_IN_HEADER]: transactionId, [REQUEST_START_AT_IN_HEADER]: startAt, }, }; }, (error: Error | AxiosError) => { // log request error if (axios.isAxiosError(error)) { const transactionId = getTargetKeyFromConfig(error.config, TRANSACTION_ID_IN_HEADER); const startAt = getTargetKeyFromConfig(error.config, REQUEST_START_AT_IN_HEADER); // 各类日志软件选其一即可 logEvent('Send Request Error (request)', { ...error.config, error: safeStringify(error.toJSON()), transactionId, startAt, }); } else { logEvent('Send Request Error (native)', { ...error, }); } return Promise.reject(error); }, ); }; const registerResponseInterceptor = (newInstance: AxiosInstance, isExternalRequest?: boolean) => { newInstance.interceptors.response.use( (response: AxiosResponse) => { // 2xx 范围内的状态码都会触发该函数 // 请求成功时,无有效信息的请求不上报日志 if (isRequestNeedRecord(response.config)) { // log response end logEvent('Receive Response', { ...response.config, transactionId: getTargetKeyFromConfig(response.config, TRANSACTION_ID_IN_HEADER), startAt: getTargetKeyFromConfig(response.config, REQUEST_START_AT_IN_HEADER), }); } return Promise.resolve(response); }, (error: Error | AxiosError) => { // log response error if (axios.isAxiosError(error)) { const transactionId = getTargetKeyFromConfig(error.config, TRANSACTION_ID_IN_HEADER); const startAt = getTargetKeyFromConfig(error.config, REQUEST_START_AT_IN_HEADER); logEvent('Receive Response Error (request)', { ...error.config, error: safeStringify(error.toJSON()), transactionId, startAt, }); } else { logEvent('Receive Response Error (native)', { ...error, }); } return Promise.reject(error); }, ); }; export const axiosErrorHandler = async <T>({ error, captureErrorInSentry = true, silenceError, config, isExternalRequest, }: { config: AxiosRequestConfig; error: Error | AxiosError; silenceError?: boolean; captureErrorInSentry?: boolean; isExternalRequest?: boolean; }): Promise<RequestResult<T>> => { // cancel 的请求直接返回 const cancelled = axios.isCancel(error); if (cancelled) { return [undefined, { cancelled }]; } // native 错误 if (!axios.isAxiosError(error)) { return handleNativeError(error, config); } // axios 错误 const isAuthFail = isAuthExpired(error); const isTimeout = isTimeoutError(error); const status = error.response?.status; const displayMessage = error.response?.data?.message; const transactionId = getTargetKeyFromConfig(error.config, TRANSACTION_ID_IN_HEADER); const requestStartTime = getTargetKeyFromConfig(error.config, REQUEST_START_AT_IN_HEADER); const networkStatusAtRequestStart = !!error.config[NETWORK_STATUS_AT_REQUEST_START]; const intervalBetweenRequestAndResponse = dayjs().diff(dayjs(requestStartTime)); const errorContext = { requestStartTime, method: config.method ?? 'N/A', endpoint: config.url ?? 'N/A', responseCode: status, message: displayMessage ?? error.message, networkStatusAtRequestStart, }; // auth 失败的请求直接返回 if (isAuthFail) { return [undefined, { cancelled }]; } // 处理错误提示 if (!silenceError) { if (isNetworkError(error)) { message.error('network_error'); } else if (status === 404) { message.error(displayMessage ?? 'error_404'); } else if (status === 403) { console.warn(displayMessage); } else { message.error(displayMessage); } } // 处理错误上报 if (captureErrorInSentry) { if (error.response) { // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围 if (!isExternalRequest && status === 400) { sendWarningLog(error, transactionId); } else { captureError({ error, context: errorContext, transactionId, }); } } else if (error.request) { // 请求已经成功发起,但没有收到响应 logEvent(isTimeout ? 'Receive Response Timeout' : 'Receive Request Error', { ...errorContext, error: safeStringify(error.toJSON()), }); // 发起请求时网络正常 && 发起请求时间和报错时间间隔不超过两倍的 timeout,才上报至 Sentry,避免无效问题误报 if ( networkStatusAtRequestStart && intervalBetweenRequestAndResponse < 2 * DEFAULT_REQUEST_TIMEOUT ) { captureError({ error, context: errorContext, transactionId, }); } } else { // 发送请求过程中出现问题 captureError({ context: errorContext, transactionId, }); } } return [ undefined, { error, cancelled, }, ]; }; export const createAxios = (axiosConfig?: AxiosRequestConfig, isExternalRequest?: boolean) => { const newInstance = axios.create(axiosConfig); // register plugin registerAxiosRetry(newInstance, isExternalRequest); // register request interceptor registerRequestInterceptor(newInstance, isExternalRequest); // register response interceptor registerResponseInterceptor(newInstance, isExternalRequest); return newInstance; };
The text was updated successfully, but these errors were encountered:
No branches or pull requests
The text was updated successfully, but these errors were encountered: