@lx-frontend/multi-track
Version:
sls 日志上传功能,当前支持小程序、web
338 lines • 13.9 kB
JavaScript
"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