UNPKG

cos-js-sdk-v5

Version:

JavaScript SDK for [腾讯云对象存储](https://cloud.tencent.com/product/cos)

417 lines (386 loc) 12.8 kB
const pkg = require('../package.json'); let beacon = null; const getBeacon = (Beacon, delay) => { if (!beacon) { // 生成 beacon if (typeof Beacon !== 'function') { throw new Error('Beacon not found'); } beacon = new Beacon({ appkey: '0WEB05PY6MHRGK0U', versionCode: pkg.version, channelID: 'js_sdk', //渠道,选填 openid: 'openid', // 用户id, 选填 unionid: 'unid', //用户unionid , 类似idfv,选填 strictMode: false, //严苛模式开关, 打开严苛模式会主动抛出异常, 上线请务必关闭!!! delay, // 普通事件延迟上报时间(单位毫秒), 默认1000(1秒),选填 sessionDuration: 60 * 1000, // session变更的时间间隔, 一个用户持续30分钟(默认值)没有任何上报则算另一次 session,每变更一次session上报一次启动事件(rqd_applaunched),使用毫秒(ms),最小值30秒,选填 }); } return beacon; }; // 毫秒转秒 const ms2s = function (ms) { if (!ms || ms < 0) return 0; return (ms / 1000).toFixed(3); }; const utils = { // 生成uid 每个链路对应唯一一条uid getUid() { var S4 = function () { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); }; return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4(); }, // 获取网络类型 4g | wifi getNetType() { if (typeof navigator === 'object') { const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; return connection?.type || connection?.effectiveType || 'unknown'; } return 'unknown'; }, // http | https getProtocol() { if (typeof location === 'object') { return location.protocol.replace(/:/, ''); } return 'unknown protocol'; }, // 获取pc端操作系统类型 getOsType() { if (typeof navigator !== 'object') { return 'unknown os'; } var agent = navigator.userAgent.toLowerCase(); var isMac = /macintosh|mac os x/i.test(navigator.userAgent); if (agent.indexOf('win32') >= 0 || agent.indexOf('wow32') >= 0) { return 'win32'; } if (agent.indexOf('win64') >= 0 || agent.indexOf('wow64') >= 0) { return 'win64'; } if (isMac) { return 'mac'; } return 'unknown os'; }, isMobile() { const exp = /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i; if (typeof navigator === 'object' && navigator.userAgent.match(exp)) { return true; // 移动端 } return false; // PC端 }, isAndroid() { const exp = /(Android|Adr|Linux)/i; if (typeof navigator === 'object' && navigator.userAgent.match(exp)) { return true; } return false; }, isIOS() { const exp = /(iPhone|iPod|iPad|iOS)/i; if (typeof navigator === 'object' && navigator.userAgent.match(exp)) { return true; } return false; }, isOtherMobile() { return isMobile && !isAndroid && !isIOS; }, getUA() { if (typeof navigator !== 'object') { return 'unknown device'; } const explorer = navigator.userAgent; return explorer; }, }; const isMobile = utils.isMobile(); const mobileOsType = utils.isAndroid() ? 'android' : utils.isIOS ? 'ios' : 'other_mobile'; const pcOsType = utils.getOsType(); const devicePlatform = isMobile ? mobileOsType : pcOsType; const ua = utils.getUA(); const protocol = utils.getProtocol(); const transApiName = (api) => { if (['putObject', 'sliceUploadFile', 'uploadFile', 'uploadFiles'].includes(api)) { return 'UploadTask'; } else if (api === 'getObject') { return 'DownloadTask'; } else if (['putObjectCopy', 'sliceCopyFile'].includes(api)) { return 'CopyTask'; } return api; }; // 上报参数驼峰改下划线 function camel2underline(key) { return key.replace(/([A-Z])/g, '_$1').toLowerCase(); } function formatParams(params) { const formattedParams = {}; const successKeys = [ 'sdkVersionName', 'sdkVersionCode', 'osName', 'networkType', 'requestName', 'requestResult', 'bucket', 'region', 'appid', 'accelerate', 'url', 'host', 'requestPath', 'userAgent', 'networkProtocol', 'httpMethod', 'httpSize', 'httpSpeed', 'httpTookTime', 'httpMd5', 'httpSign', 'httpFullTime', 'httpDomain', 'partNumber', 'httpRetryTimes', 'customId', 'traceId', 'realApi', ]; const failureKeys = [ ...successKeys, 'errorNode', 'errorCode', 'errorName', 'errorMessage', 'errorRequestId', 'errorHttpCode', 'errorServiceName', 'errorType', 'fullError', ]; // 需要上报的参数字段 const reporterKeys = params.requestResult === 'Success' ? successKeys : failureKeys; for (let key in params) { if (!reporterKeys.includes(key)) continue; const formattedKey = camel2underline(key); formattedParams[formattedKey] = params[key]; } formattedParams['request_name'] = params.realApi ? transApiName(params.realApi) : params.requestName; return formattedParams; } // 链路追踪器 class Tracker { constructor(opt) { const { parent, traceId, bucket, region, apiName, realApi, httpMethod, fileKey, fileSize, accelerate, customId, delay, deepTracker, Beacon, clsReporter, } = opt; const appid = (bucket && bucket.substr(bucket.lastIndexOf('-') + 1)) || ''; this.parent = parent; this.deepTracker = deepTracker; this.delay = delay; if (clsReporter && !this.clsReporter) { this.clsReporter = clsReporter; } // 上报用到的字段 this.params = { // 通用字段 sdkVersionName: 'cos-js-sdk-v5', sdkVersionCode: pkg.version, osName: devicePlatform, networkType: '', requestName: apiName || '', requestResult: '', // sdk api调用结果Success、Failure realApi, bucket, region, accelerate, httpMethod, url: '', // 请求url host: '', httpDomain: '', requestPath: fileKey || '', userAgent: ua, networkProtocol: protocol, errorType: '', errorCode: '', errorName: '', errorMessage: '', errorRequestId: '', errorHttpCode: 0, errorServiceName: '', errorNode: '', httpTookTime: 0, // http整体耗时 httpSize: fileSize || 0, // 主要是文件大小,大小 B httpMd5: 0, // MD5耗时 httpSign: 0, // 计算签名耗时 httpFullTime: 0, // 任务整体耗时(包括md5、签名等) httpSpeed: 0, // 主要关注上传速度,KB/s md5StartTime: 0, // md5计算开始时间 md5EndTime: 0, // md5计算结束时间 signStartTime: 0, // 计算签名开始时间 signEndTime: 0, // 计算签名结束时间 httpStartTime: 0, // 发起网络请求开始时间 httpEndTime: 0, // 网路请求结束时间 startTime: new Date().getTime(), // sdk api调用起始时间,不是纯网络耗时 endTime: 0, // sdk api调用结束时间,不是纯网络耗时 // js补充字段 traceId: traceId || utils.getUid(), // 每条上报唯一标识 appid, partNumber: 0, // 分块上传编号 httpRetryTimes: 0, // sdk内部发起的请求重试 customId: customId || '', // 业务id partTime: 0, }; if (Beacon) { this.beacon = getBeacon(Beacon, delay); } } // 格式化sdk回调 formatResult(err, data) { const now = new Date().getTime(); const networkType = utils.getNetType(); const errorCode = err ? err?.code || err?.error?.code || err?.error?.Code : ''; const errorMessage = err ? err?.message || err?.error?.message || err?.error?.Message : ''; const errorName = errorMessage; const errorServiceName = err ? err?.resource || err?.error?.resource || err?.error?.Resource : ''; const errorHttpCode = err ? err?.statusCode : data.statusCode; const requestId = err ? err?.headers && err?.headers['x-cos-request-id'] : data?.headers && data?.headers['x-cos-request-id']; const errorType = err ? (requestId ? 'Server' : 'Client') : ''; if (this.params.requestName === 'getObject') { this.params.httpSize = data ? data.headers && data.headers['content-length'] : 0; } // 上报 sliceUploadFile || uploadFile || uploadFiles 命中分块上传时 const isSliceUploadFile = this.params.realApi === 'sliceUploadFile'; const isSliceCopyFile = this.params.realApi === 'sliceCopyFile'; if (isSliceUploadFile || isSliceCopyFile) { const speed = this.params.httpSize / 1024 / this.params.partTime; Object.assign(this.params, { httpSpeed: speed < 0 ? 0 : speed.toFixed(3) }); } else { const httpFullTime = now - this.params.startTime; const httpTookTime = this.params.httpEndTime - this.params.httpStartTime; const speed = this.params.httpSize / 1024 / (httpTookTime / 1000); const httpMd5 = this.params.md5EndTime - this.params.md5StartTime; const httpSign = this.params.signEndTime - this.params.signStartTime; if (this.parent) { this.parent.addParamValue('httpTookTime', ms2s(httpTookTime)); this.parent.addParamValue('httpFullTime', ms2s(httpFullTime)); this.parent.addParamValue('httpMd5', ms2s(httpMd5)); this.parent.addParamValue('httpSign', ms2s(httpSign)); if (['multipartUpload', 'uploadPartCopy', 'putObjectCopy'].includes(this.params.requestName)) { // 只有小分块上传|复制才累计纯请求耗时,计算速度时用到 this.parent.addParamValue('partTime', ms2s(httpTookTime)); } } Object.assign(this.params, { httpFullTime: ms2s(httpFullTime), httpMd5: ms2s(httpMd5), httpSign: ms2s(httpSign), httpTookTime: ms2s(httpTookTime), httpSpeed: speed < 0 ? 0 : speed.toFixed(3), }); } Object.assign(this.params, { networkType, requestResult: err ? 'Failure' : 'Success', errorType, errorCode, errorHttpCode, errorName, errorMessage, errorServiceName, errorRequestId: requestId, }); if (err && (!errorCode || !errorMessage)) { // 暂存全量err一段时间 观察是否所有err格式都可被解析 this.params.fullError = err ? JSON.stringify(err) : ''; } if (this.params.url) { try { const execRes = /^http(s)?:\/\/(.*?)\//.exec(this.params.url); this.params.host = execRes[2]; } catch (e) { this.params.host = this.params.url; } this.params.httpDomain = this.params.host; } } // 上报 report(err, data) { if (!this.beacon && !this.clsReporter) return; this.formatResult(err, data); const formattedParams = formatParams(this.params); if (this.beacon) { this.sendEventsToBeacon(formattedParams); } if (this.clsReporter) { this.sendEventsToCLS(formattedParams); } } // 设置当前链路的参数 setParams(params) { Object.assign(this.params, params); } addParamValue(key, value) { this.params[key] = (+this.params[key] + +value).toFixed(3); } // 上报灯塔 sendEventsToBeacon(formattedParams) { // DeepTracker模式下才会上报分块上传内部细节 const isSliceUploadFile = this.params.requestName === 'sliceUploadFile' || this.params.realApi === 'sliceUploadFile'; if (isSliceUploadFile && !this.deepTracker) { return; } const eventCode = 'qcloud_track_cos_sdk'; if (this.delay === 0) { // 实时上报 this.beacon && this.beacon.onDirectUserAction(eventCode, formattedParams); } else { // 周期性上报 this.beacon && this.beacon.onUserAction(eventCode, formattedParams); } } // 上报 cls sendEventsToCLS(formattedParams) { // 是否实时上报 const immediate = !!(this.delay === 0); this.clsReporter.log(formattedParams, immediate); } // 生成子实例,与父所属一个链路,可用于分块上传内部流程上报单个分块操作 generateSubTracker(subParams) { Object.assign(subParams, { parent: this, deepTracker: this.deepTracker, traceId: this.params.traceId, bucket: this.params.bucket, region: this.params.region, accelerate: this.params.accelerate, fileKey: this.params.requestPath, customId: this.params.customId, delay: this.delay, clsReporter: this.clsReporter, }); return new Tracker(subParams); } } module.exports = Tracker;