@be-link/cls-logger 1.0.1-beta.1 → 1.0.1-beta.11
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/ClsLogger.d.ts +9 -94
- package/dist/ClsLogger.d.ts.map +1 -1
- package/dist/ClsLoggerCore.d.ts +127 -0
- package/dist/ClsLoggerCore.d.ts.map +1 -0
- package/dist/ClsLoggerMini.d.ts +8 -0
- package/dist/ClsLoggerMini.d.ts.map +1 -0
- package/dist/ClsLoggerWeb.d.ts +8 -0
- package/dist/ClsLoggerWeb.d.ts.map +1 -0
- package/dist/behaviorMonitor.d.ts.map +1 -1
- package/dist/clsSdkTypes.d.ts +18 -0
- package/dist/clsSdkTypes.d.ts.map +1 -0
- package/dist/errorMonitor.d.ts.map +1 -1
- package/dist/index.esm.js +407 -169
- package/dist/index.js +407 -169
- package/dist/index.umd.js +411 -171
- package/dist/mini.d.ts +6 -0
- package/dist/mini.d.ts.map +1 -0
- package/dist/mini.esm.js +2496 -0
- package/dist/mini.js +2521 -0
- package/dist/performanceMonitor.d.ts.map +1 -1
- package/dist/sdkUtils.d.ts +3 -0
- package/dist/sdkUtils.d.ts.map +1 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/web.d.ts +6 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.esm.js +2466 -0
- package/dist/web.js +2491 -0
- package/package.json +38 -2
package/dist/index.js
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var tencentcloudClsSdkJsWeb = require('tencentcloud-cls-sdk-js-web');
|
|
6
|
-
|
|
7
5
|
function isPlainObject(value) {
|
|
8
6
|
return Object.prototype.toString.call(value) === '[object Object]';
|
|
9
7
|
}
|
|
@@ -540,6 +538,8 @@ function installRequestMonitor(report, opts = {}) {
|
|
|
540
538
|
}
|
|
541
539
|
|
|
542
540
|
const DEFAULT_MAX_TEXT = 4000;
|
|
541
|
+
const DEFAULT_DEDUPE_WINDOW_MS = 3000;
|
|
542
|
+
const DEFAULT_DEDUPE_MAX_KEYS = 200;
|
|
543
543
|
function truncate$2(s, maxLen) {
|
|
544
544
|
if (!s)
|
|
545
545
|
return s;
|
|
@@ -565,7 +565,17 @@ function getPagePath$1() {
|
|
|
565
565
|
function normalizeErrorLike(err, maxTextLength) {
|
|
566
566
|
if (err && typeof err === 'object') {
|
|
567
567
|
const anyErr = err;
|
|
568
|
-
|
|
568
|
+
let rawMsg = anyErr.message;
|
|
569
|
+
if (!rawMsg) {
|
|
570
|
+
const str = anyErr.toString?.();
|
|
571
|
+
if (!str || str === '[object Object]') {
|
|
572
|
+
rawMsg = stringifyLogValue(anyErr);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
rawMsg = str;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const message = truncate$2(String(rawMsg ?? ''), maxTextLength);
|
|
569
579
|
const name = truncate$2(String(anyErr.name ?? ''), 200);
|
|
570
580
|
const stack = truncate$2(String(anyErr.stack ?? ''), maxTextLength);
|
|
571
581
|
return { message, name, stack };
|
|
@@ -573,6 +583,54 @@ function normalizeErrorLike(err, maxTextLength) {
|
|
|
573
583
|
const message = truncate$2(stringifyLogValue(err), maxTextLength);
|
|
574
584
|
return { message, name: '', stack: '' };
|
|
575
585
|
}
|
|
586
|
+
function createDedupeGuard(options) {
|
|
587
|
+
const cache = new Map(); // key -> lastReportAt
|
|
588
|
+
const maxKeys = Math.max(0, options.dedupeMaxKeys);
|
|
589
|
+
const windowMs = Math.max(0, options.dedupeWindowMs);
|
|
590
|
+
function touch(key, now) {
|
|
591
|
+
// refresh insertion order
|
|
592
|
+
if (cache.has(key))
|
|
593
|
+
cache.delete(key);
|
|
594
|
+
cache.set(key, now);
|
|
595
|
+
if (maxKeys > 0) {
|
|
596
|
+
while (cache.size > maxKeys) {
|
|
597
|
+
const first = cache.keys().next().value;
|
|
598
|
+
if (!first)
|
|
599
|
+
break;
|
|
600
|
+
cache.delete(first);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return (key) => {
|
|
605
|
+
if (!key)
|
|
606
|
+
return true;
|
|
607
|
+
if (windowMs <= 0 || maxKeys === 0)
|
|
608
|
+
return true;
|
|
609
|
+
const now = Date.now();
|
|
610
|
+
const last = cache.get(key);
|
|
611
|
+
if (typeof last === 'number' && now - last < windowMs)
|
|
612
|
+
return false;
|
|
613
|
+
touch(key, now);
|
|
614
|
+
return true;
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
function buildErrorKey(type, payload) {
|
|
618
|
+
// 使用最核心、最稳定的字段构造签名,避免把瞬态字段(如 time)带入导致失效
|
|
619
|
+
const parts = [
|
|
620
|
+
type,
|
|
621
|
+
String(payload.source ?? ''),
|
|
622
|
+
String(payload.pagePath ?? ''),
|
|
623
|
+
String(payload.message ?? ''),
|
|
624
|
+
String(payload.errorName ?? ''),
|
|
625
|
+
String(payload.stack ?? ''),
|
|
626
|
+
String(payload.filename ?? ''),
|
|
627
|
+
String(payload.lineno ?? ''),
|
|
628
|
+
String(payload.colno ?? ''),
|
|
629
|
+
String(payload.tagName ?? ''),
|
|
630
|
+
String(payload.resourceUrl ?? ''),
|
|
631
|
+
];
|
|
632
|
+
return parts.join('|');
|
|
633
|
+
}
|
|
576
634
|
function installBrowserErrorMonitor(report, options) {
|
|
577
635
|
if (typeof window === 'undefined')
|
|
578
636
|
return;
|
|
@@ -580,6 +638,7 @@ function installBrowserErrorMonitor(report, options) {
|
|
|
580
638
|
if (w.__beLinkClsLoggerErrorInstalled__)
|
|
581
639
|
return;
|
|
582
640
|
w.__beLinkClsLoggerErrorInstalled__ = true;
|
|
641
|
+
const shouldReport = createDedupeGuard(options);
|
|
583
642
|
window.addEventListener('error', (event) => {
|
|
584
643
|
try {
|
|
585
644
|
if (!sampleHit$1(options.sampleRate))
|
|
@@ -602,6 +661,8 @@ function installBrowserErrorMonitor(report, options) {
|
|
|
602
661
|
if (e.stack)
|
|
603
662
|
payload.stack = e.stack;
|
|
604
663
|
}
|
|
664
|
+
if (!shouldReport(buildErrorKey(options.reportType, payload)))
|
|
665
|
+
return;
|
|
605
666
|
report(options.reportType, payload);
|
|
606
667
|
return;
|
|
607
668
|
}
|
|
@@ -613,6 +674,8 @@ function installBrowserErrorMonitor(report, options) {
|
|
|
613
674
|
payload.source = 'resource.error';
|
|
614
675
|
payload.tagName = tagName;
|
|
615
676
|
payload.resourceUrl = truncate$2(url, 2000);
|
|
677
|
+
if (!shouldReport(buildErrorKey(options.reportType, payload)))
|
|
678
|
+
return;
|
|
616
679
|
report(options.reportType, payload);
|
|
617
680
|
}
|
|
618
681
|
}
|
|
@@ -633,6 +696,8 @@ function installBrowserErrorMonitor(report, options) {
|
|
|
633
696
|
errorName: e.name,
|
|
634
697
|
stack: e.stack,
|
|
635
698
|
};
|
|
699
|
+
if (!shouldReport(buildErrorKey(options.reportType, payload)))
|
|
700
|
+
return;
|
|
636
701
|
report(options.reportType, payload);
|
|
637
702
|
}
|
|
638
703
|
catch {
|
|
@@ -645,6 +710,7 @@ function installMiniProgramErrorMonitor(report, options) {
|
|
|
645
710
|
if (g.__beLinkClsLoggerMpErrorInstalled__)
|
|
646
711
|
return;
|
|
647
712
|
g.__beLinkClsLoggerMpErrorInstalled__ = true;
|
|
713
|
+
const shouldReport = createDedupeGuard(options);
|
|
648
714
|
const wxAny = globalThis.wx;
|
|
649
715
|
// wx.* 事件(兼容在 App 已经创建后的场景)
|
|
650
716
|
try {
|
|
@@ -653,10 +719,16 @@ function installMiniProgramErrorMonitor(report, options) {
|
|
|
653
719
|
try {
|
|
654
720
|
if (!sampleHit$1(options.sampleRate))
|
|
655
721
|
return;
|
|
656
|
-
|
|
722
|
+
const e = normalizeErrorLike(msg, options.maxTextLength);
|
|
723
|
+
const payload = {
|
|
657
724
|
source: 'wx.onError',
|
|
658
|
-
message:
|
|
659
|
-
|
|
725
|
+
message: e.message,
|
|
726
|
+
errorName: e.name,
|
|
727
|
+
stack: e.stack,
|
|
728
|
+
};
|
|
729
|
+
if (!shouldReport(buildErrorKey(options.reportType, payload)))
|
|
730
|
+
return;
|
|
731
|
+
report(options.reportType, payload);
|
|
660
732
|
}
|
|
661
733
|
catch {
|
|
662
734
|
// ignore
|
|
@@ -674,12 +746,15 @@ function installMiniProgramErrorMonitor(report, options) {
|
|
|
674
746
|
if (!sampleHit$1(options.sampleRate))
|
|
675
747
|
return;
|
|
676
748
|
const e = normalizeErrorLike(res?.reason, options.maxTextLength);
|
|
677
|
-
|
|
749
|
+
const payload = {
|
|
678
750
|
source: 'wx.onUnhandledRejection',
|
|
679
751
|
message: e.message,
|
|
680
752
|
errorName: e.name,
|
|
681
753
|
stack: e.stack,
|
|
682
|
-
}
|
|
754
|
+
};
|
|
755
|
+
if (!shouldReport(buildErrorKey(options.reportType, payload)))
|
|
756
|
+
return;
|
|
757
|
+
report(options.reportType, payload);
|
|
683
758
|
}
|
|
684
759
|
catch {
|
|
685
760
|
// ignore
|
|
@@ -701,10 +776,15 @@ function installMiniProgramErrorMonitor(report, options) {
|
|
|
701
776
|
next.onError = function (...args) {
|
|
702
777
|
try {
|
|
703
778
|
if (sampleHit$1(options.sampleRate)) {
|
|
704
|
-
|
|
779
|
+
const e = normalizeErrorLike(args?.[0], options.maxTextLength);
|
|
780
|
+
const payload = {
|
|
705
781
|
source: 'App.onError',
|
|
706
|
-
message:
|
|
707
|
-
|
|
782
|
+
message: e.message,
|
|
783
|
+
errorName: e.name,
|
|
784
|
+
stack: e.stack,
|
|
785
|
+
};
|
|
786
|
+
if (shouldReport(buildErrorKey(options.reportType, payload)))
|
|
787
|
+
report(options.reportType, payload);
|
|
708
788
|
}
|
|
709
789
|
}
|
|
710
790
|
catch {
|
|
@@ -720,12 +800,14 @@ function installMiniProgramErrorMonitor(report, options) {
|
|
|
720
800
|
if (sampleHit$1(options.sampleRate)) {
|
|
721
801
|
const reason = args?.[0]?.reason ?? args?.[0];
|
|
722
802
|
const e = normalizeErrorLike(reason, options.maxTextLength);
|
|
723
|
-
|
|
803
|
+
const payload = {
|
|
724
804
|
source: 'App.onUnhandledRejection',
|
|
725
805
|
message: e.message,
|
|
726
806
|
errorName: e.name,
|
|
727
807
|
stack: e.stack,
|
|
728
|
-
}
|
|
808
|
+
};
|
|
809
|
+
if (shouldReport(buildErrorKey(options.reportType, payload)))
|
|
810
|
+
report(options.reportType, payload);
|
|
729
811
|
}
|
|
730
812
|
}
|
|
731
813
|
catch {
|
|
@@ -754,6 +836,8 @@ function installErrorMonitor(report, opts = {}) {
|
|
|
754
836
|
sampleRate: raw.sampleRate ?? 1,
|
|
755
837
|
captureResourceError: raw.captureResourceError ?? true,
|
|
756
838
|
maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT,
|
|
839
|
+
dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS,
|
|
840
|
+
dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS,
|
|
757
841
|
};
|
|
758
842
|
if (isMiniProgramEnv()) {
|
|
759
843
|
installMiniProgramErrorMonitor(report, options);
|
|
@@ -1004,140 +1088,58 @@ function installBrowserPerformanceMonitor(report, options) {
|
|
|
1004
1088
|
}
|
|
1005
1089
|
}
|
|
1006
1090
|
}
|
|
1007
|
-
function wrapMiniProgramRouteApi(report, reportType, apiName, options) {
|
|
1008
|
-
const wxAny = globalThis.wx;
|
|
1009
|
-
if (!wxAny || typeof wxAny[apiName] !== 'function')
|
|
1010
|
-
return;
|
|
1011
|
-
const flagKey = `__beLinkClsLoggerMpRouteWrapped__${apiName}`;
|
|
1012
|
-
if (wxAny[flagKey])
|
|
1013
|
-
return;
|
|
1014
|
-
wxAny[flagKey] = true;
|
|
1015
|
-
const raw = wxAny[apiName].bind(wxAny);
|
|
1016
|
-
wxAny[apiName] = (opts) => {
|
|
1017
|
-
const start = Date.now();
|
|
1018
|
-
const url = opts?.url ? String(opts.url) : '';
|
|
1019
|
-
const wrapCb = (cb, success) => {
|
|
1020
|
-
return (...args) => {
|
|
1021
|
-
try {
|
|
1022
|
-
if (sampleHit(options.sampleRate)) {
|
|
1023
|
-
report(reportType, {
|
|
1024
|
-
metric: 'route',
|
|
1025
|
-
api: apiName,
|
|
1026
|
-
url,
|
|
1027
|
-
duration: Date.now() - start,
|
|
1028
|
-
success: success ? 1 : 0,
|
|
1029
|
-
error: success ? '' : truncate$1(stringifyLogValue(args?.[0]), options.maxTextLength),
|
|
1030
|
-
unit: 'ms',
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
catch {
|
|
1035
|
-
// ignore
|
|
1036
|
-
}
|
|
1037
|
-
if (typeof cb === 'function')
|
|
1038
|
-
return cb(...args);
|
|
1039
|
-
return undefined;
|
|
1040
|
-
};
|
|
1041
|
-
};
|
|
1042
|
-
const next = { ...(opts ?? {}) };
|
|
1043
|
-
next.success = wrapCb(next.success, true);
|
|
1044
|
-
next.fail = wrapCb(next.fail, false);
|
|
1045
|
-
return raw(next);
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
function installMiniProgramPageRenderMonitor(report, reportType, options) {
|
|
1049
|
-
const g = globalThis;
|
|
1050
|
-
if (typeof g.Page !== 'function')
|
|
1051
|
-
return;
|
|
1052
|
-
if (g.__beLinkClsLoggerPageWrapped__)
|
|
1053
|
-
return;
|
|
1054
|
-
g.__beLinkClsLoggerPageWrapped__ = true;
|
|
1055
|
-
const rawPage = g.Page;
|
|
1056
|
-
g.Page = (pageOptions) => {
|
|
1057
|
-
const next = { ...(pageOptions ?? {}) };
|
|
1058
|
-
const rawOnLoad = next.onLoad;
|
|
1059
|
-
const rawOnReady = next.onReady;
|
|
1060
|
-
next.onLoad = function (...args) {
|
|
1061
|
-
try {
|
|
1062
|
-
this.__beLinkClsLoggerPageLoadTs__ = Date.now();
|
|
1063
|
-
}
|
|
1064
|
-
catch {
|
|
1065
|
-
// ignore
|
|
1066
|
-
}
|
|
1067
|
-
if (typeof rawOnLoad === 'function')
|
|
1068
|
-
return rawOnLoad.apply(this, args);
|
|
1069
|
-
return undefined;
|
|
1070
|
-
};
|
|
1071
|
-
next.onReady = function (...args) {
|
|
1072
|
-
try {
|
|
1073
|
-
const start = this.__beLinkClsLoggerPageLoadTs__;
|
|
1074
|
-
if (typeof start === 'number' && sampleHit(options.sampleRate)) {
|
|
1075
|
-
report(reportType, {
|
|
1076
|
-
metric: 'page-render',
|
|
1077
|
-
route: this?.route ? String(this.route) : '',
|
|
1078
|
-
duration: Date.now() - start,
|
|
1079
|
-
unit: 'ms',
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
catch {
|
|
1084
|
-
// ignore
|
|
1085
|
-
}
|
|
1086
|
-
if (typeof rawOnReady === 'function')
|
|
1087
|
-
return rawOnReady.apply(this, args);
|
|
1088
|
-
return undefined;
|
|
1089
|
-
};
|
|
1090
|
-
return rawPage(next);
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1093
1091
|
function installMiniProgramPerformanceMonitor(report, options) {
|
|
1094
1092
|
const g = globalThis;
|
|
1093
|
+
const ctx = g.wx || g.Taro;
|
|
1094
|
+
if (!ctx || typeof ctx.getPerformance !== 'function')
|
|
1095
|
+
return;
|
|
1095
1096
|
if (g.__beLinkClsLoggerMpPerfInstalled__)
|
|
1096
1097
|
return;
|
|
1097
1098
|
g.__beLinkClsLoggerMpPerfInstalled__ = true;
|
|
1098
|
-
// 路由切换耗时(用 API 回调近似)
|
|
1099
|
-
for (const apiName of ['navigateTo', 'redirectTo', 'switchTab', 'reLaunch']) {
|
|
1100
|
-
try {
|
|
1101
|
-
wrapMiniProgramRouteApi(report, options.reportType, apiName, options);
|
|
1102
|
-
}
|
|
1103
|
-
catch {
|
|
1104
|
-
// ignore
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
// 页面渲染耗时(onLoad -> onReady)
|
|
1108
|
-
try {
|
|
1109
|
-
installMiniProgramPageRenderMonitor(report, options.reportType, options);
|
|
1110
|
-
}
|
|
1111
|
-
catch {
|
|
1112
|
-
// ignore
|
|
1113
|
-
}
|
|
1114
|
-
// wx.getPerformance()(若可用,尝试读取已有 entries)
|
|
1115
1099
|
try {
|
|
1116
|
-
const
|
|
1117
|
-
if (
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
: typeof perf.getEntriesByType === 'function'
|
|
1128
|
-
? perf.getEntriesByType('navigation')
|
|
1129
|
-
: [];
|
|
1100
|
+
const perf = ctx.getPerformance();
|
|
1101
|
+
if (!perf || typeof perf.createObserver !== 'function')
|
|
1102
|
+
return;
|
|
1103
|
+
const observer = perf.createObserver((entryList) => {
|
|
1104
|
+
try {
|
|
1105
|
+
const entries = entryList.getEntries();
|
|
1106
|
+
for (const entry of entries) {
|
|
1107
|
+
if (!sampleHit(options.sampleRate))
|
|
1108
|
+
continue;
|
|
1109
|
+
// Page Render: firstRender
|
|
1110
|
+
if (entry.entryType === 'render' && entry.name === 'firstRender') {
|
|
1130
1111
|
report(options.reportType, {
|
|
1131
|
-
metric: '
|
|
1132
|
-
|
|
1112
|
+
metric: 'page-render',
|
|
1113
|
+
duration: entry.duration,
|
|
1114
|
+
pagePath: entry.path || '',
|
|
1115
|
+
unit: 'ms',
|
|
1133
1116
|
});
|
|
1134
1117
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1118
|
+
// Route Switch: route
|
|
1119
|
+
else if (entry.entryType === 'navigation' && entry.name === 'route') {
|
|
1120
|
+
report(options.reportType, {
|
|
1121
|
+
metric: 'route',
|
|
1122
|
+
duration: entry.duration,
|
|
1123
|
+
pagePath: entry.path || '',
|
|
1124
|
+
unit: 'ms',
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
// App Launch: appLaunch (Cold)
|
|
1128
|
+
else if (entry.entryType === 'navigation' && entry.name === 'appLaunch') {
|
|
1129
|
+
report(options.reportType, {
|
|
1130
|
+
metric: 'app-launch',
|
|
1131
|
+
duration: entry.duration,
|
|
1132
|
+
launchType: 'cold',
|
|
1133
|
+
unit: 'ms',
|
|
1134
|
+
});
|
|
1137
1135
|
}
|
|
1138
|
-
}
|
|
1136
|
+
}
|
|
1139
1137
|
}
|
|
1140
|
-
|
|
1138
|
+
catch {
|
|
1139
|
+
// ignore
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
observer.observe({ entryTypes: ['navigation', 'render'] });
|
|
1141
1143
|
}
|
|
1142
1144
|
catch {
|
|
1143
1145
|
// ignore
|
|
@@ -1461,7 +1463,7 @@ function installBehaviorMonitor(report, envType, options = {}) {
|
|
|
1461
1463
|
const tag = (el.tagName || '').toLowerCase();
|
|
1462
1464
|
const trackId = getAttr(el, clickTrackIdAttr);
|
|
1463
1465
|
// 过滤无效点击:白名单 tag + 没有 trackId
|
|
1464
|
-
if (clickWhiteList.includes(tag)
|
|
1466
|
+
if (clickWhiteList.includes(tag) || !trackId)
|
|
1465
1467
|
return;
|
|
1466
1468
|
void uvStatePromise.then(({ uvId, meta }) => {
|
|
1467
1469
|
if (destroyed)
|
|
@@ -1502,8 +1504,12 @@ function installBehaviorMonitor(report, envType, options = {}) {
|
|
|
1502
1504
|
g.Page = function patchedPage(conf) {
|
|
1503
1505
|
const originalOnShow = conf?.onShow;
|
|
1504
1506
|
conf.onShow = function (...args) {
|
|
1505
|
-
if (pvEnabled)
|
|
1506
|
-
|
|
1507
|
+
if (pvEnabled) {
|
|
1508
|
+
const pagePath = getPagePath();
|
|
1509
|
+
if (pagePath?.length > 0) {
|
|
1510
|
+
reportPv(pagePath);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1507
1513
|
return typeof originalOnShow === 'function' ? originalOnShow.apply(this, args) : undefined;
|
|
1508
1514
|
};
|
|
1509
1515
|
// 点击:wrap 页面 methods(bindtap 等会调用到这里的 handler)
|
|
@@ -1839,9 +1845,20 @@ function enterClsSendingGuard() {
|
|
|
1839
1845
|
g.__beLinkClsLoggerSendingCount__ = cur > 0 ? cur - 1 : 0;
|
|
1840
1846
|
};
|
|
1841
1847
|
}
|
|
1842
|
-
|
|
1848
|
+
/**
|
|
1849
|
+
* CLS Logger 核心基类
|
|
1850
|
+
* - 负责所有上报、队列、监控逻辑
|
|
1851
|
+
* - 不包含具体的 SDK 加载实现(由子类负责)
|
|
1852
|
+
* - 这样可以把 web/mini 的 SDK 依赖彻底解耦到子入口
|
|
1853
|
+
*/
|
|
1854
|
+
class ClsLoggerCore {
|
|
1843
1855
|
constructor() {
|
|
1856
|
+
this.sdk = null;
|
|
1857
|
+
this.sdkPromise = null;
|
|
1858
|
+
this.sdkOverride = null;
|
|
1859
|
+
this.sdkLoaderOverride = null;
|
|
1844
1860
|
this.client = null;
|
|
1861
|
+
this.clientPromise = null;
|
|
1845
1862
|
this.topicId = null;
|
|
1846
1863
|
this.endpoint = 'ap-shanghai.cls.tencentcs.com';
|
|
1847
1864
|
this.retryTimes = 10;
|
|
@@ -1872,6 +1889,20 @@ class ClsLogger {
|
|
|
1872
1889
|
this.behaviorMonitorStarted = false;
|
|
1873
1890
|
this.behaviorMonitorCleanup = null;
|
|
1874
1891
|
}
|
|
1892
|
+
/**
|
|
1893
|
+
* 子类可按需重写(默认检测 wx)
|
|
1894
|
+
*/
|
|
1895
|
+
detectEnvType() {
|
|
1896
|
+
const g = globalThis;
|
|
1897
|
+
// 微信、支付宝、字节跳动、UniApp 等小程序环境通常都有特定全局变量
|
|
1898
|
+
if ((g.wx && typeof g.wx.getSystemInfoSync === 'function') ||
|
|
1899
|
+
(g.my && typeof g.my.getSystemInfoSync === 'function') ||
|
|
1900
|
+
(g.tt && typeof g.tt.getSystemInfoSync === 'function') ||
|
|
1901
|
+
(g.uni && typeof g.uni.getSystemInfoSync === 'function')) {
|
|
1902
|
+
return 'miniprogram';
|
|
1903
|
+
}
|
|
1904
|
+
return 'browser';
|
|
1905
|
+
}
|
|
1875
1906
|
init(options) {
|
|
1876
1907
|
this.initTs = Date.now();
|
|
1877
1908
|
const topicId = options?.tencentCloud?.topicID ?? options?.topic_id ?? options?.topicID ?? this.topicId ?? null;
|
|
@@ -1883,15 +1914,33 @@ class ClsLogger {
|
|
|
1883
1914
|
console.warn('ClsLogger.init 没有传 topicID/topic_id');
|
|
1884
1915
|
return;
|
|
1885
1916
|
}
|
|
1917
|
+
const nextEnvType = options.envType ?? this.detectEnvType();
|
|
1918
|
+
// envType/endpoint/retryTimes 变化时:重置 client(以及可能的 sdk)
|
|
1919
|
+
const envChanged = nextEnvType !== this.envType;
|
|
1920
|
+
const endpointChanged = endpoint !== this.endpoint;
|
|
1921
|
+
const retryChanged = retryTimes !== this.retryTimes;
|
|
1922
|
+
if (envChanged || endpointChanged || retryChanged) {
|
|
1923
|
+
this.client = null;
|
|
1924
|
+
this.clientPromise = null;
|
|
1925
|
+
}
|
|
1926
|
+
if (envChanged) {
|
|
1927
|
+
this.sdk = null;
|
|
1928
|
+
this.sdkPromise = null;
|
|
1929
|
+
}
|
|
1886
1930
|
this.topicId = topicId;
|
|
1887
1931
|
this.endpoint = endpoint;
|
|
1888
1932
|
this.retryTimes = retryTimes;
|
|
1889
1933
|
this.source = source;
|
|
1934
|
+
this.userId = options.userId ?? this.userId;
|
|
1935
|
+
this.userName = options.userName ?? this.userName;
|
|
1890
1936
|
this.projectId = options.projectId ?? this.projectId;
|
|
1891
1937
|
this.projectName = options.projectName ?? this.projectName;
|
|
1892
1938
|
this.appId = options.appId ?? this.appId;
|
|
1893
1939
|
this.appVersion = options.appVersion ?? this.appVersion;
|
|
1894
|
-
this.envType =
|
|
1940
|
+
this.envType = nextEnvType;
|
|
1941
|
+
// 可选:外部注入 SDK(优先级:sdkLoader > sdk)
|
|
1942
|
+
this.sdkLoaderOverride = options.sdkLoader ?? this.sdkLoaderOverride;
|
|
1943
|
+
this.sdkOverride = options.sdk ?? this.sdkOverride;
|
|
1895
1944
|
this.userGenerateBaseFields = options.generateBaseFields ?? this.userGenerateBaseFields;
|
|
1896
1945
|
this.autoGenerateBaseFields = createAutoDeviceInfoBaseFields(this.envType, options.deviceInfo);
|
|
1897
1946
|
this.storageKey = options.storageKey ?? this.storageKey;
|
|
@@ -1901,8 +1950,10 @@ class ClsLogger {
|
|
|
1901
1950
|
this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
|
|
1902
1951
|
this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
|
|
1903
1952
|
this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
|
|
1904
|
-
//
|
|
1905
|
-
this.getInstance()
|
|
1953
|
+
// 预热(避免首条日志触发 import/初始化开销)
|
|
1954
|
+
void this.getInstance().catch(() => {
|
|
1955
|
+
// ignore
|
|
1956
|
+
});
|
|
1906
1957
|
// 启动时尝试发送失败缓存
|
|
1907
1958
|
this.flushFailed();
|
|
1908
1959
|
// 初始化后立即启动请求监听
|
|
@@ -1927,7 +1978,7 @@ class ClsLogger {
|
|
|
1927
1978
|
try {
|
|
1928
1979
|
const userRaw = this.userGenerateBaseFields ? this.userGenerateBaseFields() : undefined;
|
|
1929
1980
|
if (userRaw && isPlainObject(userRaw))
|
|
1930
|
-
user = normalizeFlatFields(userRaw, 'generateBaseFields');
|
|
1981
|
+
user = normalizeFlatFields({ ...userRaw }, 'generateBaseFields');
|
|
1931
1982
|
}
|
|
1932
1983
|
catch {
|
|
1933
1984
|
user = undefined;
|
|
@@ -2006,20 +2057,29 @@ class ClsLogger {
|
|
|
2006
2057
|
this.behaviorMonitorCleanup = null;
|
|
2007
2058
|
this.behaviorMonitorStarted = false;
|
|
2008
2059
|
}
|
|
2009
|
-
|
|
2060
|
+
/**
|
|
2061
|
+
* 获取 CLS client(按环境懒加载 SDK)
|
|
2062
|
+
*/
|
|
2063
|
+
async getInstance() {
|
|
2010
2064
|
if (this.client)
|
|
2011
2065
|
return this.client;
|
|
2012
|
-
this.
|
|
2013
|
-
|
|
2014
|
-
|
|
2066
|
+
if (this.clientPromise)
|
|
2067
|
+
return this.clientPromise;
|
|
2068
|
+
this.clientPromise = this.loadSdk()
|
|
2069
|
+
.then(({ AsyncClient }) => {
|
|
2070
|
+
const client = new AsyncClient({
|
|
2071
|
+
endpoint: this.endpoint,
|
|
2072
|
+
retry_times: this.retryTimes,
|
|
2073
|
+
});
|
|
2074
|
+
this.client = client;
|
|
2075
|
+
return client;
|
|
2076
|
+
})
|
|
2077
|
+
.catch((err) => {
|
|
2078
|
+
// 失败后允许下次重试
|
|
2079
|
+
this.clientPromise = null;
|
|
2080
|
+
throw err;
|
|
2015
2081
|
});
|
|
2016
|
-
return this.
|
|
2017
|
-
}
|
|
2018
|
-
detectEnvType() {
|
|
2019
|
-
const wxAny = globalThis.wx;
|
|
2020
|
-
if (wxAny && typeof wxAny.getSystemInfoSync === 'function')
|
|
2021
|
-
return 'miniprogram';
|
|
2022
|
-
return 'browser';
|
|
2082
|
+
return this.clientPromise;
|
|
2023
2083
|
}
|
|
2024
2084
|
/**
|
|
2025
2085
|
* 直接上报:埋点入参必须是一维(扁平)Object
|
|
@@ -2045,22 +2105,33 @@ class ClsLogger {
|
|
|
2045
2105
|
appVersion: this.appVersion || undefined,
|
|
2046
2106
|
...normalizedFields,
|
|
2047
2107
|
});
|
|
2048
|
-
|
|
2049
|
-
|
|
2108
|
+
// 同步 API:内部异步发送,避免把网络异常冒泡到业务(尤其小程序)
|
|
2109
|
+
void this.putAsync(finalFields).catch(() => {
|
|
2110
|
+
// ignore
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
async putAsync(finalFields) {
|
|
2114
|
+
if (!this.topicId)
|
|
2115
|
+
return;
|
|
2116
|
+
const sdk = await this.loadSdk();
|
|
2117
|
+
const client = await this.getInstance();
|
|
2118
|
+
const logGroup = new sdk.LogGroup('127.0.0.1');
|
|
2050
2119
|
logGroup.setSource(this.source);
|
|
2051
|
-
const log = new
|
|
2120
|
+
const log = new sdk.Log(Date.now());
|
|
2052
2121
|
for (const key of Object.keys(finalFields)) {
|
|
2053
2122
|
log.addContent(key, stringifyLogValue(finalFields[key]));
|
|
2054
2123
|
}
|
|
2055
2124
|
logGroup.addLog(log);
|
|
2056
|
-
const request = new
|
|
2125
|
+
const request = new sdk.PutLogsRequest(this.topicId, logGroup);
|
|
2057
2126
|
const exit = enterClsSendingGuard();
|
|
2127
|
+
let p;
|
|
2058
2128
|
try {
|
|
2059
|
-
client.PutLogs(request);
|
|
2129
|
+
p = client.PutLogs(request);
|
|
2060
2130
|
}
|
|
2061
2131
|
finally {
|
|
2062
2132
|
exit();
|
|
2063
2133
|
}
|
|
2134
|
+
await p;
|
|
2064
2135
|
}
|
|
2065
2136
|
/**
|
|
2066
2137
|
* 直接上报:把 data 序列化后放入指定 key(默认 “日志内容”)
|
|
@@ -2119,11 +2190,19 @@ class ClsLogger {
|
|
|
2119
2190
|
console.warn('ClsLogger.putBatch:未初始化 topic_id');
|
|
2120
2191
|
return;
|
|
2121
2192
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2193
|
+
void this.putBatchAsync(queue).catch(() => {
|
|
2194
|
+
// ignore
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
async putBatchAsync(queue) {
|
|
2198
|
+
if (!this.topicId)
|
|
2199
|
+
return;
|
|
2200
|
+
const sdk = await this.loadSdk();
|
|
2201
|
+
const client = await this.getInstance();
|
|
2202
|
+
const logGroup = new sdk.LogGroup('127.0.0.1');
|
|
2124
2203
|
logGroup.setSource(this.source);
|
|
2125
2204
|
for (const item of queue) {
|
|
2126
|
-
const log = new
|
|
2205
|
+
const log = new sdk.Log(item.time);
|
|
2127
2206
|
const data = item.data ?? {};
|
|
2128
2207
|
for (const key of Object.keys(data)) {
|
|
2129
2208
|
log.addContent(key, stringifyLogValue(data[key]));
|
|
@@ -2132,14 +2211,16 @@ class ClsLogger {
|
|
|
2132
2211
|
}
|
|
2133
2212
|
if (logGroup.getLogs().length === 0)
|
|
2134
2213
|
return;
|
|
2135
|
-
const request = new
|
|
2214
|
+
const request = new sdk.PutLogsRequest(this.topicId, logGroup);
|
|
2136
2215
|
const exit = enterClsSendingGuard();
|
|
2216
|
+
let p;
|
|
2137
2217
|
try {
|
|
2138
|
-
client.PutLogs(request);
|
|
2218
|
+
p = client.PutLogs(request);
|
|
2139
2219
|
}
|
|
2140
2220
|
finally {
|
|
2141
2221
|
exit();
|
|
2142
2222
|
}
|
|
2223
|
+
await p;
|
|
2143
2224
|
}
|
|
2144
2225
|
/**
|
|
2145
2226
|
* 参考《一、概述》:统一上报入口(内存队列 + 批量发送)
|
|
@@ -2252,12 +2333,13 @@ class ClsLogger {
|
|
|
2252
2333
|
async sendReportLogs(logs) {
|
|
2253
2334
|
if (!this.topicId)
|
|
2254
2335
|
return;
|
|
2255
|
-
const
|
|
2256
|
-
const
|
|
2336
|
+
const sdk = await this.loadSdk();
|
|
2337
|
+
const client = await this.getInstance();
|
|
2338
|
+
const logGroup = new sdk.LogGroup('127.0.0.1');
|
|
2257
2339
|
logGroup.setSource(this.source);
|
|
2258
2340
|
for (const item of logs) {
|
|
2259
2341
|
const fields = this.buildReportFields(item);
|
|
2260
|
-
const log = new
|
|
2342
|
+
const log = new sdk.Log(fields.timestamp);
|
|
2261
2343
|
for (const key of Object.keys(fields)) {
|
|
2262
2344
|
if (key === 'timestamp')
|
|
2263
2345
|
continue;
|
|
@@ -2265,7 +2347,7 @@ class ClsLogger {
|
|
|
2265
2347
|
}
|
|
2266
2348
|
logGroup.addLog(log);
|
|
2267
2349
|
}
|
|
2268
|
-
const request = new
|
|
2350
|
+
const request = new sdk.PutLogsRequest(this.topicId, logGroup);
|
|
2269
2351
|
// 只在“发起网络请求”的同步阶段打标记,避免 requestMonitor 监控 CLS 上报请求导致递归
|
|
2270
2352
|
const exit = enterClsSendingGuard();
|
|
2271
2353
|
let p;
|
|
@@ -2341,6 +2423,162 @@ class ClsLogger {
|
|
|
2341
2423
|
}
|
|
2342
2424
|
}
|
|
2343
2425
|
|
|
2426
|
+
function readGlobal(key) {
|
|
2427
|
+
try {
|
|
2428
|
+
const g = globalThis;
|
|
2429
|
+
return g[key] ?? null;
|
|
2430
|
+
}
|
|
2431
|
+
catch {
|
|
2432
|
+
return null;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
function tryRequire(moduleName) {
|
|
2436
|
+
try {
|
|
2437
|
+
// 说明:
|
|
2438
|
+
// - ESM 构建(exports.import/module)里通常不存在模块作用域的 require
|
|
2439
|
+
// - 一些小程序运行时/构建链路会把 require 挂到 globalThis 上
|
|
2440
|
+
// 因此这里同时探测“模块作用域 require”与 “globalThis.require”
|
|
2441
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
2442
|
+
const localReq = (typeof require === 'function' ? require : null);
|
|
2443
|
+
const globalReq = readGlobal('require');
|
|
2444
|
+
const candidates = [localReq, globalReq].filter((fn) => typeof fn === 'function');
|
|
2445
|
+
for (const fn of candidates) {
|
|
2446
|
+
try {
|
|
2447
|
+
return fn(moduleName);
|
|
2448
|
+
}
|
|
2449
|
+
catch {
|
|
2450
|
+
// continue
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
return null;
|
|
2454
|
+
}
|
|
2455
|
+
catch {
|
|
2456
|
+
return null;
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
/**
|
|
2461
|
+
* 兼容版 ClsLogger(默认入口)
|
|
2462
|
+
* - 保留了自动识别环境 + 动态 import 的逻辑
|
|
2463
|
+
* - 如果业务想瘦身,建议改用 @be-link/cls-logger/web 或 /mini
|
|
2464
|
+
*/
|
|
2465
|
+
class ClsLogger extends ClsLoggerCore {
|
|
2466
|
+
async loadSdk() {
|
|
2467
|
+
if (this.sdk)
|
|
2468
|
+
return this.sdk;
|
|
2469
|
+
if (this.sdkPromise)
|
|
2470
|
+
return this.sdkPromise;
|
|
2471
|
+
const normalizeSdk = (m) => {
|
|
2472
|
+
const mod = (m?.default && m.default.AsyncClient ? m.default : m);
|
|
2473
|
+
if (mod?.AsyncClient && mod?.Log && mod?.LogGroup && mod?.PutLogsRequest) {
|
|
2474
|
+
return {
|
|
2475
|
+
AsyncClient: mod.AsyncClient,
|
|
2476
|
+
Log: mod.Log,
|
|
2477
|
+
LogGroup: mod.LogGroup,
|
|
2478
|
+
PutLogsRequest: mod.PutLogsRequest,
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
return null;
|
|
2482
|
+
};
|
|
2483
|
+
// 1) 外部注入的 loader(最高优先级)
|
|
2484
|
+
if (this.sdkLoaderOverride) {
|
|
2485
|
+
try {
|
|
2486
|
+
const loaded = await this.sdkLoaderOverride();
|
|
2487
|
+
const sdk = normalizeSdk(loaded);
|
|
2488
|
+
if (sdk) {
|
|
2489
|
+
this.sdk = sdk;
|
|
2490
|
+
return sdk;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
catch {
|
|
2494
|
+
// ignore and fallback
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
// 2) 外部直接注入的 sdk
|
|
2498
|
+
if (this.sdkOverride) {
|
|
2499
|
+
const sdk = normalizeSdk(this.sdkOverride);
|
|
2500
|
+
if (sdk) {
|
|
2501
|
+
this.sdk = sdk;
|
|
2502
|
+
return sdk;
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
const isMini = this.envType === 'miniprogram';
|
|
2506
|
+
// 3) 尝试 require / 全局变量
|
|
2507
|
+
// Mini Program: 尝试直接 require
|
|
2508
|
+
if (isMini) {
|
|
2509
|
+
// 显式使用字面量 require,帮助打包工具识别依赖(尤其是 CJS 构建模式下)
|
|
2510
|
+
try {
|
|
2511
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
2512
|
+
const req = typeof require === 'function' ? require('tencentcloud-cls-sdk-js-mini') : null;
|
|
2513
|
+
const reqSdk = normalizeSdk(req);
|
|
2514
|
+
if (reqSdk) {
|
|
2515
|
+
this.sdk = reqSdk;
|
|
2516
|
+
return reqSdk;
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
catch {
|
|
2520
|
+
// ignore
|
|
2521
|
+
}
|
|
2522
|
+
// 尝试使用 tryRequire(作为补充)
|
|
2523
|
+
const reqMod = tryRequire('tencentcloud-cls-sdk-js-mini');
|
|
2524
|
+
const reqSdk = normalizeSdk(reqMod);
|
|
2525
|
+
if (reqSdk) {
|
|
2526
|
+
this.sdk = reqSdk;
|
|
2527
|
+
return reqSdk;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
else {
|
|
2531
|
+
// Web: 优先读全局变量 (UMD)
|
|
2532
|
+
const g = readGlobal('tencentcloudClsSdkJsWeb');
|
|
2533
|
+
const sdk = normalizeSdk(g);
|
|
2534
|
+
if (sdk) {
|
|
2535
|
+
this.sdk = sdk;
|
|
2536
|
+
return sdk;
|
|
2537
|
+
}
|
|
2538
|
+
// Web: 尝试 require
|
|
2539
|
+
const reqMod = tryRequire('tencentcloud-cls-sdk-js-web');
|
|
2540
|
+
const reqSdk = normalizeSdk(reqMod);
|
|
2541
|
+
if (reqSdk) {
|
|
2542
|
+
this.sdk = reqSdk;
|
|
2543
|
+
return reqSdk;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
// 4) 动态 import
|
|
2547
|
+
// 使用字符串字面量,确保 Bundler 能正确识别并进行 Code Splitting
|
|
2548
|
+
if (isMini) {
|
|
2549
|
+
this.sdkPromise = import('tencentcloud-cls-sdk-js-mini')
|
|
2550
|
+
.then((m) => {
|
|
2551
|
+
const sdk = normalizeSdk(m);
|
|
2552
|
+
if (!sdk)
|
|
2553
|
+
throw new Error(`ClsLogger.loadSdk: invalid sdk module for mini`);
|
|
2554
|
+
this.sdk = sdk;
|
|
2555
|
+
return sdk;
|
|
2556
|
+
})
|
|
2557
|
+
.catch((err) => {
|
|
2558
|
+
this.sdkPromise = null;
|
|
2559
|
+
console.error('[ClsLogger] Failed to load mini sdk:', err);
|
|
2560
|
+
throw err;
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
else {
|
|
2564
|
+
this.sdkPromise = import('tencentcloud-cls-sdk-js-web')
|
|
2565
|
+
.then((m) => {
|
|
2566
|
+
const sdk = normalizeSdk(m);
|
|
2567
|
+
if (!sdk)
|
|
2568
|
+
throw new Error(`ClsLogger.loadSdk: invalid sdk module for web`);
|
|
2569
|
+
this.sdk = sdk;
|
|
2570
|
+
return sdk;
|
|
2571
|
+
})
|
|
2572
|
+
.catch((err) => {
|
|
2573
|
+
this.sdkPromise = null;
|
|
2574
|
+
console.error('[ClsLogger] Failed to load web sdk:', err);
|
|
2575
|
+
throw err;
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
return this.sdkPromise;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2344
2582
|
const clsLogger = new ClsLogger();
|
|
2345
2583
|
|
|
2346
2584
|
exports.ClsLogger = ClsLogger;
|