uni-app App端日志必须用plus.io.requestFileSystem实现稳定落盘,支持按天分文件(YYYYMMDD.txt)、Promise队列串行写入、自动清理;H5和小程序需分别适配。

uni-app本地日志写入必须用plus.io.requestFileSystem
真机环境(尤其是App端)下,uni.setStorageSync 或 uni.writeFile 无法满足日志持续追加、按天分文件、自动清理等需求。真正能稳定落盘的只有 plus.io.requestFileSystem —— 它是5+ Runtime提供的原生文件系统API,H5和小程序不支持,但App端必须走这条路。
常见错误现象:uni.writeFile 在Android离线包里写入成功却找不到文件;日志写入后重启App丢失;多线程并发写导致内容错乱。
- 只在App端启用该方案,H5用
console.log+localStorage做轻量记录,小程序用wx.getFileSystemManager()单独适配 -
plus.io.requestFileSystem返回的是FileSystem对象,后续所有root操作都基于它,不能缓存root引用,每次写前需重新resolve - 路径必须用
plus.io.convertLocalFileSystemURL转成可访问URL,否则uni.downloadFile或用户导出时路径无效
日志文件名必须含日期且固定格式
按天分割不是可选项,而是避免单文件爆炸、便于人工排查的关键设计。文件名必须用YYYYMMDD纯数字格式(如20261030.txt),不能带横线或冒号——部分Android机型文件系统不识别-作为文件名字符,会导致创建失败静默丢日志。
示例函数:
function getLogFileName() {
const date = new Date()
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
return `_doc/logs/${y}${m}${d}.txt`
}- 目录
_doc/logs/是推荐路径:App私有空间,无需权限声明,iOS/Android均兼容 - 不要用
plus.io.resolveLocalFileSystemURL直接拼接路径,它返回的Entry对象在写入时可能因缓存失效报NOT_FOUND_ERR - 首次写入前务必先
root.getDirectory创建logs目录,否则Android 10+会直接拒绝写入
写入必须用Promise队列防并发冲突
用户快速连续触发多个log('info')、log('error')时,如果直接并发调用fileWriter.write,大概率出现内容覆盖、换行错乱、甚至文件损坏。真实场景中,崩溃前的最后几条日志经常因并发写入而丢失关键上下文。
核心做法是维护一个promiseQueue,所有写请求进队列,串行执行:
let promiseQueue = []
let isTaskRunning = false
<p>function writeToLog(content) {
return new Promise((resolve) => {
promiseQueue.push(() => writeActual(content).then(resolve))
if (!isTaskRunning) runQueue()
})
}</p><p>function runQueue() {
if (promiseQueue.length === 0) {
isTaskRunning = false
return
}
isTaskRunning = true
promiseQueue.shift()().catch(() => {}).finally(runQueue)
}- 每个
writeActual内部必须用fileWriter.seek(fileWriter.length)确保追加到末尾,不能依赖FILE_APPEND模式(iOS不支持) - 每条日志开头强制加
\n,结尾加\n,避免多条日志挤在同一行 - 队列满时不要
reject,应console.warn并丢弃——宁可少记一条,也不能卡住主线程
自动清理过期日志要避开iOS沙盒限制
设置“最多保留7天日志”听起来简单,但在iOS上直接遍历_doc/logs/目录删除旧文件,极可能因沙盒路径不可见而失败。正确做法是:只删当前已知存在的文件名(即你生成过的YYYYMMDD.txt),不依赖getDirectory的entries列表。
清理逻辑示例:
function cleanupOldLogs(keepDays = 7) {
const today = new Date()
for (let i = keepDays; i < 100; i++) {
const date = new Date()
date.setDate(today.getDate() - i)
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
const fileName = `_doc/logs/${y}${m}${d}.txt`
// 直接尝试删除,不检查是否存在
plus.io.resolveLocalFileSystemURL(fileName, entry => {
entry.remove()
}, () => {}) // 忽略不存在错误
}
}- 不要在
onLaunch里立即执行清理,应延时1~2秒,等文件系统就绪 - 清理动作本身也需进
promiseQueue,避免与写日志争抢文件句柄 - Android上建议同时调用
plus.runtime.getProperty('version')判断是否为debug包,debug包可设keepDays=30,正式包严格按7天执行
日志工具最易被忽略的点是跨端入口统一:App端用plus.io,H5用localStorage拼接字符串,小程序用fs.appendFile,三者API差异大,但对外暴露的logger.info()、logger.error()接口必须完全一致——否则业务层条件编译会失控。封装时优先保证调用方式干净,再解决底层差异。



















