/** * @class ErrorResult 错误结果统计模型 */ const BaseMod = require('./base') const Platform = require('./platform') const Channel = require('./channel') const Version = require('./version') const ErrorLog = require('./errorLog') const AppCrashLogs = require('./appCrashLogs') const SessionLog = require('./sessionLog') const { DateTime } = require('../lib') module.exports = class ErrorResult extends BaseMod { constructor() { super() this.tableName = 'error-result' this.platforms = [] this.channels = [] this.versions = [] this.errors = [] } /** * 错误结果统计 * @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计 * @param {Date|Time} date 指定日期或时间戳 * @param {Boolean} reset 是否重置,为ture时会重置该批次数据 */ async stat(type, date, reset) { //前端js错误统计 const resJs = await this.statJs(type, date, reset) //原生应用崩溃错误统计 const resCrash = await this.statCrash(type, date, reset) return { code: 0, msg: 'success', data: { resJs, resCrash } } } /** * 前端js错误结果统计 * @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计 * @param {Date|Time} date 指定日期或时间戳 * @param {Boolean} reset 是否重置,为ture时会重置该批次数据 */ async statJs(type, date, reset) { const allowedType = ['day'] if (!allowedType.includes(type)) { return { code: 1002, msg: 'This type is not allowed' } } this.fillType = type const dateTime = new DateTime() const dateDimension = dateTime.getTimeDimensionByType(type, -1, date) this.startTime = dateDimension.startTime this.endTime = dateDimension.endTime if (this.debug) { console.log('dimension time', this.startTime + '--' + this.endTime) } // 查看当前时间段日志是否已存在,防止重复生成 if (!reset) { const checkRes = await this.getCollection(this.tableName).where({ type: 'js', start_time: this.startTime, end_time: this.endTime }).get() if (checkRes.data.length > 0) { console.log('error log have existed') return { code: 1003, msg: 'This log have existed' } } } else { const delRes = await this.delete(this.tableName, { type: 'js', start_time: this.startTime, end_time: this.endTime }) console.log('delete old data result:', JSON.stringify(delRes)) } // 数据获取 this.errorLog = new ErrorLog() const statRes = await this.aggregate(this.errorLog.tableName, { project: { appid: 1, version: 1, platform: 1, channel: 1, error_hash: 1, create_time: 1 }, match: { create_time: { $gte: this.startTime, $lte: this.endTime } }, group: { _id: { appid: '$appid', version: '$version', platform: '$platform', channel: '$channel', error_hash: '$error_hash' }, error_count: { $sum: 1 } }, sort: { error_count: 1 }, getAll: true }) let res = { code: 0, msg: 'success' } if (this.debug) { console.log('statRes', JSON.stringify(statRes)) } if (statRes.data.length > 0) { this.fillData = [] for (const i in statRes.data) { await this.fillJs(statRes.data[i]) } if (this.fillData.length > 0) { res = await this.batchInsert(this.tableName, this.fillData) } } return res } /** * 前端js错误统计结果数据填充 * @param {Object} data 数据集合 */ async fillJs(data) { // 平台信息 let platformInfo = null if (this.platforms && this.platforms[data._id.platform]) { //暂存下数据,减少读库 platformInfo = this.platforms[data._id.platform] } else { const platform = new Platform() platformInfo = await platform.getPlatformAndCreate(data._id.platform, null) if (!platformInfo || platformInfo.length === 0) { platformInfo._id = '' } this.platforms[data._id.platform] = platformInfo if (this.debug) { console.log('platformInfo', JSON.stringify(platformInfo)) } } // 渠道信息 let channelInfo = null const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel if (this.channels && this.channels[channelKey]) { channelInfo = this.channels[channelKey] } else { const channel = new Channel() channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel) if (!channelInfo || channelInfo.length === 0) { channelInfo._id = '' } this.channels[channelKey] = channelInfo if (this.debug) { console.log('channelInfo', JSON.stringify(channelInfo)) } } // 版本信息 let versionInfo = null const versionKey = data._id.appid + '_' + data._id.platform + '_' + data._id.version if (this.versions && this.versions[versionKey]) { versionInfo = this.versions[versionKey] } else { const version = new Version() versionInfo = await version.getVersionAndCreate(data._id.appid, data._id.platform, data._id.version) if (!versionInfo || versionInfo.length === 0) { versionInfo._id = '' } this.versions[versionKey] = versionInfo if (this.debug) { console.log('versionInfo', JSON.stringify(versionInfo)) } } // 错误信息 let errorInfo = null if (this.errors && this.errors[data._id.error_hash]) { errorInfo = this.errors[data._id.error_hash] } else { const cacheKey = 'uni-stat-errors-' + data._id.error_hash errorInfo = await this.getCache(cacheKey) if (!errorInfo) { errorInfo = await this.getCollection(this.errorLog.tableName).where({ error_hash: data._id.error_hash }).limit(1).get() if (!errorInfo || errorInfo.data.length === 0) { errorInfo.error_msg = '' } else { errorInfo = errorInfo.data[0] await this.setCache(cacheKey, errorInfo) } } this.errors[data._id.error_hash] = errorInfo } // 最近一次报错时间 const matchCondition = data._id Object.assign(matchCondition, { create_time: { $gte: this.startTime, $lte: this.endTime } }) const lastErrorLog = await this.getCollection(this.errorLog.tableName).where(matchCondition).orderBy( 'create_time', 'desc').limit(1).get() let lastErrorTime = '' if (lastErrorLog && lastErrorLog.data.length > 0) { lastErrorTime = lastErrorLog.data[0].create_time } //数据填充 const datetime = new DateTime() const insertParams = { appid: data._id.appid, platform_id: platformInfo._id, channel_id: channelInfo._id, version_id: versionInfo._id, type: 'js', hash: data._id.error_hash, msg: errorInfo.error_msg, count: data.error_count, last_time: lastErrorTime, dimension: this.fillType, stat_date: datetime.getDate('Ymd', this.startTime), start_time: this.startTime, end_time: this.endTime } this.fillData.push(insertParams) return insertParams } /** * 原生应用错误结果统计 * @param {String} type 统计类型 hour:实时统计 day:按天统计,week:按周统计 month:按月统计 * @param {Date|Time} date 指定日期或时间戳 * @param {Boolean} reset 是否重置,为ture时会重置该批次数据 */ async statCrash(type, date, reset) { const allowedType = ['day'] if (!allowedType.includes(type)) { return { code: 1002, msg: 'This type is not allowed' } } this.fillType = type const dateTime = new DateTime() const dateDimension = dateTime.getTimeDimensionByType(type, -1, date) this.startTime = dateDimension.startTime this.endTime = dateDimension.endTime if (this.debug) { console.log('dimension time', this.startTime + '--' + this.endTime) } // 查看当前时间段日志是否已存在,防止重复生成 if (!reset) { const checkRes = await this.getCollection(this.tableName).where({ type: 'crash', start_time: this.startTime, end_time: this.endTime }).get() if (checkRes.data.length > 0) { console.log('error log have existed') return { code: 1003, msg: 'This log have existed' } } } else { const delRes = await this.delete(this.tableName, { type: 'crash', start_time: this.startTime, end_time: this.endTime }) console.log('delete old data result:', JSON.stringify(delRes)) } // 数据获取 this.crashLogs = new AppCrashLogs() const statRes = await this.aggregate(this.crashLogs.tableName, { project: { appid: 1, version: 1, platform: 1, channel: 1, create_time: 1 }, match: { create_time: { $gte: this.startTime, $lte: this.endTime } }, group: { _id: { appid: '$appid', version: '$version', platform: '$platform', channel: '$channel' }, error_count: { $sum: 1 } }, sort: { error_count: 1 }, getAll: true }) let res = { code: 0, msg: 'success' } if (this.debug) { console.log('statRes', JSON.stringify(statRes)) } if (statRes.data.length > 0) { this.fillData = [] for (const i in statRes.data) { await this.fillCrash(statRes.data[i]) } if (this.fillData.length > 0) { res = await this.batchInsert(this.tableName, this.fillData) } } return res } async fillCrash(data) { // 平台信息 let platformInfo = null if (this.platforms && this.platforms[data._id.platform]) { //暂存下数据,减少读库 platformInfo = this.platforms[data._id.platform] } else { const platform = new Platform() platformInfo = await platform.getPlatformAndCreate(data._id.platform, null) if (!platformInfo || platformInfo.length === 0) { platformInfo._id = '' } this.platforms[data._id.platform] = platformInfo if (this.debug) { console.log('platformInfo', JSON.stringify(platformInfo)) } } // 渠道信息 let channelInfo = null data._id.channel = data._id.channel ? data._id.channel : '1001' const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel if (this.channels && this.channels[channelKey]) { channelInfo = this.channels[channelKey] } else { const channel = new Channel() channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel) if (!channelInfo || channelInfo.length === 0) { channelInfo._id = '' } this.channels[channelKey] = channelInfo if (this.debug) { console.log('channelInfo', JSON.stringify(channelInfo)) } } // 版本信息 let versionInfo = null const versionKey = data._id.appid + '_' + data._id.platform + '_' + data._id.version if (this.versions && this.versions[versionKey]) { versionInfo = this.versions[versionKey] } else { const version = new Version() versionInfo = await version.getVersionAndCreate(data._id.appid, data._id.platform, data._id.version) if (!versionInfo || versionInfo.length === 0) { versionInfo._id = '' } this.versions[versionKey] = versionInfo if (this.debug) { console.log('versionInfo', JSON.stringify(versionInfo)) } } //app启动次数 const sessionLog = new SessionLog() const sessionTimesRes = await this.getCollection(sessionLog.tableName).where({ appid: data._id.appid, version: data._id.version, platform: data._id.platform, channel: data._id.channel, create_time: { $gte: this.startTime, $lte: this.endTime } }).count() let sessionTimes = 0 if(sessionTimesRes && sessionTimesRes.total > 0) { sessionTimes = sessionTimesRes.total } else { console.log('Not found session logs') return false } //数据填充 const datetime = new DateTime() const insertParams = { appid: data._id.appid, platform_id: platformInfo._id, channel_id: channelInfo._id, version_id: versionInfo._id, type: 'crash', count: data.error_count, app_launch_count: sessionTimes, dimension: this.fillType, stat_date: datetime.getDate('Ymd', this.startTime), start_time: this.startTime, end_time: this.endTime } this.fillData.push(insertParams) return insertParams } }