在上一篇《UniApp嵌套的H5项目本地记录日志(一)》中提到了安卓APP本地记录日志的需求,使用H5+实现日志记录。而在实际测试过程中,还是发现了几个问题。
1、程序部分出现异常,数据有不显示的情况,某些场景会导致程序卡死。
2、当前日志记录为每次直接写入文件,当调用logInfo方法间隔较短时可能会引发异常。
3、假设出现无法预知的情况,导致大量报错,且持续时间很长,会导致日志文件过大,影响读写。
在经过一段时间的研究后,找到了解决方案
1、通过注释代码得知,是handlerMethods的处理导致了数据显示异常和应用卡死。
2、关于短时间内多次写日志的,添加一个定时器,控制三秒内只进行一次日志写入操作,如果有多次日志记录,则进行数据缓存
3、关于一天一个日志文件,文件可能存在过大的情况,更换思路,一天建一个文件夹,按照数字顺序生成日志文件,每当日志达到5M,再生成一个新的文件。
不过要实现以上功能,还需要几个方法的支持,清理超过天数日志文件夹、递归删除文件、文件夹目录读取、文件信息转换
生成日志文件如下
生成日志完整功能代码
// 创建排序方法 function sortByKeys(array, key, order) { return array.sort(function(a, b) { var x = a[key]; var y = b[key] if (order) { return x - y } else { return y - x } }) } /** * 返回当前日期 YYYY-MM-DD */ function getToday(date) { // const date = new Date() const seperator1 = '-' const year = date.getFullYear() let month = date.getMonth() + 1 let strDate = date.getDate() if (month >= 1 && month <= 9) { month = '0' + month } if (strDate >= 0 && strDate <= 9) { strDate = '0' + strDate } const currentdate = year + seperator1 + month + seperator1 + strDate return currentdate } function supplyZero(number) { return number >= 10 ? number : '0' + number } // 获取当前时间 function getNowTime() { const date = new Date() // 年 getFullYear():四位数字返回年份 const year = date.getFullYear() // getFullYear()代替getYear() // 月 getMonth():0 ~ 11 const month = date.getMonth() + 1 // 日 getDate():(1 ~ 31) const day = date.getDate() // 时 getHours():(0 ~ 23) const hour = date.getHours() // 分 getMinutes(): (0 ~ 59) const minute = date.getMinutes() // 秒 getSeconds():(0 ~ 59) const second = date.getSeconds() const time = year + '-' + supplyZero(month) + '-' + supplyZero(day) + ' ' + supplyZero(hour) + ':' + supplyZero(minute) + ':' + supplyZero(second) return time } // 获取给定日期至前七天的日期数组 function getDay7(data) { // 传入 yyyy-MM-dd 格式 const datas = [] for (let i = 0; i < 7; i++) { datas.push(getBeforeDate(data, -i)) } return datas } // 获取xx天后的日期 传入负数 则是多少天前的日期 /** * @param {Object} date 日期字符串 yyyy-MM-dd * @param {Object} num 多少天 数字 */ function getBeforeDate(date, num) { date = date.replace(/-/g, '/') var timestamp = new Date(date).getTime() return new Date(timestamp + num * 1000 * 60 * 60 * 24) } /** * @param {Object} value Date对象 * @param {Object} fmt */ function formatDate(value, fmt = 'yyyy-MM-dd') { let getDate if (value) { getDate = new Date(value) } else { getDate = new Date() } const o = { 'M+': getDate.getMonth() + 1, 'd+': getDate.getDate(), 'h+': getDate.getHours(), 'm+': getDate.getMinutes(), 's+': getDate.getSeconds(), 'q+': Math.floor((getDate.getMonth() + 3) / 3), S: getDate.getMilliseconds() } if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (getDate.getFullYear() + '').substr(4 - RegExp.$1.length)) } for (const k in o) { if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)) } } return fmt } /** * 记录日志 * @param logLevel 日志级别 * @param logType 日志类型 * @param message 日志内容 */ function logInfo(logLevel, logType, message) { // 非App模式 或者不支持H5+ 跳过 if (window.localStorage.getItem('showModel') !== 'app' || !window.plus) { return } // 清除超过范围的日志文件 clearOverLog() const File = window.plus.android.importClass('java.io.File') // 今天的日期 const today = formatDate(new Date(), 'yyyyMMdd') // 当天日期路径 const todayLogPath = '/sdcard/wisdomApp/log/' + String(today) // 当前日期文件夹 const logFolder = new File(todayLogPath) if (!logFolder.exists()) { logFolder.mkdirs() } // 读取当天日志目录下的文件列表 const curLogfileArr = readFolderStructure(todayLogPath) // 最新的日志文件 let newLogFile = null if (curLogfileArr && curLogfileArr.length) { curLogfileArr.forEach(item => { item.sortKey = Number(item.fileName.replace(/.txt/, '')) }) // 根据名称排序 const sortArr = sortByKeys(curLogfileArr, 'sortKey', false) newLogFile = sortArr[0] } // 新文件名称 let newLogFileName = '' // 有文件 判定文件大小 if (newLogFile) { // 如果文件大小大于等于5MB if (Number(newLogFile.fileSize || 0) >= 5) { // 则重新创建一个新的文件 newLogFileName = (Number(newLogFile.fileName.replace(/.txt/, '')) + 1) + '.txt' // 读取原文件名称 } else { newLogFileName = newLogFile.fileName } // 没有文件 则创建新的 } else { newLogFileName = '1.txt' } // 写入日志路径 const writeLogFilePath = todayLogPath + '/' + newLogFileName const newLogFileObj = new File(writeLogFilePath) if (!newLogFileObj.exists()) { newLogFileObj.createNewFile() // 创建文件 } // 处理日志内容 const messageLine = getNowTime() + '[' + logLevel + '][' + logType + ']:' + message // 读取缓存日志记录 const logCache = window.logCache || [] // 记录当前日志 logCache.push(messageLine) // 重新赋值 window.logCache = logCache // 定时器存在 if (window.writerLogTimer) { // 直接返回 return } // 写日志定时器 window.writerLogTimer = setTimeout(() => { // 获取日志中的全部记录 const messageLineStr = window.logCache.join('\r\n') // 立即清空日志 window.logCache = [] // 写入日志 writeFileFun(writeLogFilePath, messageLineStr, true) // 清除定时器 window.writerLogTimer = null }, 3000) } /** * 清除超过范围的日志文件 */ function clearOverLog() { // 读取当前日志目录下的文件列表 const fileArr = readFolderStructure('/sdcard/wisdomApp/log/') // 获取七天的日期列表 const day7Arr = getDay7(getToday(new Date())) const logNameArr = [] for (let i = 0; i < day7Arr.length; i++) { logNameArr.push(formatDate(day7Arr[i], 'yyyyMMdd')) } try { const File = window.plus.android.importClass('java.io.File') // 遍历全部文件 for (let i = 0; i < fileArr.length; i++) { const fileName = fileArr[i].fileName const filePath = fileArr[i].filePath // 超过七天日期的文件夹 需要清理 if (!logNameArr.includes(fileName)) { const parentFolder = new File(filePath) // 递归删除文件 delete0(parentFolder) } } const logFolder = new File('/sdcard/wisdomApp/log/') if (!logFolder.exists()) { logFolder.mkdirs() return } } catch (e) { console.error(e) } } /** * 递归删除文件 * @param filePath */ function delete0(file) { try { if (!file.exists()) { return } if (file.isDirectory()) { const files = file.listFiles() if (files != null && files.length > 0) { for (let i = 0; i < files.length; i++) { delete0(files[i]) } } } else { file.delete() } } catch (e) { return false } } /** * 读取文件夹结构 * @param filePath */ function readFolderStructure(filePath) { const folderArr = [] try { // 只能用于安卓 导入java类 const File = window.plus.android.importClass('java.io.File') const logFolder = new File(filePath) if (!logFolder.exists()) { logFolder.mkdirs() return folderArr } // 文件列表 const files = logFolder.listFiles() if (files && files.length > 0) { for (let i = 0; i < files.length; i++) { const fileObj = files[i] const fileJsObj = convertFile(fileObj) // 当前是目录 if (fileObj.isDirectory()) { // 递归获取子目录 const children = readFolderStructure(fileObj.getPath()) // 记录子目录 fileJsObj.children = children } folderArr.push(fileJsObj) } } return folderArr } catch (e) { return folderArr } } /** * 根据路径读取文件信息 * @param filePath */ /* function getFileInfoByPath(filePath) { try { // 只能用于安卓 导入java类 const File = window.plus.android.importClass('java.io.File') const logFile = new File(filePath) if (!logFile.exists()) { return null } return convertFile(logFile) } catch (e) { return null } }*/ /** * 转换文件对象 * @param curFile * @returns {{}} */ function convertFile(curFile) { const fileObj = {} fileObj.fileName = curFile.getName() fileObj.filePath = curFile.getPath() const lastModified = curFile.lastModified() fileObj.lastUpdateTimeFormat = formatDate(new Date(lastModified), 'yyyy-MM-dd hh:mm:ss') fileObj.lastUpdateTimeNum = formatDate(new Date(lastModified), 'yyyyMMddhhmmss') fileObj.fileSize = parseFloat((curFile.length() || 0) / (1024 * 1024)).toFixed(2) fileObj.isDirectory = curFile.isDirectory() return fileObj } /** * 将文本写入文件 * @param filePath 文件路径 * @param text 文本 * @param isAppend 是否追加 * @returns {boolean} */ function writeFileFun(filePath, res, isAppend) { try { // 只能用于安卓 导入java类 const File = window.plus.android.importClass('java.io.File') const FileWriter = window.plus.android.importClass('java.io.FileWriter') // 不加根目录创建文件(即用相对地址)的话directory.exists()这个判断一值都是false const n = filePath.lastIndexOf('/') if (n !== -1) { const fileDirs = filePath.substring(0, n) const directory = new File(fileDirs) if (!directory.exists()) { directory.mkdirs() // 不存在创建目录 } } const file = new File(filePath) if (!file.exists()) { file.createNewFile() // 创建文件 } const fos = new FileWriter(filePath, !!isAppend) fos.write(res) fos.close() return true } catch (e) { return false } }
发表评论