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