monitor-track 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/reporter.d.ts CHANGED
@@ -184,6 +184,8 @@ export declare const reportMap: {
184
184
  detail: string;
185
185
  /** 上报时带上的请求参数 */
186
186
  requestData: string;
187
+ /** 上报时带上的响应结果 */
188
+ responseBody: string;
187
189
  /** 上报时带上的请求方法 */
188
190
  method: string;
189
191
  };
@@ -16,6 +16,10 @@ export interface IConfig {
16
16
  enableBehavior?: boolean;
17
17
  /** 启用异常信息上报 */
18
18
  enableError?: boolean;
19
+ /** 启用异常截图上报 */
20
+ enableErrorScreenshot?: boolean;
21
+ /** 启用异常录像上报 */
22
+ enableErrorEvent?: boolean;
19
23
  maxLength?: number;
20
24
  /** 忽略上报的信息 */
21
25
  ignore?: {
@@ -37,8 +41,12 @@ export interface IConfig {
37
41
  enableVisualTrack?: boolean;
38
42
  /** 启用页面卡顿上报 */
39
43
  enableLagTrack?: boolean;
40
- /** 上报 页面卡顿的超时时间 */
44
+ /** 上报页面卡顿的超时时间 */
41
45
  lagTimeout?: number;
46
+ /** 是否启用页面录制 */
47
+ enableRecord: boolean;
48
+ /** 启用在线人数上报 */
49
+ enableOnlinePersons?: boolean;
42
50
  }
43
51
  type IParams = {
44
52
  name: string;
@@ -48,7 +48,8 @@ export interface IReport extends INavigator {
48
48
  statusText: string;
49
49
  reason: 'slow' | 'failed' | string;
50
50
  detail: string;
51
- requestData: string;
51
+ requestData: string | null;
52
+ responseBody: string | null;
52
53
  method: string;
53
54
  } | null;
54
55
  vD?: {
@@ -13,5 +13,3 @@ export declare function setReportValue<T extends IReport, K extends keyof T>(key
13
13
  * @description 获取上报数据
14
14
  */
15
15
  export declare function getReport(): IReport;
16
- export declare const recordXMLHttpRequestLog: (XMLHttpRequestTimeout?: number) => void;
17
- export declare const hackFetch: (XMLHttpRequestTimeout?: number) => void;
@@ -107,171 +107,6 @@ function getReport() {
107
107
  customPayload: Config.customPayload,
108
108
  dpr: window.devicePixelRatio,
109
109
  });
110
- }
111
- function ajaxEventTrigger(event) {
112
- const ajaxEvent = new CustomEvent(event, {
113
- detail: this,
114
- });
115
- window.dispatchEvent(ajaxEvent);
116
- }
117
- const OldXHR = window.XMLHttpRequest;
118
- function newXHR() {
119
- const realXHR = new OldXHR();
120
- realXHR.addEventListener('loadstart', function () {
121
- ajaxEventTrigger.call(this, 'ajaxLoadStart');
122
- }, false);
123
- realXHR.addEventListener('loadend', function () {
124
- ajaxEventTrigger.call(this, 'ajaxLoadEnd');
125
- }, false);
126
- // 此处的捕获的异常会连日志接口也一起捕获,如果日志上报接口异常了,就会导致死循环了。
127
- realXHR.onerror = function (e) {
128
- // eslint-disable-next-line no-console
129
- console.warn('realXHR.onerror, e', e);
130
- };
131
- return realXHR;
132
- }
133
- /**
134
- * 页面接口请求监控
135
- */
136
- const tempUrlInfo = {};
137
- const recordXMLHttpRequestLog = (XMLHttpRequestTimeout) => {
138
- XMLHttpRequestTimeout = typeof XMLHttpRequestTimeout === 'number' ? XMLHttpRequestTimeout : 1000;
139
- const timeRecordArray = [];
140
- //@ts-ignore
141
- window.__XMLHttpRequest__ = window.XMLHttpRequest;
142
- //@ts-ignore
143
- window.XMLHttpRequest = newXHR;
144
- window.addEventListener('ajaxLoadStart', function (e) {
145
- const tempObj = {
146
- timeStamp: new Date().getTime(),
147
- event: e,
148
- };
149
- timeRecordArray.push(tempObj);
150
- });
151
- window.addEventListener('ajaxLoadEnd', function () {
152
- const timeRecordArrayCopy = [].concat(timeRecordArray);
153
- for (let i = 0; i < timeRecordArrayCopy.length; i++) {
154
- if (timeRecordArrayCopy[i].event.detail && timeRecordArrayCopy[i].event.detail.status > 0) {
155
- const currentTime = new Date().getTime();
156
- const { responseURL, status, statusText, timeStamp } = timeRecordArrayCopy[i].event.detail;
157
- const previousTime = timeStamp || timeRecordArrayCopy[i].timeStamp;
158
- const loadTime = currentTime - previousTime;
159
- const request = {
160
- requestType: 'xhr',
161
- responseURL,
162
- status,
163
- loadTime,
164
- statusText,
165
- reason: '',
166
- detail: '',
167
- requestData: '',
168
- method: '',
169
- };
170
- if (loadTime && loadTime > XMLHttpRequestTimeout) {
171
- request.reason = 'slow';
172
- request.detail = `request is too slow, XMLHttpRequestTimeout: ${loadTime}`;
173
- }
174
- else if (status && status >= 300 && statusText && statusText.toLowerCase() !== 'ok') {
175
- request.reason = 'failed';
176
- request.detail = `request is failed, status: ${status}, statusText: ${statusText}`;
177
- }
178
- else ;
179
- if (request.reason) {
180
- if (!tempUrlInfo[responseURL]) {
181
- tempUrlInfo[responseURL] = true;
182
- setTimeout(() => {
183
- delete tempUrlInfo[responseURL];
184
- }, 10);
185
- setReportValue('error', null);
186
- report(Object.assign(Object.assign({}, getReport()), {
187
- page: location.href,
188
- originPage: location.href,
189
- type: 'request',
190
- req: request,
191
- }));
192
- }
193
- }
194
- }
195
- // 当前请求成功后就在数组中移除掉
196
- timeRecordArray.splice(i, 1);
197
- }
198
- });
199
- };
200
- const hackFetch = (XMLHttpRequestTimeout) => {
201
- XMLHttpRequestTimeout = typeof XMLHttpRequestTimeout === 'number' ? XMLHttpRequestTimeout : 1000;
202
- if (typeof window.fetch === 'function') {
203
- const __fetch__ = window.fetch;
204
- window.__fetch__ = __fetch__;
205
- //@ts-ignore
206
- window.fetch = function (t, ...args) {
207
- const begin = Date.now();
208
- //禁用数组的扩展运算符,否则portal的生产环境会报Uncaught TypeError: Object(...) is not a function,
209
- //编译后的__spreadArray函数有问题,而这个函数来自于tslib.具体报错原因不知.
210
- //其他平台使用数组的扩展运算符没有问题,比如前端监控平台的管理界面
211
- const params = [].concat(t).concat(args);
212
- return __fetch__
213
- .apply(window, params)
214
- .then(function (res) {
215
- const response = res.clone();
216
- const headers = response.headers;
217
- if (headers && typeof headers.get === 'function') {
218
- const ct = headers.get('content-type');
219
- if (ct && !/(text)|(json)/.test(ct)) {
220
- return res;
221
- }
222
- }
223
- const loadTime = Date.now() - begin;
224
- response
225
- .text()
226
- .then(function (result) {
227
- const { url, status, statusText, ok } = response;
228
- const request = {
229
- requestType: 'fetch',
230
- responseURL: url,
231
- status,
232
- loadTime,
233
- statusText,
234
- reason: '',
235
- detail: '',
236
- requestData: '',
237
- method: '',
238
- };
239
- if (!ok || status >= 300) {
240
- request.reason = 'failed';
241
- request.detail = `request is failed, status: ${status}, statusText: ${statusText}`;
242
- }
243
- else if (loadTime > XMLHttpRequestTimeout) {
244
- request.reason = 'slow';
245
- request.detail = `request is too slow, XMLHttpRequestTimeout: ${loadTime}, result: ${result === null || result === void 0 ? void 0 : result.slice(0, 500)}`;
246
- }
247
- if (request.reason) {
248
- if (!tempUrlInfo[url]) {
249
- tempUrlInfo[url] = true;
250
- setTimeout(() => {
251
- delete tempUrlInfo[url];
252
- }, 10);
253
- setReportValue('error', null);
254
- report(Object.assign(Object.assign({}, getReport()), {
255
- page: location.href,
256
- originPage: location.href,
257
- type: 'request',
258
- req: request,
259
- }));
260
- }
261
- }
262
- })
263
- .catch((err) => {
264
- // eslint-disable-next-line no-console
265
- console.log('hackFetch response.text() err', err);
266
- });
267
- return res;
268
- })
269
- .catch((err) => {
270
- // eslint-disable-next-line no-console
271
- console.log('hackFetch err', err);
272
- });
273
- };
274
- }
275
- };
110
+ }
276
111
 
277
- export { getReport, hackFetch, initReport, recordXMLHttpRequestLog, setReportValue };
112
+ export { getReport, initReport, setReportValue };
@@ -6,12 +6,16 @@ const Config = {
6
6
  reportUrl: '',
7
7
  projectID: '',
8
8
  maxLength: 1000,
9
- spa: false,
10
- hash: false,
9
+ spa: true,
10
+ hash: true,
11
11
  enableBehavior: true,
12
12
  enableError: true,
13
+ enableErrorScreenshot: false,
14
+ enableErrorEvent: false,
13
15
  enableVisualTrack: false,
14
- enableLagTrack: false,
16
+ enableLagTrack: true,
17
+ enableRecord: false,
18
+ enableOnlinePersons: true,
15
19
  ignore: {
16
20
  urls: [],
17
21
  errors: [],
@@ -23,7 +27,17 @@ const Config = {
23
27
  * @param config 配置项
24
28
  */
25
29
  function setConfig(config) {
30
+ var _a;
26
31
  Object.assign(Config, config);
32
+ if (!Config.ignore)
33
+ Config.ignore = {};
34
+ if (Array.isArray((_a = Config.ignore) === null || _a === void 0 ? void 0 : _a.errors)) {
35
+ ['ResizeObserver loop limit exceeded', 'ResizeObserver loop completed with undelivered notifications.', 'Cancel'].forEach((item) => {
36
+ if (!Config.ignore.errors.includes(item)) {
37
+ Config.ignore.errors.push(item);
38
+ }
39
+ });
40
+ }
27
41
  }
28
42
 
29
43
  export { Config, setConfig };
@@ -0,0 +1,15 @@
1
+ export interface IRequestInfo {
2
+ requestType: 'xhr' | 'fetch';
3
+ method: string;
4
+ responseURL: string;
5
+ requestData: string | null;
6
+ responseBody: string | null;
7
+ status: number;
8
+ startTime: number;
9
+ loadTime: number;
10
+ statusText: string;
11
+ reason: string;
12
+ detail: string;
13
+ }
14
+ export declare const recordXMLHttpRequest: (XMLHttpRequestTimeout: number) => void;
15
+ export declare const recordFetch: (XMLHttpRequestTimeout: number) => void;
@@ -0,0 +1,158 @@
1
+ import { __awaiter } from '../_virtual/_tslib.js';
2
+ import { uploadRequest } from '../handlers/websocket.js';
3
+ import { report } from '../reporter.js';
4
+ import { setReportValue, getReport } from './global.js';
5
+
6
+ let xhrTimeout = 2500;
7
+ let fetchTimeout = 2500;
8
+ const maxContentLength = 1000;
9
+ // 保存原始 XMLHttpRequest
10
+ const OriginalXHR = window.XMLHttpRequest;
11
+ // 创建增强版的 XMLHttpRequest 类
12
+ class InterceptedXHR extends OriginalXHR {
13
+ constructor() {
14
+ super();
15
+ this.requestInfo = {
16
+ requestType: 'xhr',
17
+ method: '',
18
+ responseURL: '',
19
+ requestData: null,
20
+ responseBody: null,
21
+ status: 0,
22
+ startTime: Date.now(),
23
+ loadTime: 0,
24
+ statusText: '',
25
+ reason: '',
26
+ detail: '',
27
+ };
28
+ this.setupInterceptors();
29
+ }
30
+ open(method, url, async = true, username, password) {
31
+ super.open(method, url, async, username, password);
32
+ this.requestInfo.method = method;
33
+ this.requestInfo.responseURL = url;
34
+ }
35
+ // eslint-disable-next-line no-undef
36
+ send(body) {
37
+ super.send(body);
38
+ try {
39
+ this.requestInfo.requestData =
40
+ typeof body === 'object' && body
41
+ ? JSON.stringify(body).slice(0, maxContentLength)
42
+ : typeof body === 'string'
43
+ ? body.slice(0, maxContentLength)
44
+ : body || '';
45
+ }
46
+ catch (err) {
47
+ // eslint-disable-next-line no-console
48
+ console.error('xhr send err: ', err, 'body', body);
49
+ }
50
+ this.requestInfo.startTime = Date.now();
51
+ }
52
+ setupInterceptors() {
53
+ this.addEventListener('load', () => {
54
+ try {
55
+ this.requestInfo.status = this.status;
56
+ this.requestInfo.responseBody = this.responseText.slice(0, maxContentLength);
57
+ this.requestInfo.loadTime = Date.now() - this.requestInfo.startTime;
58
+ this.requestInfo.statusText = this.statusText;
59
+ if (this.requestInfo.loadTime > xhrTimeout) {
60
+ this.requestInfo.reason = 'slow';
61
+ this.requestInfo.detail = `request is too slow, XMLHttpRequestTimeout: ${this.requestInfo.loadTime}`;
62
+ }
63
+ if (this.status >= 400) {
64
+ this.requestInfo.reason = 'failed';
65
+ this.requestInfo.detail = `request is failed, status: ${this.status}, statusText: ${this.statusText}`;
66
+ }
67
+ if (this.requestInfo.reason) {
68
+ setReportValue('error', null);
69
+ report(Object.assign(Object.assign({}, getReport()), {
70
+ page: location.href,
71
+ originPage: location.href,
72
+ type: 'request',
73
+ req: this.requestInfo,
74
+ }));
75
+ }
76
+ uploadRequest(this.requestInfo);
77
+ }
78
+ catch (err) {
79
+ // eslint-disable-next-line no-console
80
+ console.error('xhr load err: ', err, 'this.requestInfo', this.requestInfo);
81
+ }
82
+ });
83
+ this.addEventListener('error', () => {
84
+ // console.error('❌ 请求失败:', this.requestInfo);
85
+ });
86
+ }
87
+ }
88
+ const recordXMLHttpRequest = (XMLHttpRequestTimeout) => {
89
+ xhrTimeout = XMLHttpRequestTimeout;
90
+ // 重写全局 XMLHttpRequest
91
+ window.XMLHttpRequest = InterceptedXHR;
92
+ };
93
+ const originalFetch = window.fetch;
94
+ // eslint-disable-next-line no-undef
95
+ const fetchFUnc = function (input, init) {
96
+ return __awaiter(this, void 0, void 0, function* () {
97
+ const url = typeof input === 'string' ? input : input.toString();
98
+ const options = init || {};
99
+ const requestInfo = {
100
+ requestType: 'fetch',
101
+ method: options.method || 'GET',
102
+ responseURL: url,
103
+ requestData: typeof options.body === 'object' && options.body ? JSON.stringify(options.body).slice(0, maxContentLength) : options.body || '',
104
+ responseBody: '',
105
+ status: 0,
106
+ startTime: Date.now(),
107
+ loadTime: 0,
108
+ statusText: '',
109
+ reason: '',
110
+ detail: '',
111
+ };
112
+ const response = yield originalFetch(input, init);
113
+ try {
114
+ const clonedResponse = response.clone();
115
+ requestInfo.status = response.status;
116
+ requestInfo.loadTime = Date.now() - requestInfo.startTime;
117
+ requestInfo.statusText = response.statusText;
118
+ if (!response.ok) {
119
+ requestInfo.reason = 'failed';
120
+ requestInfo.detail = `request is failed, status: ${response.status}, statusText: ${response.statusText}`;
121
+ }
122
+ else {
123
+ if (requestInfo.loadTime > fetchTimeout) {
124
+ requestInfo.reason = 'slow';
125
+ requestInfo.detail = `request is too slow, XMLHttpRequestTimeout: ${requestInfo.loadTime}`;
126
+ }
127
+ }
128
+ try {
129
+ const responseBody = yield clonedResponse.text();
130
+ requestInfo.responseBody = responseBody.slice(0, maxContentLength);
131
+ }
132
+ catch (e) {
133
+ requestInfo.responseBody = (e === null || e === void 0 ? void 0 : e.message) || (e === null || e === void 0 ? void 0 : e.stack) || (e === null || e === void 0 ? void 0 : e.toString());
134
+ }
135
+ if (requestInfo.reason) {
136
+ setReportValue('error', null);
137
+ report(Object.assign(Object.assign({}, getReport()), {
138
+ page: location.href,
139
+ originPage: location.href,
140
+ type: 'request',
141
+ req: requestInfo,
142
+ }));
143
+ }
144
+ uploadRequest(requestInfo);
145
+ }
146
+ catch (err) {
147
+ // eslint-disable-next-line no-console
148
+ console.error('❌ 获取响应数据失败, requestInfo: ', requestInfo, 'err: ', err);
149
+ }
150
+ return response;
151
+ });
152
+ };
153
+ const recordFetch = (XMLHttpRequestTimeout) => {
154
+ fetchTimeout = XMLHttpRequestTimeout;
155
+ window.fetch = fetchFUnc;
156
+ };
157
+
158
+ export { recordFetch, recordXMLHttpRequest };
@@ -1,3 +1,4 @@
1
+ export declare function enableRecordFunc(): void;
1
2
  /**
2
3
  * @description 错误事件触发后的操作
3
4
  */
@@ -7,21 +7,23 @@ import { setReportValue, getReport } from '../config/global.js';
7
7
  import { report } from '../reporter.js';
8
8
 
9
9
  const eventsMatrix = [[]];
10
- rrweb.record({
11
- emit(event, isCheckout) {
12
- // isCheckout 是一个标识,告诉你重新制作了快照
13
- if (isCheckout) {
14
- eventsMatrix.push([]);
15
- }
16
- if (eventsMatrix.length > 2) {
17
- eventsMatrix.shift();
18
- }
19
- const lastEvents = eventsMatrix[eventsMatrix.length - 1];
20
- lastEvents.push(event);
21
- },
22
- // 每30秒重新制作快照
23
- checkoutEveryNms: 30 * 1000,
24
- });
10
+ function enableRecordFunc() {
11
+ rrweb.record({
12
+ emit(event, isCheckout) {
13
+ // isCheckout 是一个标识,告诉你重新制作了快照
14
+ if (isCheckout) {
15
+ eventsMatrix.push([]);
16
+ }
17
+ if (eventsMatrix.length > 2) {
18
+ eventsMatrix.shift();
19
+ }
20
+ const lastEvents = eventsMatrix[eventsMatrix.length - 1];
21
+ lastEvents.push(event);
22
+ },
23
+ // 30秒重新制作快照
24
+ checkoutEveryNms: 30 * 1000,
25
+ });
26
+ }
25
27
  /**
26
28
  * @description 错误事件触发后的操作
27
29
  */
@@ -173,4 +175,4 @@ function getFullScreenShoot(filename) {
173
175
  });
174
176
  }
175
177
 
176
- export { getFullScreenShoot, getUserEvents, handleError, ignoreError, setCaughtError, setPromiseError, setResourceError };
178
+ export { enableRecordFunc, getFullScreenShoot, getUserEvents, handleError, ignoreError, setCaughtError, setPromiseError, setResourceError };
@@ -0,0 +1,16 @@
1
+ import { IRequestInfo } from '../config/request';
2
+ export declare const uploadRequest: (requestInfo: IRequestInfo) => void;
3
+ export declare function enableOnlinePersonsFunc(): void;
4
+ export declare const WEBSOCKET_TYPE: {
5
+ TRY_CONNECT: string;
6
+ CHECK_CONNECT: string;
7
+ PING: string;
8
+ PONG: string;
9
+ REPLY_SERVER_HEART_BEAT: string;
10
+ SOCKET_HEART_BEAT: string;
11
+ RESPONSE_DATE: string;
12
+ REMOTE_DEBUG_ENABLE: string;
13
+ DEBUG_INFO_UPLOAD: string;
14
+ REPlY_MESSAGE: string;
15
+ };
16
+ export declare const closeWebsocket: (info: string) => void;