This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-client-js.git
The following commit(s) were added to refs/heads/master by this push:
new 39dd135 fix: catch errors for http requests (#62)
39dd135 is described below
commit 39dd1359d78fec319ebbedfa22300f73e4660f94
Author: Fine0830 <[email protected]>
AuthorDate: Tue Jul 6 16:49:06 2021 +0800
fix: catch errors for http requests (#62)
---
README.md | 2 +-
src/errors/ajax.ts | 67 +++++--------
src/services/base.ts | 3 +-
src/trace/interceptors/fetch.ts | 211 ++++++++++++++++++++++------------------
4 files changed, 148 insertions(+), 135 deletions(-)
diff --git a/README.md b/README.md
index 849ac9e..935ca4b 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ app.on('routeChange', function (next) {
## Tracing range of data requests in the browser
-Support tracking
these([XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
and [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)) two
modes of data requests. At the same time, Support tracking libraries and tools
that base on XMLHttpRequest and fetch, such as
[Axios](https://github.com/axios/axios),
[SuperAgent](https://github.com/visionmedia/superagent),
[OpenApi](https://www.openapis.org/) and so on.
+Support tracking
these([XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
and [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API))
two modes of data requests. At the same time, Support tracking libraries and
tools that base on XMLHttpRequest and fetch, such as
[Axios](https://github.com/axios/axios),
[SuperAgent](https://github.com/visionmedia/superagent),
[OpenApi](https://www.openapis.org/) and so on.
## Catching errors in frames, including React, Angular, Vue.
diff --git a/src/errors/ajax.ts b/src/errors/ajax.ts
index d2174b5..f2e2825 100644
--- a/src/errors/ajax.ts
+++ b/src/errors/ajax.ts
@@ -22,53 +22,40 @@ import { GradeTypeEnum, ErrorsCategory, ReportTypes } from
'../services/constant
class AjaxErrors extends Base {
// get http error info
public handleError(options: { service: string; serviceVersion: string;
pagePath: string; collector: string }) {
+ // XMLHttpRequest Object
if (!window.XMLHttpRequest) {
return;
}
- const xhrSend = XMLHttpRequest.prototype.send;
- const xhrEvent = (event: any) => {
- try {
- if (event && event.currentTarget && (event.currentTarget.status >= 400
|| event.currentTarget.status === 0)) {
- const response = 'net::ERR_EMPTY_RESPONSE';
+ window.addEventListener(
+ 'xhrReadyStateChange',
+ (event: CustomEvent<XMLHttpRequest & { getRequestConfig: any[] }>) => {
+ const detail = event.detail;
- if (event.target && event.target.getRequestConfig[1] ===
options.collector + ReportTypes.ERRORS) {
- return;
- }
- this.logInfo = {
- uniqueId: uuid(),
- service: options.service,
- serviceVersion: options.serviceVersion,
- pagePath: options.pagePath,
- category: ErrorsCategory.AJAX_ERROR,
- grade: GradeTypeEnum.ERROR,
- errorUrl: event.target.getRequestConfig[1],
- message: event.target.response || response,
- collector: options.collector,
- stack: event.type + ': ' + (event.target.response || response),
- };
- this.traceInfo();
+ if (detail.readyState !== 4) {
+ return;
+ }
+ if (detail.getRequestConfig[1] === options.collector +
ReportTypes.ERRORS) {
+ return;
+ }
+ if (detail.status !== 0 && detail.status < 400) {
+ return;
}
- } catch (error) {
- console.log(error);
- }
- };
- XMLHttpRequest.prototype.send = function () {
- if (this.addEventListener) {
- this.addEventListener('error', xhrEvent);
- this.addEventListener('abort', xhrEvent);
- this.addEventListener('timeout', xhrEvent);
- } else {
- const stateChange = this.onreadystatechange;
- this.onreadystatechange = function (event: any) {
- stateChange.apply(this, arguments);
- if (this.readyState === 4) {
- xhrEvent(event);
- }
+ this.logInfo = {
+ uniqueId: uuid(),
+ service: options.service,
+ serviceVersion: options.serviceVersion,
+ pagePath: options.pagePath,
+ category: ErrorsCategory.AJAX_ERROR,
+ grade: GradeTypeEnum.ERROR,
+ errorUrl: detail.getRequestConfig[1],
+ message: `status: ${detail.status}; statusText:
${detail.statusText};`,
+ collector: options.collector,
+ stack: detail.responseText,
};
- }
- return xhrSend.apply(this, arguments);
- };
+ this.traceInfo();
+ },
+ );
}
}
diff --git a/src/services/base.ts b/src/services/base.ts
index f6cd98b..005a513 100644
--- a/src/services/base.ts
+++ b/src/services/base.ts
@@ -35,7 +35,8 @@ export default class Base {
collector: '',
};
- public traceInfo() {
+ public traceInfo(logInfo?: ErrorInfoFields & ReportFields & { collector:
string }) {
+ this.logInfo = logInfo || this.logInfo;
// mark js error pv
if (!jsErrorPv && this.logInfo.category === ErrorsCategory.JS_ERROR) {
jsErrorPv = true;
diff --git a/src/trace/interceptors/fetch.ts b/src/trace/interceptors/fetch.ts
index 8e4b3ec..baeb96b 100644
--- a/src/trace/interceptors/fetch.ts
+++ b/src/trace/interceptors/fetch.ts
@@ -18,109 +18,134 @@ import { encode } from 'js-base64';
import uuid from '../../services/uuid';
import { SegmentFields, SpanFields } from '../type';
import { CustomOptionsType } from '../../types';
-import { ComponentId, ReportTypes, ServiceTag, SpanLayer, SpanType } from
'../../services/constant';
+import Base from '../../services/base';
+import {
+ ComponentId,
+ ReportTypes,
+ ServiceTag,
+ SpanLayer,
+ SpanType,
+ ErrorsCategory,
+ GradeTypeEnum,
+} from '../../services/constant';
export default function windowFetch(options: CustomOptionsType, segments:
SegmentFields[]) {
- const fetch: any = window.fetch;
- let segment = {
- traceId: '',
- service: options.service + ServiceTag,
- spans: [],
- serviceInstance: options.serviceVersion,
- traceSegmentId: '',
- } as SegmentFields;
- let url = {} as URL;
+ const origFetch: any = window.fetch;
- window.fetch = (...args) =>
- (async (args: any) => {
- const startTime = new Date().getTime();
- const traceId = uuid();
- const traceSegmentId = uuid();
+ window.fetch = async (...args: any) => {
+ const startTime = new Date().getTime();
+ const traceId = uuid();
+ const traceSegmentId = uuid();
+ let segment = {
+ traceId: '',
+ service: options.service + ServiceTag,
+ spans: [],
+ serviceInstance: options.serviceVersion,
+ traceSegmentId: '',
+ } as SegmentFields;
+ let url = {} as URL;
- if (args[0].startsWith('http://') || args[0].startsWith('https://')) {
- url = new URL(args[0]);
- } else if (args[0].startsWith('//')) {
- url = new URL(`${window.location.protocol}${args[0]}`);
- } else {
- url = new URL(window.location.href);
- url.pathname = args[0];
- }
-
- const noTrace = options.noTraceOrigins.some((rule: string | RegExp) => {
- if (typeof rule === 'string') {
- if (rule === url.origin) {
- return true;
- }
- } else if (rule instanceof RegExp) {
- if (rule.test(url.origin)) {
- return true;
- }
- }
- });
- const hasTrace = !(
- noTrace ||
- (([ReportTypes.ERROR, ReportTypes.ERRORS, ReportTypes.PERF,
ReportTypes.SEGMENTS] as string[]).includes(
- url.pathname,
- ) &&
- !options.traceSDKInternal)
- );
+ if (args[0].startsWith('http://') || args[0].startsWith('https://')) {
+ url = new URL(args[0]);
+ } else if (args[0].startsWith('//')) {
+ url = new URL(`${window.location.protocol}${args[0]}`);
+ } else {
+ url = new URL(window.location.href);
+ url.pathname = args[0];
+ }
- if (hasTrace) {
- const traceIdStr = String(encode(traceId));
- const segmentId = String(encode(traceSegmentId));
- const service = String(encode(segment.service));
- const instance = String(encode(segment.serviceInstance));
- const endpoint = String(encode(options.pagePath));
- const peer = String(encode(url.host));
- const index = segment.spans.length;
- const values =
`${1}-${traceIdStr}-${segmentId}-${index}-${service}-${instance}-${endpoint}-${peer}`;
-
- if (!args[1]) {
- args[1] = {};
+ const noTrace = options.noTraceOrigins.some((rule: string | RegExp) => {
+ if (typeof rule === 'string') {
+ if (rule === url.origin) {
+ return true;
}
- if (!args[1].headers) {
- args[1].headers = {};
+ } else if (rule instanceof RegExp) {
+ if (rule.test(url.origin)) {
+ return true;
}
- args[1].headers['sw8'] = values;
}
+ });
+ const hasTrace = !(
+ noTrace ||
+ (([ReportTypes.ERROR, ReportTypes.ERRORS, ReportTypes.PERF,
ReportTypes.SEGMENTS] as string[]).includes(
+ url.pathname,
+ ) &&
+ !options.traceSDKInternal)
+ );
- const result = await fetch(...args);
+ if (hasTrace) {
+ const traceIdStr = String(encode(traceId));
+ const segmentId = String(encode(traceSegmentId));
+ const service = String(encode(segment.service));
+ const instance = String(encode(segment.serviceInstance));
+ const endpoint = String(encode(options.pagePath));
+ const peer = String(encode(url.host));
+ const index = segment.spans.length;
+ const values =
`${1}-${traceIdStr}-${segmentId}-${index}-${service}-${instance}-${endpoint}-${peer}`;
- if (hasTrace) {
- const endTime = new Date().getTime();
- const exitSpan: SpanFields = {
- operationName: options.pagePath,
- startTime: startTime,
- endTime,
- spanId: segment.spans.length,
- spanLayer: SpanLayer,
- spanType: SpanType,
- isError: result.status === 0 || result.status >= 400, // when
requests failed, the status is 0
- parentSpanId: segment.spans.length - 1,
- componentId: ComponentId,
- peer: url.host,
- tags: options.detailMode
- ? [
- {
- key: 'http.method',
- value: args[1].method,
- },
- {
- key: 'url',
- value: result.url,
- },
- ]
- : undefined,
- };
- segment = {
- ...segment,
- traceId: traceId,
- traceSegmentId: traceSegmentId,
- };
- segment.spans.push(exitSpan);
- segments.push(segment);
+ if (!args[1]) {
+ args[1] = {};
+ }
+ if (!args[1].headers) {
+ args[1].headers = {};
}
+ args[1].headers['sw8'] = values;
+ }
+
+ const response = await origFetch(...args);
+ const result = response
+ .clone()
+ .json()
+ .then((body: any) => body)
+ .catch((err: any) => err);
+ const logInfo = {
+ uniqueId: uuid(),
+ service: options.service,
+ serviceVersion: options.serviceVersion,
+ pagePath: options.pagePath,
+ category: ErrorsCategory.AJAX_ERROR,
+ grade: GradeTypeEnum.ERROR,
+ errorUrl: response.url || location.href,
+ message: `status: ${response.status}; statusText:
${response.statusText};`,
+ collector: options.collector,
+ stack: 'Fetch: ' + response.statusText,
+ };
+ new Base().traceInfo(logInfo);
+ if (hasTrace) {
+ const endTime = new Date().getTime();
+ const exitSpan: SpanFields = {
+ operationName: options.pagePath,
+ startTime: startTime,
+ endTime,
+ spanId: segment.spans.length,
+ spanLayer: SpanLayer,
+ spanType: SpanType,
+ isError: response.status === 0 || response.status >= 400, // when
requests failed, the status is 0
+ parentSpanId: segment.spans.length - 1,
+ componentId: ComponentId,
+ peer: url.host,
+ tags: options.detailMode
+ ? [
+ {
+ key: 'http.method',
+ value: args[1].method || 'GET',
+ },
+ {
+ key: 'url',
+ value: response.url,
+ },
+ ]
+ : undefined,
+ };
+ segment = {
+ ...segment,
+ traceId: traceId,
+ traceSegmentId: traceSegmentId,
+ };
+ segment.spans.push(exitSpan);
+ segments.push(segment);
+ }
- return result;
- })(args);
+ return result;
+ };
}