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.cjs.js CHANGED
@@ -775,6 +775,79 @@ function isSdkRequest(url) {
775
775
  return false;
776
776
  return url.includes(cfg.dsn);
777
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
+ }
778
851
  function interceptXHR() {
779
852
  XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
780
853
  const urlStr = url.toString();
@@ -826,6 +899,8 @@ function interceptXHR() {
826
899
  },
827
900
  });
828
901
  }
902
+ const responseBody = this.responseText?.substring(0, 1000);
903
+ // HTTP 错误或业务错误
829
904
  if (status === 0 || status >= 400) {
830
905
  reportHttpError({
831
906
  method: monitorData.method,
@@ -833,9 +908,20 @@ function interceptXHR() {
833
908
  status,
834
909
  duration,
835
910
  requestBody: monitorData.requestBody,
836
- responseBody: this.responseText?.substring(0, 1000), // 限制长度
911
+ responseBody,
837
912
  });
838
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
+ }
839
925
  });
840
926
  return originalXHRSend.call(this, body);
841
927
  };
@@ -877,15 +963,16 @@ function interceptFetch() {
877
963
  },
878
964
  });
879
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 { }
880
974
  if (!response.ok) {
881
- // 克隆响应以读取 body
882
- const cloned = response.clone();
883
- let responseBody;
884
- try {
885
- responseBody = await cloned.text();
886
- responseBody = responseBody.substring(0, 1000);
887
- }
888
- catch { }
975
+ // HTTP 错误
889
976
  reportHttpError({
890
977
  method,
891
978
  url,
@@ -895,6 +982,17 @@ function interceptFetch() {
895
982
  responseBody,
896
983
  });
897
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
+ }
898
996
  return response;
899
997
  }
900
998
  catch (error) {
@@ -930,10 +1028,13 @@ function interceptFetch() {
930
1028
  /**
931
1029
  * 上报 HTTP 错误
932
1030
  */
933
- 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}`;
934
1035
  const errorData = {
935
1036
  type: 'http_error',
936
- message: `HTTP ${httpInfo.status} ${httpInfo.method} ${httpInfo.url}`,
1037
+ message,
937
1038
  httpInfo,
938
1039
  };
939
1040
  reporter.reportError(errorData);