Skip to content

Commit 39dd135

Browse files
authored
fix: catch errors for http requests (#62)
1 parent a73c1d2 commit 39dd135

File tree

4 files changed

+148
-135
lines changed

4 files changed

+148
-135
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ app.on('routeChange', function (next) {
101101

102102
## Tracing range of data requests in the browser
103103

104-
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.
104+
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.
105105

106106
## Catching errors in frames, including React, Angular, Vue.
107107

src/errors/ajax.ts

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,53 +22,40 @@ import { GradeTypeEnum, ErrorsCategory, ReportTypes } from '../services/constant
2222
class AjaxErrors extends Base {
2323
// get http error info
2424
public handleError(options: { service: string; serviceVersion: string; pagePath: string; collector: string }) {
25+
// XMLHttpRequest Object
2526
if (!window.XMLHttpRequest) {
2627
return;
2728
}
28-
const xhrSend = XMLHttpRequest.prototype.send;
29-
const xhrEvent = (event: any) => {
30-
try {
31-
if (event && event.currentTarget && (event.currentTarget.status >= 400 || event.currentTarget.status === 0)) {
32-
const response = 'net::ERR_EMPTY_RESPONSE';
29+
window.addEventListener(
30+
'xhrReadyStateChange',
31+
(event: CustomEvent<XMLHttpRequest & { getRequestConfig: any[] }>) => {
32+
const detail = event.detail;
3333

34-
if (event.target && event.target.getRequestConfig[1] === options.collector + ReportTypes.ERRORS) {
35-
return;
36-
}
37-
this.logInfo = {
38-
uniqueId: uuid(),
39-
service: options.service,
40-
serviceVersion: options.serviceVersion,
41-
pagePath: options.pagePath,
42-
category: ErrorsCategory.AJAX_ERROR,
43-
grade: GradeTypeEnum.ERROR,
44-
errorUrl: event.target.getRequestConfig[1],
45-
message: event.target.response || response,
46-
collector: options.collector,
47-
stack: event.type + ': ' + (event.target.response || response),
48-
};
49-
this.traceInfo();
34+
if (detail.readyState !== 4) {
35+
return;
36+
}
37+
if (detail.getRequestConfig[1] === options.collector + ReportTypes.ERRORS) {
38+
return;
39+
}
40+
if (detail.status !== 0 && detail.status < 400) {
41+
return;
5042
}
51-
} catch (error) {
52-
console.log(error);
53-
}
54-
};
5543

56-
XMLHttpRequest.prototype.send = function () {
57-
if (this.addEventListener) {
58-
this.addEventListener('error', xhrEvent);
59-
this.addEventListener('abort', xhrEvent);
60-
this.addEventListener('timeout', xhrEvent);
61-
} else {
62-
const stateChange = this.onreadystatechange;
63-
this.onreadystatechange = function (event: any) {
64-
stateChange.apply(this, arguments);
65-
if (this.readyState === 4) {
66-
xhrEvent(event);
67-
}
44+
this.logInfo = {
45+
uniqueId: uuid(),
46+
service: options.service,
47+
serviceVersion: options.serviceVersion,
48+
pagePath: options.pagePath,
49+
category: ErrorsCategory.AJAX_ERROR,
50+
grade: GradeTypeEnum.ERROR,
51+
errorUrl: detail.getRequestConfig[1],
52+
message: `status: ${detail.status}; statusText: ${detail.statusText};`,
53+
collector: options.collector,
54+
stack: detail.responseText,
6855
};
69-
}
70-
return xhrSend.apply(this, arguments);
71-
};
56+
this.traceInfo();
57+
},
58+
);
7259
}
7360
}
7461

src/services/base.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export default class Base {
3535
collector: '',
3636
};
3737

38-
public traceInfo() {
38+
public traceInfo(logInfo?: ErrorInfoFields & ReportFields & { collector: string }) {
39+
this.logInfo = logInfo || this.logInfo;
3940
// mark js error pv
4041
if (!jsErrorPv && this.logInfo.category === ErrorsCategory.JS_ERROR) {
4142
jsErrorPv = true;

src/trace/interceptors/fetch.ts

Lines changed: 118 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -18,109 +18,134 @@ import { encode } from 'js-base64';
1818
import uuid from '../../services/uuid';
1919
import { SegmentFields, SpanFields } from '../type';
2020
import { CustomOptionsType } from '../../types';
21-
import { ComponentId, ReportTypes, ServiceTag, SpanLayer, SpanType } from '../../services/constant';
21+
import Base from '../../services/base';
22+
import {
23+
ComponentId,
24+
ReportTypes,
25+
ServiceTag,
26+
SpanLayer,
27+
SpanType,
28+
ErrorsCategory,
29+
GradeTypeEnum,
30+
} from '../../services/constant';
2231

2332
export default function windowFetch(options: CustomOptionsType, segments: SegmentFields[]) {
24-
const fetch: any = window.fetch;
25-
let segment = {
26-
traceId: '',
27-
service: options.service + ServiceTag,
28-
spans: [],
29-
serviceInstance: options.serviceVersion,
30-
traceSegmentId: '',
31-
} as SegmentFields;
32-
let url = {} as URL;
33+
const origFetch: any = window.fetch;
3334

34-
window.fetch = (...args) =>
35-
(async (args: any) => {
36-
const startTime = new Date().getTime();
37-
const traceId = uuid();
38-
const traceSegmentId = uuid();
35+
window.fetch = async (...args: any) => {
36+
const startTime = new Date().getTime();
37+
const traceId = uuid();
38+
const traceSegmentId = uuid();
39+
let segment = {
40+
traceId: '',
41+
service: options.service + ServiceTag,
42+
spans: [],
43+
serviceInstance: options.serviceVersion,
44+
traceSegmentId: '',
45+
} as SegmentFields;
46+
let url = {} as URL;
3947

40-
if (args[0].startsWith('http://') || args[0].startsWith('https://')) {
41-
url = new URL(args[0]);
42-
} else if (args[0].startsWith('//')) {
43-
url = new URL(`${window.location.protocol}${args[0]}`);
44-
} else {
45-
url = new URL(window.location.href);
46-
url.pathname = args[0];
47-
}
48-
49-
const noTrace = options.noTraceOrigins.some((rule: string | RegExp) => {
50-
if (typeof rule === 'string') {
51-
if (rule === url.origin) {
52-
return true;
53-
}
54-
} else if (rule instanceof RegExp) {
55-
if (rule.test(url.origin)) {
56-
return true;
57-
}
58-
}
59-
});
60-
const hasTrace = !(
61-
noTrace ||
62-
(([ReportTypes.ERROR, ReportTypes.ERRORS, ReportTypes.PERF, ReportTypes.SEGMENTS] as string[]).includes(
63-
url.pathname,
64-
) &&
65-
!options.traceSDKInternal)
66-
);
48+
if (args[0].startsWith('http://') || args[0].startsWith('https://')) {
49+
url = new URL(args[0]);
50+
} else if (args[0].startsWith('//')) {
51+
url = new URL(`${window.location.protocol}${args[0]}`);
52+
} else {
53+
url = new URL(window.location.href);
54+
url.pathname = args[0];
55+
}
6756

68-
if (hasTrace) {
69-
const traceIdStr = String(encode(traceId));
70-
const segmentId = String(encode(traceSegmentId));
71-
const service = String(encode(segment.service));
72-
const instance = String(encode(segment.serviceInstance));
73-
const endpoint = String(encode(options.pagePath));
74-
const peer = String(encode(url.host));
75-
const index = segment.spans.length;
76-
const values = `${1}-${traceIdStr}-${segmentId}-${index}-${service}-${instance}-${endpoint}-${peer}`;
77-
78-
if (!args[1]) {
79-
args[1] = {};
57+
const noTrace = options.noTraceOrigins.some((rule: string | RegExp) => {
58+
if (typeof rule === 'string') {
59+
if (rule === url.origin) {
60+
return true;
8061
}
81-
if (!args[1].headers) {
82-
args[1].headers = {};
62+
} else if (rule instanceof RegExp) {
63+
if (rule.test(url.origin)) {
64+
return true;
8365
}
84-
args[1].headers['sw8'] = values;
8566
}
67+
});
68+
const hasTrace = !(
69+
noTrace ||
70+
(([ReportTypes.ERROR, ReportTypes.ERRORS, ReportTypes.PERF, ReportTypes.SEGMENTS] as string[]).includes(
71+
url.pathname,
72+
) &&
73+
!options.traceSDKInternal)
74+
);
8675

87-
const result = await fetch(...args);
76+
if (hasTrace) {
77+
const traceIdStr = String(encode(traceId));
78+
const segmentId = String(encode(traceSegmentId));
79+
const service = String(encode(segment.service));
80+
const instance = String(encode(segment.serviceInstance));
81+
const endpoint = String(encode(options.pagePath));
82+
const peer = String(encode(url.host));
83+
const index = segment.spans.length;
84+
const values = `${1}-${traceIdStr}-${segmentId}-${index}-${service}-${instance}-${endpoint}-${peer}`;
8885

89-
if (hasTrace) {
90-
const endTime = new Date().getTime();
91-
const exitSpan: SpanFields = {
92-
operationName: options.pagePath,
93-
startTime: startTime,
94-
endTime,
95-
spanId: segment.spans.length,
96-
spanLayer: SpanLayer,
97-
spanType: SpanType,
98-
isError: result.status === 0 || result.status >= 400, // when requests failed, the status is 0
99-
parentSpanId: segment.spans.length - 1,
100-
componentId: ComponentId,
101-
peer: url.host,
102-
tags: options.detailMode
103-
? [
104-
{
105-
key: 'http.method',
106-
value: args[1].method,
107-
},
108-
{
109-
key: 'url',
110-
value: result.url,
111-
},
112-
]
113-
: undefined,
114-
};
115-
segment = {
116-
...segment,
117-
traceId: traceId,
118-
traceSegmentId: traceSegmentId,
119-
};
120-
segment.spans.push(exitSpan);
121-
segments.push(segment);
86+
if (!args[1]) {
87+
args[1] = {};
88+
}
89+
if (!args[1].headers) {
90+
args[1].headers = {};
12291
}
92+
args[1].headers['sw8'] = values;
93+
}
94+
95+
const response = await origFetch(...args);
96+
const result = response
97+
.clone()
98+
.json()
99+
.then((body: any) => body)
100+
.catch((err: any) => err);
101+
const logInfo = {
102+
uniqueId: uuid(),
103+
service: options.service,
104+
serviceVersion: options.serviceVersion,
105+
pagePath: options.pagePath,
106+
category: ErrorsCategory.AJAX_ERROR,
107+
grade: GradeTypeEnum.ERROR,
108+
errorUrl: response.url || location.href,
109+
message: `status: ${response.status}; statusText: ${response.statusText};`,
110+
collector: options.collector,
111+
stack: 'Fetch: ' + response.statusText,
112+
};
113+
new Base().traceInfo(logInfo);
114+
if (hasTrace) {
115+
const endTime = new Date().getTime();
116+
const exitSpan: SpanFields = {
117+
operationName: options.pagePath,
118+
startTime: startTime,
119+
endTime,
120+
spanId: segment.spans.length,
121+
spanLayer: SpanLayer,
122+
spanType: SpanType,
123+
isError: response.status === 0 || response.status >= 400, // when requests failed, the status is 0
124+
parentSpanId: segment.spans.length - 1,
125+
componentId: ComponentId,
126+
peer: url.host,
127+
tags: options.detailMode
128+
? [
129+
{
130+
key: 'http.method',
131+
value: args[1].method || 'GET',
132+
},
133+
{
134+
key: 'url',
135+
value: response.url,
136+
},
137+
]
138+
: undefined,
139+
};
140+
segment = {
141+
...segment,
142+
traceId: traceId,
143+
traceSegmentId: traceSegmentId,
144+
};
145+
segment.spans.push(exitSpan);
146+
segments.push(segment);
147+
}
123148

124-
return result;
125-
})(args);
149+
return result;
150+
};
126151
}

0 commit comments

Comments
 (0)