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