在上一篇《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
}
}



发表评论