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