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.cjs.js
CHANGED
|
@@ -735,11 +735,12 @@ function installResourceErrorHandler() {
|
|
|
735
735
|
};
|
|
736
736
|
// 添加到面包屑
|
|
737
737
|
context.addBreadcrumb({
|
|
738
|
-
type: '
|
|
738
|
+
type: 'resource',
|
|
739
739
|
category: 'resource_error',
|
|
740
740
|
data: {
|
|
741
741
|
tagName,
|
|
742
|
-
resourceUrl,
|
|
742
|
+
url: resourceUrl,
|
|
743
|
+
error: `Failed to load ${tagName}`,
|
|
743
744
|
},
|
|
744
745
|
});
|
|
745
746
|
reporter.reportError(errorData);
|
|
@@ -774,6 +775,79 @@ function isSdkRequest(url) {
|
|
|
774
775
|
return false;
|
|
775
776
|
return url.includes(cfg.dsn);
|
|
776
777
|
}
|
|
778
|
+
/**
|
|
779
|
+
* 获取嵌套对象的字段值
|
|
780
|
+
* @param obj 对象
|
|
781
|
+
* @param path 字段路径,如 "status" 或 "data.code"
|
|
782
|
+
*/
|
|
783
|
+
function getNestedValue(obj, path) {
|
|
784
|
+
if (!obj || typeof obj !== 'object')
|
|
785
|
+
return undefined;
|
|
786
|
+
const keys = path.split('.');
|
|
787
|
+
let value = obj;
|
|
788
|
+
for (const key of keys) {
|
|
789
|
+
if (value === null || value === undefined)
|
|
790
|
+
return undefined;
|
|
791
|
+
value = value[key];
|
|
792
|
+
}
|
|
793
|
+
return value;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* 检查单条规则是否匹配
|
|
797
|
+
*/
|
|
798
|
+
function matchRule(data, rule) {
|
|
799
|
+
const value = getNestedValue(data, rule.field);
|
|
800
|
+
switch (rule.operator) {
|
|
801
|
+
case 'exists':
|
|
802
|
+
return value !== undefined && value !== null;
|
|
803
|
+
case 'eq':
|
|
804
|
+
return value === rule.value;
|
|
805
|
+
case 'ne':
|
|
806
|
+
return value !== rule.value;
|
|
807
|
+
case 'gt':
|
|
808
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value > rule.value;
|
|
809
|
+
case 'gte':
|
|
810
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value >= rule.value;
|
|
811
|
+
case 'lt':
|
|
812
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value < rule.value;
|
|
813
|
+
case 'lte':
|
|
814
|
+
return typeof value === 'number' && typeof rule.value === 'number' && value <= rule.value;
|
|
815
|
+
default:
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* 检测业务错误
|
|
821
|
+
* @param responseBody 响应体字符串
|
|
822
|
+
* @returns 是否为业务错误
|
|
823
|
+
*/
|
|
824
|
+
function detectBusinessError(responseBody) {
|
|
825
|
+
if (!responseBody)
|
|
826
|
+
return false;
|
|
827
|
+
if (!config.isInitialized())
|
|
828
|
+
return false;
|
|
829
|
+
const cfg = config.get();
|
|
830
|
+
const rules = cfg.error?.businessErrorRules;
|
|
831
|
+
if (!rules || rules.length === 0)
|
|
832
|
+
return false;
|
|
833
|
+
try {
|
|
834
|
+
const data = JSON.parse(responseBody);
|
|
835
|
+
// 任一规则匹配时返回 true
|
|
836
|
+
for (const rule of rules) {
|
|
837
|
+
if (matchRule(data, rule)) {
|
|
838
|
+
if (cfg.debug) {
|
|
839
|
+
console.log('[Monitor] Business error detected by rule:', rule, 'data:', data);
|
|
840
|
+
}
|
|
841
|
+
return true;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
catch {
|
|
847
|
+
// JSON 解析失败,不是业务错误
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
777
851
|
function interceptXHR() {
|
|
778
852
|
XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
|
|
779
853
|
const urlStr = url.toString();
|
|
@@ -825,6 +899,8 @@ function interceptXHR() {
|
|
|
825
899
|
},
|
|
826
900
|
});
|
|
827
901
|
}
|
|
902
|
+
const responseBody = this.responseText?.substring(0, 1000);
|
|
903
|
+
// HTTP 错误或业务错误
|
|
828
904
|
if (status === 0 || status >= 400) {
|
|
829
905
|
reportHttpError({
|
|
830
906
|
method: monitorData.method,
|
|
@@ -832,9 +908,20 @@ function interceptXHR() {
|
|
|
832
908
|
status,
|
|
833
909
|
duration,
|
|
834
910
|
requestBody: monitorData.requestBody,
|
|
835
|
-
responseBody
|
|
911
|
+
responseBody,
|
|
836
912
|
});
|
|
837
913
|
}
|
|
914
|
+
else if (status >= 200 && status < 300 && detectBusinessError(responseBody)) {
|
|
915
|
+
// HTTP 200 但检测到业务错误
|
|
916
|
+
reportHttpError({
|
|
917
|
+
method: monitorData.method,
|
|
918
|
+
url: monitorData.url,
|
|
919
|
+
status,
|
|
920
|
+
duration,
|
|
921
|
+
requestBody: monitorData.requestBody,
|
|
922
|
+
responseBody,
|
|
923
|
+
}, true);
|
|
924
|
+
}
|
|
838
925
|
});
|
|
839
926
|
return originalXHRSend.call(this, body);
|
|
840
927
|
};
|
|
@@ -876,15 +963,16 @@ function interceptFetch() {
|
|
|
876
963
|
},
|
|
877
964
|
});
|
|
878
965
|
}
|
|
966
|
+
// 克隆响应以读取 body(无论成功与否都需要,用于业务错误检测)
|
|
967
|
+
const cloned = response.clone();
|
|
968
|
+
let responseBody;
|
|
969
|
+
try {
|
|
970
|
+
responseBody = await cloned.text();
|
|
971
|
+
responseBody = responseBody.substring(0, 1000);
|
|
972
|
+
}
|
|
973
|
+
catch { }
|
|
879
974
|
if (!response.ok) {
|
|
880
|
-
//
|
|
881
|
-
const cloned = response.clone();
|
|
882
|
-
let responseBody;
|
|
883
|
-
try {
|
|
884
|
-
responseBody = await cloned.text();
|
|
885
|
-
responseBody = responseBody.substring(0, 1000);
|
|
886
|
-
}
|
|
887
|
-
catch { }
|
|
975
|
+
// HTTP 错误
|
|
888
976
|
reportHttpError({
|
|
889
977
|
method,
|
|
890
978
|
url,
|
|
@@ -894,6 +982,17 @@ function interceptFetch() {
|
|
|
894
982
|
responseBody,
|
|
895
983
|
});
|
|
896
984
|
}
|
|
985
|
+
else if (detectBusinessError(responseBody)) {
|
|
986
|
+
// HTTP 200 但检测到业务错误
|
|
987
|
+
reportHttpError({
|
|
988
|
+
method,
|
|
989
|
+
url,
|
|
990
|
+
status,
|
|
991
|
+
duration,
|
|
992
|
+
requestBody,
|
|
993
|
+
responseBody,
|
|
994
|
+
}, true);
|
|
995
|
+
}
|
|
897
996
|
return response;
|
|
898
997
|
}
|
|
899
998
|
catch (error) {
|
|
@@ -929,10 +1028,13 @@ function interceptFetch() {
|
|
|
929
1028
|
/**
|
|
930
1029
|
* 上报 HTTP 错误
|
|
931
1030
|
*/
|
|
932
|
-
function reportHttpError(httpInfo) {
|
|
1031
|
+
function reportHttpError(httpInfo, isBusinessError = false) {
|
|
1032
|
+
const message = isBusinessError
|
|
1033
|
+
? `Business Error ${httpInfo.method} ${httpInfo.url}`
|
|
1034
|
+
: `HTTP ${httpInfo.status} ${httpInfo.method} ${httpInfo.url}`;
|
|
933
1035
|
const errorData = {
|
|
934
1036
|
type: 'http_error',
|
|
935
|
-
message
|
|
1037
|
+
message,
|
|
936
1038
|
httpInfo,
|
|
937
1039
|
};
|
|
938
1040
|
reporter.reportError(errorData);
|