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