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;
+  };
 }

Reply via email to