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.umd.js CHANGED
@@ -770,12 +770,93 @@
770
770
  * 检查是否是 SDK 自身的请求
771
771
  */
772
772
  function isSdkRequest(url) {
773
+ // 检查是否匹配常见的 SDK 上报路径
774
+ if (url.includes('/monitor/api/report') ||
775
+ url.includes('/monitor/api/beacon') ||
776
+ url.includes('/monitor/api/config')) {
777
+ return true;
778
+ }
779
+ // 配置已初始化时,检查 dsn
780
+ if (config.isInitialized()) {
781
+ const cfg = config.get();
782
+ if (cfg.dsn && url.includes(cfg.dsn)) {
783
+ return true;
784
+ }
785
+ }
786
+ return false;
787
+ }
788
+ /**
789
+ * 获取嵌套对象的字段值
790
+ * @param obj 对象
791
+ * @param path 字段路径,如 "status" 或 "data.code"
792
+ */
793
+ function getNestedValue(obj, path) {
794
+ if (!obj || typeof obj !== 'object')
795
+ return undefined;
796
+ const keys = path.split('.');
797
+ let value = obj;
798
+ for (const key of keys) {
799
+ if (value === null || value === undefined)
800
+ return undefined;
801
+ value = value[key];
802
+ }
803
+ return value;
804
+ }
805
+ /**
806
+ * 检查单条规则是否匹配
807
+ */
808
+ function matchRule(data, rule) {
809
+ const value = getNestedValue(data, rule.field);
810
+ switch (rule.operator) {
811
+ case 'exists':
812
+ return value !== undefined && value !== null;
813
+ case 'eq':
814
+ return value === rule.value;
815
+ case 'ne':
816
+ return value !== rule.value;
817
+ case 'gt':
818
+ return typeof value === 'number' && typeof rule.value === 'number' && value > rule.value;
819
+ case 'gte':
820
+ return typeof value === 'number' && typeof rule.value === 'number' && value >= rule.value;
821
+ case 'lt':
822
+ return typeof value === 'number' && typeof rule.value === 'number' && value < rule.value;
823
+ case 'lte':
824
+ return typeof value === 'number' && typeof rule.value === 'number' && value <= rule.value;
825
+ default:
826
+ return false;
827
+ }
828
+ }
829
+ /**
830
+ * 检测业务错误
831
+ * @param responseBody 响应体字符串
832
+ * @returns 是否为业务错误
833
+ */
834
+ function detectBusinessError(responseBody) {
835
+ if (!responseBody)
836
+ return false;
773
837
  if (!config.isInitialized())
774
838
  return false;
775
839
  const cfg = config.get();
776
- if (!cfg.dsn)
840
+ const rules = cfg.error?.businessErrorRules;
841
+ if (!rules || rules.length === 0)
842
+ return false;
843
+ try {
844
+ const data = JSON.parse(responseBody);
845
+ // 任一规则匹配时返回 true
846
+ for (const rule of rules) {
847
+ if (matchRule(data, rule)) {
848
+ if (cfg.debug) {
849
+ console.log('[Monitor] Business error detected by rule:', rule, 'data:', data);
850
+ }
851
+ return true;
852
+ }
853
+ }
777
854
  return false;
778
- return url.includes(cfg.dsn);
855
+ }
856
+ catch {
857
+ // JSON 解析失败,不是业务错误
858
+ return false;
859
+ }
779
860
  }
780
861
  function interceptXHR() {
781
862
  XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
@@ -828,6 +909,8 @@
828
909
  },
829
910
  });
830
911
  }
912
+ const responseBody = this.responseText?.substring(0, 1000);
913
+ // HTTP 错误或业务错误
831
914
  if (status === 0 || status >= 400) {
832
915
  reportHttpError({
833
916
  method: monitorData.method,
@@ -835,9 +918,20 @@
835
918
  status,
836
919
  duration,
837
920
  requestBody: monitorData.requestBody,
838
- responseBody: this.responseText?.substring(0, 1000), // 限制长度
921
+ responseBody,
839
922
  });
840
923
  }
924
+ else if (status >= 200 && status < 300 && detectBusinessError(responseBody)) {
925
+ // HTTP 200 但检测到业务错误
926
+ reportHttpError({
927
+ method: monitorData.method,
928
+ url: monitorData.url,
929
+ status,
930
+ duration,
931
+ requestBody: monitorData.requestBody,
932
+ responseBody,
933
+ }, true);
934
+ }
841
935
  });
842
936
  return originalXHRSend.call(this, body);
843
937
  };
@@ -879,8 +973,10 @@
879
973
  },
880
974
  });
881
975
  }
882
- if (!response.ok) {
883
- // 克隆响应以读取 body
976
+ // 检查是否配置了业务错误规则
977
+ const hasBusinessRules = cfg?.error?.businessErrorRules && cfg.error.businessErrorRules.length > 0;
978
+ // 只有在请求失败或配置了业务规则时才读取响应体
979
+ if (!response.ok || hasBusinessRules) {
884
980
  const cloned = response.clone();
885
981
  let responseBody;
886
982
  try {
@@ -888,14 +984,28 @@
888
984
  responseBody = responseBody.substring(0, 1000);
889
985
  }
890
986
  catch { }
891
- reportHttpError({
892
- method,
893
- url,
894
- status,
895
- duration,
896
- requestBody,
897
- responseBody,
898
- });
987
+ if (!response.ok) {
988
+ // HTTP 错误
989
+ reportHttpError({
990
+ method,
991
+ url,
992
+ status,
993
+ duration,
994
+ requestBody,
995
+ responseBody,
996
+ });
997
+ }
998
+ else if (hasBusinessRules && detectBusinessError(responseBody)) {
999
+ // HTTP 200 但检测到业务错误
1000
+ reportHttpError({
1001
+ method,
1002
+ url,
1003
+ status,
1004
+ duration,
1005
+ requestBody,
1006
+ responseBody,
1007
+ }, true);
1008
+ }
899
1009
  }
900
1010
  return response;
901
1011
  }
@@ -932,10 +1042,13 @@
932
1042
  /**
933
1043
  * 上报 HTTP 错误
934
1044
  */
935
- function reportHttpError(httpInfo) {
1045
+ function reportHttpError(httpInfo, isBusinessError = false) {
1046
+ const message = isBusinessError
1047
+ ? `Business Error ${httpInfo.method} ${httpInfo.url}`
1048
+ : `HTTP ${httpInfo.status} ${httpInfo.method} ${httpInfo.url}`;
936
1049
  const errorData = {
937
1050
  type: 'http_error',
938
- message: `HTTP ${httpInfo.status} ${httpInfo.method} ${httpInfo.url}`,
1051
+ message,
939
1052
  httpInfo,
940
1053
  };
941
1054
  reporter.reportError(errorData);