uniApp App 端日志本地存储方案:实现可靠的日志记录功能
在移动应用开发过程中,日志记录是排查问题、分析用户行为的重要手段。对于 UniApp 开发的 App 来说,实现日志本地存储并在需要时导出,能极大地方便问题定位。本文将介绍如何在 UniApp 的 App 端实现日志的本地文件存储功能。
功能需求分析
一个完善的本地日志存储方案应具备以下功能:
- 支持写入不同级别日志(info、warn、error 等)
- 自动记录日志时间和级别
- 按日期分割日志文件,避免单个文件过大
- 支持设置日志文件最大保存天数
- 提供日志文件清理功能
- 确保在 App 重启后日志不会丢失
实现方案
下面是一个完整的日志工具类实现,基于 UniApp 的文件系统 API:
// logger.js
const LOG_LEVELS = {DEBUG: 'DEBUG',INFO: 'INFO',WARN: 'WARN',ERROR: 'ERROR'
}class Logger {constructor() {this.maxLogDays = 7 // 默认保存7天日志this.logDir = 'logs' // 日志目录this.init()}async init() {// 确保日志目录存在try {const dirInfo = await this.getDirInfo(this.logDir)if (!dirInfo) {await this.createDir(this.logDir)}} catch (e) {console.error('初始化日志目录失败:', e)}// 启动时清理过期日志this.cleanOldLogs()}// 设置日志最大保存天数setMaxLogDays(days) {if (days > 0) {this.maxLogDays = days}}// 获取当前日期字符串 (格式: YYYY-MM-DD)getCurrentDateStr() {const date = new Date()return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`}// 获取当前时间字符串getCurrentTimeStr() {const date = new Date()return `${this.getCurrentDateStr()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}.${date.getMilliseconds().toString().padStart(3, '0')}`}// 获取日志文件名getLogFileName() {return `app_${this.getCurrentDateStr()}.log`}// 写入日志async log(level, message, data = null) {try {const time = this.getCurrentTimeStr()let logContent = `[${time}] [${level}] ${message}`if (data) {logContent += ` | ${typeof data === 'object' ? JSON.stringify(data) : data}`}logContent += '\n'const fileName = this.getLogFileName()const filePath = `${this.logDir}/${fileName}`// 检查文件是否存在const fileInfo = await this.getFileInfo(filePath)if (fileInfo) {// 文件存在,追加内容await this.appendFile(filePath, logContent)} else {// 文件不存在,创建新文件await this.writeFile(filePath, logContent)}} catch (e) {console.error('写入日志失败:', e)}}// 快捷方法debug(message, data) {this.log(LOG_LEVELS.DEBUG, message, data)}info(message, data) {this.log(LOG_LEVELS.INFO, message, data)}warn(message, data) {this.log(LOG_LEVELS.WARN, message, data)}error(message, data) {this.log(LOG_LEVELS.ERROR, message, data)}// 清理过期日志async cleanOldLogs() {try {const now = new Date()const threshold = now.setDate(now.getDate() - this.maxLogDays)const dirInfo = await this.getDirInfo(this.logDir)if (!dirInfo) returnconst files = await this.readDir(this.logDir)for (const file of files) {const fileName = file.name// 解析文件名中的日期const dateStr = fileName.match(/app_(\d{4}-\d{2}-\d{2})\.log/)?.[1]if (dateStr) {const fileDate = new Date(dateStr)if (fileDate.getTime() < threshold) {// 文件日期早于阈值,删除await this.deleteFile(`${this.logDir}/${fileName}`)}}}} catch (e) {console.error('清理日志失败:', e)}}// 手动清理所有日志async clearAllLogs() {try {const files = await this.readDir(this.logDir)for (const file of files) {await this.deleteFile(`${this.logDir}/${file.name}`)}} catch (e) {console.error('清除所有日志失败:', e)}}// 获取所有日志文件列表async getLogFiles() {try {const files = await this.readDir(this.logDir)return files.filter(file => file.name.endsWith('.log'))} catch (e) {console.error('获取日志文件列表失败:', e)return []}}// 读取日志文件内容async readLogFile(fileName) {try {const filePath = `${this.logDir}/${fileName}`const content = await this.readFile(filePath)return content} catch (e) {console.error('读取日志文件失败:', e)return null}}// ========== 文件系统操作封装 ==========// 获取目录信息async getDirInfo(dirPath) {return new Promise((resolve, reject) => {plus.io.resolveLocalFileSystemURL(`_doc/${dirPath}`,entry => resolve(entry),error => resolve(null))})}// 创建目录async createDir(dirPath) {return new Promise((resolve, reject) => {plus.io.resolveLocalFileSystemURL('_doc',rootEntry => {rootEntry.getDirectory(dirPath,{ create: true, exclusive: false },dirEntry => resolve(dirEntry),error => reject(error))},error => reject(error))})}// 获取文件信息async getFileInfo(filePath) {return new Promise((resolve, reject) => {plus.io.resolveLocalFileSystemURL(`_doc/${filePath}`,entry => resolve(entry),error => resolve(null))})}// 写入文件async writeFile(filePath, content) {return new Promise((resolve, reject) => {plus.io.resolveLocalFileSystemURL('_doc',rootEntry => {rootEntry.getFile(filePath,{ create: true, exclusive: false },fileEntry => {fileEntry.createWriter(writer => {writer.onwriteend = () => resolve()writer.onerror = e => reject(e)writer.write(content)},error => reject(error))},error => reject(error))},error => reject(error))})}// 追加内容到文件async appendFile(filePath, content) {return new Promise((resolve, reject) => {plus.io.resolveLocalFileSystemURL(`_doc/${filePath}`,fileEntry => {fileEntry.createWriter(writer => {writer.onwriteend = () => resolve()writer.onerror = e => reject(e)writer.seek(writer.length)writer.write(content)},error => reject(error))},error => reject(error))})}// 读取文件内容async readFile(filePath) {return new Promise((resolve, reject) => {plus.io.resolveLocalFileSystemURL(`_doc/${filePath}`,fileEntry => {fileEntry.file(file => {const reader = new plus.io.FileReader()reader.onloadend = e => resolve(e.target.result)reader.onerror = e => reject(e)reader.readAsText(file)},error => reject(error))},error => reject(error))})}// 读取目录内容async readDir(dirPath) {return new Promise((resolve, reject) => {plus.io.resolveLocalFileSystemURL(`_doc/${dirPath}`,dirEntry => {const reader = dirEntry.createReader()reader.readEntries(entries => resolve(entries),error => reject(error))},error => reject(error))})}// 删除文件async deleteFile(filePath) {return new Promise((resolve, reject) => {plus.io.resolveLocalFileSystemURL(`_doc/${filePath}`,entry => {entry.remove(() => resolve(),error => reject(error))},error => reject(error))})}
}// 创建全局单例
const logger = new Logger()export default logger
使用方法
在 UniApp 项目中使用该日志工具非常简单,以下是使用示例:
import logger from './logger.js'// 记录不同级别日志
logger.debug('这是一条调试信息', { key: 'value' })
logger.info('这是一条普通信息')
logger.warn('这是一条警告信息')
logger.error('这是一条错误信息', new Error('示例错误'))// 设置日志最大保存天数
logger.setMaxLogDays(30) // 保存30天日志// 手动清理日志
logger.cleanOldLogs()// 清除所有日志
logger.clearAllLogs()
使用时,只需在项目中引入该方法,即可在任何需要记录日志的地方调用相应方法。