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