UNPKG

@lx-frontend/multi-track

Version:

sls 日志上传功能,当前支持小程序、web

338 lines 13.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Track = void 0; var tslib_1 = require("tslib"); /** * 日志上报抽象类 * 业务端需要重新实现 format 方法,将日志格式化成 Sls 需要的格式 */ var Track = /** @class */ (function () { function Track(trackConfig) { var _a, _b, _c, _d, _e, _f; this.logQueue = []; // 日志队列 this.prepareLogQueue = []; // 未经格式化的日志队列 this.processingIds = new Set(); // 正在处理的日志ID集合 this.isUploading = false; // 标记是否正在上传 this.debounceTimer = null; this.flushIntervalTimer = null; this.nextId = 1; // 用于生成唯一ID var config = trackConfig.trackOptions, slsOptions = trackConfig.slsOptions; this.config = { maxQueueSize: (_a = config.maxQueueSize) !== null && _a !== void 0 ? _a : 30, debounceTime: (_b = config.debounceTime) !== null && _b !== void 0 ? _b : 5000, persistLogs: (_c = config.persistLogs) !== null && _c !== void 0 ? _c : true, flushInterval: (_d = config.flushInterval) !== null && _d !== void 0 ? _d : 0, localStorageKey: (_e = config.localStorageKey) !== null && _e !== void 0 ? _e : 'sls_pending_logs', uploadEnabled: (_f = config.uploadEnabled) !== null && _f !== void 0 ? _f : false, }; this.slsOptions = slsOptions; this.initializeFlushInterval(); this.restorePersistedLogs(); } /** * 生成唯一ID,通过时间戳+自增ID。很高效的唯一ID方式。 */ Track.prototype.generateId = function () { return "".concat(Date.now(), "_").concat(this.nextId++); }; /** * 当前不同平台处理 request 不同,暴露给子类处理 * 生成 sls 请求 url * eq: https://biz-frontend-upload.cn-shanghai.log.aliyuncs.com/logstores/ucma-frontend/track?APIVersion=0.6.0 * project + host + '/logstores' + logstore + '/track?APIVersion=0.6.0' */ Track.prototype.generateSlsUrl = function () { var _a = this.slsOptions, host = _a.host, logstore = _a.logstore, project = _a.project; return "https://".concat(project, ".").concat(host, "/logstores/").concat(logstore, "/track?APIVersion=0.6.0"); }; /** * 注意 sls 的 value 只能是字符串。单单 JSON.stringify 遇到 number 这些会报错 PostBodyInvalid * 将对象转换为字符串 */ Track.prototype.transString = function (obj) { var newObj = {}; for (var i in obj) { if (typeof obj[i] == 'object') { newObj[i] = JSON.stringify(obj[i]); } else { newObj[i] = String(obj[i]); } } return newObj; }; /** * 获取 sls 上报所需格式,注意 sls 的 value 只能是字符串 * 数据需要二次 JSON.stringify * @link https://help.aliyun.com/zh/sls/developer-reference/api-sls-2020-12-30-putwebtracking?spm=a2c4g.11186623.0.0.39c93b79Z9iOcy * @param logData 日志数组 */ Track.prototype.getRequestInfo = function (logData) { var _this = this; var __logs__ = logData.map(function (log) { return _this.transString(log); }); var assembledData = { __logs__: __logs__ }; if (this.slsOptions.tags) { assembledData.__tags__ = this.slsOptions.tags; } if (this.slsOptions.topic) { assembledData.__topic__ = this.slsOptions.topic; } if (this.slsOptions.source) { assembledData.__source__ = this.slsOptions.source; } // 需要二次 JSON.stringify var stringifyResult = JSON.stringify(assembledData); return { payload: stringifyResult, headers: { 'x-log-apiversion': '0.6.0', 'x-log-bodyrawsize': stringifyResult.length.toString(), }, }; }; /** * 设置是否开启日志上报 */ Track.prototype.setUploadEnabled = function (enabled) { var _this = this; this.config.uploadEnabled = enabled; // 如果开启且有日志等待,尝试上报 if (enabled) { this.prepareLogQueue.forEach(function (log) { return _this.add(log); }); this.prepareLogQueue = []; } }; /** * 添加日志到队列 */ Track.prototype.add = function (log) { // 上传任务ID log.id = this.generateId(); if (!this.config.uploadEnabled) { this.prepareLogQueue.push(log); return; } this.logQueue.push(this.format(log)); if (this.logQueue.length >= this.config.maxQueueSize && !this.isUploading) { this.upload(); } else { // 否则使用防抖逻辑 this.debounceUpload(); } }; /** * 使用防抖上报日志 */ Track.prototype.debounceUpload = function () { var _this = this; if (!this.config.uploadEnabled) return; if (this.debounceTimer) { clearTimeout(this.debounceTimer); } this.debounceTimer = setTimeout(function () { _this.upload(); }, this.config.debounceTime); }; /** * 由于日志都是异步参数,可能会出现乱序,所以需要排序 * 按照 eventId 升序排序 * @param arr * @private */ Track.prototype.sortByEventId = function (arr) { return arr.sort(function (a, b) { var _a, _b, _c, _d, _e, _f; var numA = parseInt(((_c = (_b = (_a = a === null || a === void 0 ? void 0 : a.eventId) === null || _a === void 0 ? void 0 : _a.split) === null || _b === void 0 ? void 0 : _b.call(_a, '_')) === null || _c === void 0 ? void 0 : _c[1]) || 0, 10); var numB = parseInt(((_f = (_e = (_d = b === null || b === void 0 ? void 0 : b.eventId) === null || _d === void 0 ? void 0 : _d.split) === null || _e === void 0 ? void 0 : _e.call(_d, '_')) === null || _f === void 0 ? void 0 : _f[1]) || 0, 10); return numA - numB; // 升序排序 }); }; /** * 获取可上传的日志(未在处理中的日志) */ Track.prototype.getUploadLogs = function (maxCount) { var uploadLogs = []; for (var _i = 0, _a = this.logQueue; _i < _a.length; _i++) { var log = _a[_i]; if (log.id && !this.processingIds.has(log.id)) { uploadLogs.push(log); this.processingIds.add(log.id); if (uploadLogs.length >= maxCount) { break; } } } return this.sortByEventId(uploadLogs); }; /** * 从队列中移除指定ID的日志 */ Track.prototype.removeLogsById = function (ids) { var _this = this; // 从处理中的ID集合中移除 ids.forEach(function (id) { return _this.processingIds.delete(id); }); // 从队列中移除这些日志 this.logQueue = this.logQueue.filter(function (log) { return !log.id || !ids.includes(log.id); }); }; /** * 上报日志 */ Track.prototype.upload = function () { return tslib_1.__awaiter(this, void 0, void 0, function () { var logsData, uploadLogIds, _a, payload, headers, error_1; var _this = this; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: // 如果上传被禁用或已经有一个上传进行中,则返回 if (!this.config.uploadEnabled || this.isUploading) { return [2 /*return*/, false]; } logsData = this.getUploadLogs(this.config.maxQueueSize); // 如果没有可上传的日志,则返回 if (logsData.length === 0) { return [2 /*return*/, false]; } // 清除任何挂起的防抖 if (this.debounceTimer) { clearTimeout(this.debounceTimer); this.debounceTimer = null; } // 标记上传状态 this.isUploading = true; uploadLogIds = logsData.map(function (log) { return log.id; }).filter(function (id) { return id !== undefined; }); _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); _a = this.getRequestInfo(logsData), payload = _a.payload, headers = _a.headers; return [4 /*yield*/, this._upload(payload, headers) // 上传成功,从队列中移除这些日志 ]; case 2: _b.sent(); // 上传成功,从队列中移除这些日志 this.removeLogsById(uploadLogIds); // 重置上传状态 this.isUploading = false; return [2 /*return*/, true]; case 3: error_1 = _b.sent(); console.info('Error uploading logs:', error_1.message); // 上传失败,处理失败的日志 if (this.config.persistLogs) { // 存储到 localStorage,但不从队列中移除 this.persistLogs(logsData); } // 释放这些ID,以便下次尝试上传 uploadLogIds.forEach(function (id) { return _this.processingIds.delete(id); }); // 重置上传状态 this.isUploading = false; return [2 /*return*/, false]; case 4: return [2 /*return*/]; } }); }); }; /** * 强制上报日志 */ Track.prototype.flush = function () { return tslib_1.__awaiter(this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) { // 如果当前没有上传进行中,则开始一次新的上传 if (!this.isUploading) { return [2 /*return*/, this.upload()]; } return [2 /*return*/, false]; }); }); }; /** * 定时上报日志 */ Track.prototype.initializeFlushInterval = function () { var _this = this; if (this.config.flushInterval > 0) { this.flushIntervalTimer = setInterval(function () { if (_this.logQueue.length > 0 && !_this.isUploading) { _this.upload(); } }, this.config.flushInterval); } }; /** * 持久化日志到 localStorage */ Track.prototype.persistLogs = function (logs, withLocalStorage) { if (withLocalStorage === void 0) { withLocalStorage = true; } try { var existingLogs = this.getPersistedLogs(); var updatedLogs = withLocalStorage ? tslib_1.__spreadArray(tslib_1.__spreadArray([], existingLogs, true), logs, true) : logs; this._setStorage(this.config.localStorageKey, updatedLogs); } catch (error) { console.error('Error persisting logs to localStorage:', error); } }; /** * 获取本地存储的日志 * @returns */ Track.prototype.getPersistedLogs = function () { var _a; try { return ((_a = this._getStorage) === null || _a === void 0 ? void 0 : _a.call(this, this.config.localStorageKey)) || []; } catch (error) { console.error('Error reading persisted logs:', error); return []; } }; /** * 恢复持久化的日志 */ Track.prototype.restorePersistedLogs = function () { var _this = this; var persistedLogs = this.getPersistedLogs(); if (persistedLogs.length > 0) { // 确保每个恢复的日志都有ID persistedLogs.forEach(function (log) { if (!log.id) { log.id = _this.generateId(); } }); // 添加到队列 this.logQueue = tslib_1.__spreadArray(tslib_1.__spreadArray([], persistedLogs, true), this.logQueue, true); this.persistLogs([], false); // 如果上传启用,尝试上传 if (this.config.uploadEnabled) { this.debounceUpload(); } } }; // 清理资源 Track.prototype.destroy = function () { var _this = this; if (this.debounceTimer) { clearTimeout(this.debounceTimer); } if (this.flushIntervalTimer) { clearInterval(this.flushIntervalTimer); } // 如果有日志且上传被启用,尝试上传 if (this.logQueue.length > 0 && this.config.uploadEnabled && !this.isUploading) { this.upload(); } // 如果无法上传但有持久化选项,则存储到 localStorage // 过滤掉正在处理的日志,以避免重复 var logsToStore = this.logQueue.filter(function (log) { return !log.id || !_this.processingIds.has(log.id); }); this.persistLogs(logsToStore); }; return Track; }()); exports.Track = Track; //# sourceMappingURL=track.js.map