sobey-monitor-sdk 1.1.8 → 1.1.10
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 +128 -15
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.esm.js +128 -15
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +128 -15
- package/dist/index.umd.js.map +1 -1
- package/dist/types/index.d.ts +18 -0
- 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
|
* 性能监控配置
|
package/dist/index.esm.js
CHANGED
|
@@ -764,12 +764,93 @@ function installHttpErrorHandler() {
|
|
|
764
764
|
* 检查是否是 SDK 自身的请求
|
|
765
765
|
*/
|
|
766
766
|
function isSdkRequest(url) {
|
|
767
|
+
// 检查是否匹配常见的 SDK 上报路径
|
|
768
|
+
if (url.includes('/monitor/api/report') ||
|
|
769
|
+
url.includes('/monitor/api/beacon') ||
|
|
770
|
+
url.includes('/monitor/api/config')) {
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
// 配置已初始化时,检查 dsn
|
|
774
|
+
if (config.isInitialized()) {
|
|
775
|
+
const cfg = config.get();
|
|
776
|
+
if (cfg.dsn && url.includes(cfg.dsn)) {
|
|
777
|
+
return true;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* 获取嵌套对象的字段值
|
|
784
|
+
* @param obj 对象
|
|
785
|
+
* @param path 字段路径,如 "status" 或 "data.code"
|
|
786
|
+
*/
|
|
787
|
+
function getNestedValue(obj, path) {
|
|
788
|
+
if (!obj || typeof obj !== 'object')
|
|
789
|
+
return undefined;
|
|
790
|
+
const keys = path.split('.');
|
|
791
|
+
let value = obj;
|
|
792
|
+
for (const key of keys) {
|
|
793
|
+
if (value === null || value === undefined)
|
|
794
|
+
return undefined;
|
|
795
|
+
value = value[key];
|
|
796
|
+
}
|
|
797
|
+
return value;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* 检查单条规则是否匹配
|
|
801
|
+
*/
|
|
802
|
+
function matchRule(data, rule) {
|
|
803
|
+
const value = getNestedValue(data, rule.field);
|
|
804
|
+
switch (rule.operator) {
|
|
805
|
+
case 'exists':
|
|
806
|
+
return value !== undefined && value !== null;
|
|
807
|
+
case 'eq':
|
|
808
|
+
return value === rule.value;
|
|
809
|
+
case 'ne':
|
|
810
|
+
return value !== rule.value;
|
|
811
|
+
case 'gt':
|
|
812
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value > rule.value;
|
|
813
|
+
case 'gte':
|
|
814
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value >= rule.value;
|
|
815
|
+
case 'lt':
|
|
816
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value < rule.value;
|
|
817
|
+
case 'lte':
|
|
818
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value <= rule.value;
|
|
819
|
+
default:
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* 检测业务错误
|
|
825
|
+
* @param responseBody 响应体字符串
|
|
826
|
+
* @returns 是否为业务错误
|
|
827
|
+
*/
|
|
828
|
+
function detectBusinessError(responseBody) {
|
|
829
|
+
if (!responseBody)
|
|
830
|
+
return false;
|
|
767
831
|
if (!config.isInitialized())
|
|
768
832
|
return false;
|
|
769
833
|
const cfg = config.get();
|
|
770
|
-
|
|
834
|
+
const rules = cfg.error?.businessErrorRules;
|
|
835
|
+
if (!rules || rules.length === 0)
|
|
836
|
+
return false;
|
|
837
|
+
try {
|
|
838
|
+
const data = JSON.parse(responseBody);
|
|
839
|
+
// 任一规则匹配时返回 true
|
|
840
|
+
for (const rule of rules) {
|
|
841
|
+
if (matchRule(data, rule)) {
|
|
842
|
+
if (cfg.debug) {
|
|
843
|
+
console.log('[Monitor] Business error detected by rule:', rule, 'data:', data);
|
|
844
|
+
}
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
771
848
|
return false;
|
|
772
|
-
|
|
849
|
+
}
|
|
850
|
+
catch {
|
|
851
|
+
// JSON 解析失败,不是业务错误
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
773
854
|
}
|
|
774
855
|
function interceptXHR() {
|
|
775
856
|
XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
|
|
@@ -822,6 +903,8 @@ function interceptXHR() {
|
|
|
822
903
|
},
|
|
823
904
|
});
|
|
824
905
|
}
|
|
906
|
+
const responseBody = this.responseText?.substring(0, 1000);
|
|
907
|
+
// HTTP 错误或业务错误
|
|
825
908
|
if (status === 0 || status >= 400) {
|
|
826
909
|
reportHttpError({
|
|
827
910
|
method: monitorData.method,
|
|
@@ -829,9 +912,20 @@ function interceptXHR() {
|
|
|
829
912
|
status,
|
|
830
913
|
duration,
|
|
831
914
|
requestBody: monitorData.requestBody,
|
|
832
|
-
responseBody
|
|
915
|
+
responseBody,
|
|
833
916
|
});
|
|
834
917
|
}
|
|
918
|
+
else if (status >= 200 && status < 300 && detectBusinessError(responseBody)) {
|
|
919
|
+
// HTTP 200 但检测到业务错误
|
|
920
|
+
reportHttpError({
|
|
921
|
+
method: monitorData.method,
|
|
922
|
+
url: monitorData.url,
|
|
923
|
+
status,
|
|
924
|
+
duration,
|
|
925
|
+
requestBody: monitorData.requestBody,
|
|
926
|
+
responseBody,
|
|
927
|
+
}, true);
|
|
928
|
+
}
|
|
835
929
|
});
|
|
836
930
|
return originalXHRSend.call(this, body);
|
|
837
931
|
};
|
|
@@ -873,8 +967,10 @@ function interceptFetch() {
|
|
|
873
967
|
},
|
|
874
968
|
});
|
|
875
969
|
}
|
|
876
|
-
|
|
877
|
-
|
|
970
|
+
// 检查是否配置了业务错误规则
|
|
971
|
+
const hasBusinessRules = cfg?.error?.businessErrorRules && cfg.error.businessErrorRules.length > 0;
|
|
972
|
+
// 只有在请求失败或配置了业务规则时才读取响应体
|
|
973
|
+
if (!response.ok || hasBusinessRules) {
|
|
878
974
|
const cloned = response.clone();
|
|
879
975
|
let responseBody;
|
|
880
976
|
try {
|
|
@@ -882,14 +978,28 @@ function interceptFetch() {
|
|
|
882
978
|
responseBody = responseBody.substring(0, 1000);
|
|
883
979
|
}
|
|
884
980
|
catch { }
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
981
|
+
if (!response.ok) {
|
|
982
|
+
// HTTP 错误
|
|
983
|
+
reportHttpError({
|
|
984
|
+
method,
|
|
985
|
+
url,
|
|
986
|
+
status,
|
|
987
|
+
duration,
|
|
988
|
+
requestBody,
|
|
989
|
+
responseBody,
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
else if (hasBusinessRules && detectBusinessError(responseBody)) {
|
|
993
|
+
// HTTP 200 但检测到业务错误
|
|
994
|
+
reportHttpError({
|
|
995
|
+
method,
|
|
996
|
+
url,
|
|
997
|
+
status,
|
|
998
|
+
duration,
|
|
999
|
+
requestBody,
|
|
1000
|
+
responseBody,
|
|
1001
|
+
}, true);
|
|
1002
|
+
}
|
|
893
1003
|
}
|
|
894
1004
|
return response;
|
|
895
1005
|
}
|
|
@@ -926,10 +1036,13 @@ function interceptFetch() {
|
|
|
926
1036
|
/**
|
|
927
1037
|
* 上报 HTTP 错误
|
|
928
1038
|
*/
|
|
929
|
-
function reportHttpError(httpInfo) {
|
|
1039
|
+
function reportHttpError(httpInfo, isBusinessError = false) {
|
|
1040
|
+
const message = isBusinessError
|
|
1041
|
+
? `Business Error ${httpInfo.method} ${httpInfo.url}`
|
|
1042
|
+
: `HTTP ${httpInfo.status} ${httpInfo.method} ${httpInfo.url}`;
|
|
930
1043
|
const errorData = {
|
|
931
1044
|
type: 'http_error',
|
|
932
|
-
message
|
|
1045
|
+
message,
|
|
933
1046
|
httpInfo,
|
|
934
1047
|
};
|
|
935
1048
|
reporter.reportError(errorData);
|