sobey-monitor-sdk 1.1.7 → 1.1.9
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/dist/index.cjs.js +115 -13
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.esm.js +115 -13
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +115 -13
- package/dist/index.umd.js.map +1 -1
- package/dist/types/index.d.ts +19 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -70,6 +70,18 @@ interface ReportConfig {
|
|
|
70
70
|
/** 请求超时(ms) */
|
|
71
71
|
timeout?: number;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* 业务错误检测规则
|
|
75
|
+
* 用于检测 HTTP 200 响应中的业务层错误
|
|
76
|
+
*/
|
|
77
|
+
interface BusinessErrorRule {
|
|
78
|
+
/** 字段路径,如 "status" 或 "data.code" */
|
|
79
|
+
field: string;
|
|
80
|
+
/** 比较操作符 */
|
|
81
|
+
operator: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'exists';
|
|
82
|
+
/** 比较值(当 operator 为 'exists' 时可不填) */
|
|
83
|
+
value?: string | number | boolean;
|
|
84
|
+
}
|
|
73
85
|
/**
|
|
74
86
|
* 错误监控配置
|
|
75
87
|
*/
|
|
@@ -86,6 +98,12 @@ interface ErrorMonitorConfig {
|
|
|
86
98
|
httpError?: boolean;
|
|
87
99
|
/** 错误过滤函数 */
|
|
88
100
|
filter?: (error: ErrorData) => boolean;
|
|
101
|
+
/**
|
|
102
|
+
* 业务错误检测规则
|
|
103
|
+
* 当 HTTP 状态码为 200 时,根据这些规则检测响应体中的业务错误
|
|
104
|
+
* 任一规则匹配时将上报 http_error
|
|
105
|
+
*/
|
|
106
|
+
businessErrorRules?: BusinessErrorRule[];
|
|
89
107
|
}
|
|
90
108
|
/**
|
|
91
109
|
* 性能监控配置
|
|
@@ -240,7 +258,7 @@ interface BehaviorData extends BaseData {
|
|
|
240
258
|
*/
|
|
241
259
|
interface BreadCrumb {
|
|
242
260
|
/** 类型 */
|
|
243
|
-
type: 'route' | 'click' | 'request' | 'console' | 'input' | 'custom' | 'framework';
|
|
261
|
+
type: 'route' | 'click' | 'request' | 'console' | 'input' | 'custom' | 'framework' | 'resource';
|
|
244
262
|
/** 分类 */
|
|
245
263
|
category: string;
|
|
246
264
|
/** 数据 */
|
package/dist/index.esm.js
CHANGED
|
@@ -731,11 +731,12 @@ function installResourceErrorHandler() {
|
|
|
731
731
|
};
|
|
732
732
|
// 添加到面包屑
|
|
733
733
|
context.addBreadcrumb({
|
|
734
|
-
type: '
|
|
734
|
+
type: 'resource',
|
|
735
735
|
category: 'resource_error',
|
|
736
736
|
data: {
|
|
737
737
|
tagName,
|
|
738
|
-
resourceUrl,
|
|
738
|
+
url: resourceUrl,
|
|
739
|
+
error: `Failed to load ${tagName}`,
|
|
739
740
|
},
|
|
740
741
|
});
|
|
741
742
|
reporter.reportError(errorData);
|
|
@@ -770,6 +771,79 @@ function isSdkRequest(url) {
|
|
|
770
771
|
return false;
|
|
771
772
|
return url.includes(cfg.dsn);
|
|
772
773
|
}
|
|
774
|
+
/**
|
|
775
|
+
* 获取嵌套对象的字段值
|
|
776
|
+
* @param obj 对象
|
|
777
|
+
* @param path 字段路径,如 "status" 或 "data.code"
|
|
778
|
+
*/
|
|
779
|
+
function getNestedValue(obj, path) {
|
|
780
|
+
if (!obj || typeof obj !== 'object')
|
|
781
|
+
return undefined;
|
|
782
|
+
const keys = path.split('.');
|
|
783
|
+
let value = obj;
|
|
784
|
+
for (const key of keys) {
|
|
785
|
+
if (value === null || value === undefined)
|
|
786
|
+
return undefined;
|
|
787
|
+
value = value[key];
|
|
788
|
+
}
|
|
789
|
+
return value;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* 检查单条规则是否匹配
|
|
793
|
+
*/
|
|
794
|
+
function matchRule(data, rule) {
|
|
795
|
+
const value = getNestedValue(data, rule.field);
|
|
796
|
+
switch (rule.operator) {
|
|
797
|
+
case 'exists':
|
|
798
|
+
return value !== undefined && value !== null;
|
|
799
|
+
case 'eq':
|
|
800
|
+
return value === rule.value;
|
|
801
|
+
case 'ne':
|
|
802
|
+
return value !== rule.value;
|
|
803
|
+
case 'gt':
|
|
804
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value > rule.value;
|
|
805
|
+
case 'gte':
|
|
806
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value >= rule.value;
|
|
807
|
+
case 'lt':
|
|
808
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value < rule.value;
|
|
809
|
+
case 'lte':
|
|
810
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value <= rule.value;
|
|
811
|
+
default:
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* 检测业务错误
|
|
817
|
+
* @param responseBody 响应体字符串
|
|
818
|
+
* @returns 是否为业务错误
|
|
819
|
+
*/
|
|
820
|
+
function detectBusinessError(responseBody) {
|
|
821
|
+
if (!responseBody)
|
|
822
|
+
return false;
|
|
823
|
+
if (!config.isInitialized())
|
|
824
|
+
return false;
|
|
825
|
+
const cfg = config.get();
|
|
826
|
+
const rules = cfg.error?.businessErrorRules;
|
|
827
|
+
if (!rules || rules.length === 0)
|
|
828
|
+
return false;
|
|
829
|
+
try {
|
|
830
|
+
const data = JSON.parse(responseBody);
|
|
831
|
+
// 任一规则匹配时返回 true
|
|
832
|
+
for (const rule of rules) {
|
|
833
|
+
if (matchRule(data, rule)) {
|
|
834
|
+
if (cfg.debug) {
|
|
835
|
+
console.log('[Monitor] Business error detected by rule:', rule, 'data:', data);
|
|
836
|
+
}
|
|
837
|
+
return true;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
catch {
|
|
843
|
+
// JSON 解析失败,不是业务错误
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
773
847
|
function interceptXHR() {
|
|
774
848
|
XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
|
|
775
849
|
const urlStr = url.toString();
|
|
@@ -821,6 +895,8 @@ function interceptXHR() {
|
|
|
821
895
|
},
|
|
822
896
|
});
|
|
823
897
|
}
|
|
898
|
+
const responseBody = this.responseText?.substring(0, 1000);
|
|
899
|
+
// HTTP 错误或业务错误
|
|
824
900
|
if (status === 0 || status >= 400) {
|
|
825
901
|
reportHttpError({
|
|
826
902
|
method: monitorData.method,
|
|
@@ -828,9 +904,20 @@ function interceptXHR() {
|
|
|
828
904
|
status,
|
|
829
905
|
duration,
|
|
830
906
|
requestBody: monitorData.requestBody,
|
|
831
|
-
responseBody
|
|
907
|
+
responseBody,
|
|
832
908
|
});
|
|
833
909
|
}
|
|
910
|
+
else if (status >= 200 && status < 300 && detectBusinessError(responseBody)) {
|
|
911
|
+
// HTTP 200 但检测到业务错误
|
|
912
|
+
reportHttpError({
|
|
913
|
+
method: monitorData.method,
|
|
914
|
+
url: monitorData.url,
|
|
915
|
+
status,
|
|
916
|
+
duration,
|
|
917
|
+
requestBody: monitorData.requestBody,
|
|
918
|
+
responseBody,
|
|
919
|
+
}, true);
|
|
920
|
+
}
|
|
834
921
|
});
|
|
835
922
|
return originalXHRSend.call(this, body);
|
|
836
923
|
};
|
|
@@ -872,15 +959,16 @@ function interceptFetch() {
|
|
|
872
959
|
},
|
|
873
960
|
});
|
|
874
961
|
}
|
|
962
|
+
// 克隆响应以读取 body(无论成功与否都需要,用于业务错误检测)
|
|
963
|
+
const cloned = response.clone();
|
|
964
|
+
let responseBody;
|
|
965
|
+
try {
|
|
966
|
+
responseBody = await cloned.text();
|
|
967
|
+
responseBody = responseBody.substring(0, 1000);
|
|
968
|
+
}
|
|
969
|
+
catch { }
|
|
875
970
|
if (!response.ok) {
|
|
876
|
-
//
|
|
877
|
-
const cloned = response.clone();
|
|
878
|
-
let responseBody;
|
|
879
|
-
try {
|
|
880
|
-
responseBody = await cloned.text();
|
|
881
|
-
responseBody = responseBody.substring(0, 1000);
|
|
882
|
-
}
|
|
883
|
-
catch { }
|
|
971
|
+
// HTTP 错误
|
|
884
972
|
reportHttpError({
|
|
885
973
|
method,
|
|
886
974
|
url,
|
|
@@ -890,6 +978,17 @@ function interceptFetch() {
|
|
|
890
978
|
responseBody,
|
|
891
979
|
});
|
|
892
980
|
}
|
|
981
|
+
else if (detectBusinessError(responseBody)) {
|
|
982
|
+
// HTTP 200 但检测到业务错误
|
|
983
|
+
reportHttpError({
|
|
984
|
+
method,
|
|
985
|
+
url,
|
|
986
|
+
status,
|
|
987
|
+
duration,
|
|
988
|
+
requestBody,
|
|
989
|
+
responseBody,
|
|
990
|
+
}, true);
|
|
991
|
+
}
|
|
893
992
|
return response;
|
|
894
993
|
}
|
|
895
994
|
catch (error) {
|
|
@@ -925,10 +1024,13 @@ function interceptFetch() {
|
|
|
925
1024
|
/**
|
|
926
1025
|
* 上报 HTTP 错误
|
|
927
1026
|
*/
|
|
928
|
-
function reportHttpError(httpInfo) {
|
|
1027
|
+
function reportHttpError(httpInfo, isBusinessError = false) {
|
|
1028
|
+
const message = isBusinessError
|
|
1029
|
+
? `Business Error ${httpInfo.method} ${httpInfo.url}`
|
|
1030
|
+
: `HTTP ${httpInfo.status} ${httpInfo.method} ${httpInfo.url}`;
|
|
929
1031
|
const errorData = {
|
|
930
1032
|
type: 'http_error',
|
|
931
|
-
message
|
|
1033
|
+
message,
|
|
932
1034
|
httpInfo,
|
|
933
1035
|
};
|
|
934
1036
|
reporter.reportError(errorData);
|