生产环境,app应用不能像PC端一样,可以在浏览器控制台调试,所以应用加了日志打印的开关,需要实现日志文件的生成,写入,获取大小以及压缩功能,方便后期定位数据问题。
一、创建公共文件
引入与定义变量
javascript
// utils/logService.js
import moment from 'moment';
let logFileEntry = null; // 判断是否存在上次生成文件的依据
const LOG_ENTRY_KEY = 'log_file_entry'; // 存到本地存储的key值
const time = moment().format("YYYY-MM-DD HH:mm:ss"); // 生成文件名所用
export const logService = {
// 所有方法都是放在这里的
// 初始化
async init() {
// 如果已有引用,直接返回
if (logFileEntry) {
return logFileEntry;
}
// 先从本地存储中获取
const cachedEntry = await this.getStoredLogFileEntry();
// 如果缓存中存在,则使用缓存的引用
if (cachedEntry && cachedEntry.fullPath) {
try {
// 尝试恢复文件引用
logFileEntry = await this.restoreFileEntry(cachedEntry);
return logFileEntry;
} catch (error) {
console.warn('恢复缓存文件引用失败,将创建新文件:', error);
}
}
// 缓存不存在或恢复失败,创建新文件
if (!logFileEntry) {
try {
const res = await this.createLogFile();
logFileEntry = res.fileEntry;
} catch (error) {
console.error('初始化日志文件失败:', error);
throw error;
}
}
return logFileEntry;
},
...
}
二、生成txt文件
javascript
// 创建当前时间日志文件
createLogFile() {
const fileName = `appLog_${time}.txt`;
return new Promise((resolve, reject) => {
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {
fs.root.getFile(fileName, {
create: true
}, (fileEntry) => {
// 获取文件路径
fileEntry.file((file) => {
const filePath = fileEntry.toLocalURL();
// 清空缓存
this.clearStoredLogFileEntry();
const initData = `创建应用日志文件`;
// 初始数据写入
this.writeLogContent(fileEntry, initData)
.then(() => {
// 创建成功后立即存储到缓存
this.storeLogFileEntry(fileEntry);
resolve({
fileEntry,
filePath
});
})
.catch((writeError) => {
// 新文件信息已存储到缓存(写入失败)
this.storeLogFileEntry(fileEntry);
resolve({
fileEntry,
filePath
});
});
}, (fileError) => {
// 即使获取文件信息失败,也返回文件对象
resolve(fileEntry);
});
}, (getFileError) => {
reject(getFileError);
});
}, (fsError) => {
reject(fsError);
});
});
},
三、写入内容
javascript
// 写入日志(提供该方法给其他页面组件调用,写入之前初始化判断文件引用是否存在)
async writeLog(content) {
try {
await this.init();
return this.writeLogContent(logFileEntry, content);
} catch (error) {
console.error('写入日志失败:', error);
}
},
/**
* 写入文件
* @param {String} 文件路径
* @returns 当前日志文件引用
* @returns {String} 写入内容
*/
writeLogContent(logFileEntry, content) {
return new Promise((resolve, reject) => {
const time = moment().format("YYYY-MM-DD HH:mm:ss");
logFileEntry.createWriter((writer) => {
writer.seek(writer.length);
// 格式化内容
let formattedContent = content;
if (typeof content === 'object' && content !== null) {
formattedContent = JSON.stringify(content, null, 2);
}
// 添加时间和换行
const logEntry = `\n[${time}] \n ${formattedContent}`;
// const blob = new Blob([logEntry], { type: 'text/plain;charset=utf-8' });
writer.write(logEntry);
writer.onwrite = () => {
resolve();
};
writer.onerror = (error) => {
console.error('日志写入失败:', error);
reject(error);
};
}, reject);
});
},
页面组件调用写入方法
javascript
import {
logService
} from '@/utils/logService';
// 写入文件
writeToFile() {
const data = "写入内容"
logService.writeLog(data);
}
四、获取文件大小
javascript
// 获取日志文件信息(路径和大小)
async getLogFileInfo() {
try {
const filePath = await this.getCurrentLogFilePath();
if (!filePath) {
return {
filePath: null,
fileSize: '0KB'
};
}
const fileSize = await this.getFileSize(filePath);
return {
filePath,
fileSize
};
} catch (error) {
console.error('获取日志文件信息失败:', error);
return {
filePath: null,
fileSize: '0KB'
};
}
},
页面组件调用获取大小方法
javascript
import {
logService
} from '@/utils/logService';
// 更新日志文件信息
async updateLogFileInfo() {
try {
const fileInfo = await logService.getLogFileInfo(); // fileInfo:filePath、fileSize
// 执行其他操作
},
五、文件压缩成zip
javascript
/**
* 压缩日志文件-zip
* @returns {Promise} 返回一个Promise,包含上传结果
*/
async compressLog() {
// 确保日志文件已初始化
await this.init();
if (!logFileEntry) {
throw new Error('日志文件未初始化');
}
// 验证日志文件是否可用
if (!logFileEntry.toLocalURL) {
console.error('日志文件引用无效');
return;
}
const logFilePathOrigin = logFileEntry.toLocalURL();
let logFilePath = logFilePathOrigin;
// 移除可能的file://前缀
if (logFilePath.startsWith('file://')) {
logFilePath = logFilePath.substring(7);
}
// 检查文件是否存在
const isExist = await this.fileExists(logFilePathOrigin);
if (!isExist) {
console.error("日志文件不存在:", logFilePathOrigin);
return;
}
// 获取临时目录
const tempFs = await new Promise((resolve, reject) => {
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, resolve, reject);
});
const tempDir = tempFs.root.fullPath;
if (!tempDir.endsWith('/')) {
tempDir += '/';
}
// 构建ZIP文件名
const logFileName = logFileEntry.name;
const logTimestampMatch = logFileName.match(/_([^\.]+)\./); // 正则取出_和.之间的,也就是时间:2025-12-17 15:38:43
const logTimestamp = logTimestampMatch?.length && logTimestampMatch[1] || time; // 取txt文件的时间作为zip的时间
const zipFileName = `appLog_${logTimestamp}.zip`;
const zipFilePath = `${tempDir}${zipFileName}`;
const zipFilePathOrigin = `file://${zipFilePath}`;
// 检查并删除已存在的zip文件
try {
// 判断压缩文件是否存在
const zipExists = await this.fileExists(zipFilePathOrigin);
if (zipExists) {
// 删除压缩文件
await this.deleteFile(zipFilePathOrigin);
}
} catch (e) {
console.warn("清理旧zip文件失败:", e);
}
// 检查plus.zip对象是否存在
if (!plus.zip || !plus.zip.compress) {
return;
}
// 返回Promise
return new Promise((resolve, reject) => {
plus.zip.compress(
logFilePath,
zipFilePath,
async (result) => {
try {
// 判断是否压缩成功
const exists = await this.fileExists(zipFilePathOrigin);
if (exists) {
resolve({
success: true,
zipFilePath: zipFilePathOrigin,
zipFileName: zipFileName,
message: '压缩成功'
});
} else {
reject(new Error('压缩文件未创建'));
}
} catch (error) {
reject(error);
}
},
(error) => {
const errorMsg = error && (error.message || JSON.stringify(
error));
reject(new Error(`压缩失败: ${errorMsg}`));
}
);
})
},
页面组件调用压缩方法
javascript
import {
logService
} from '@/utils/logService';
// 压缩日志文件
async handleCompress() {
const zipRes = await logService.compressLog();
if (zipRes?.success) {
// 压缩成功,执行其他操作,比如上传
}
}
六、工具类方法
javascript
// 检查文件是否存在
async fileExists(filePath) {
if (!filePath) {
return false;
}
// 统一转为file://格式
let checkPath = filePath;
if (!checkPath.startsWith('file://')) {
checkPath = `file://${checkPath}`;
}
return new Promise((resolve) => {
// 使用resolveLocalFileSystemURL检查文件
plus.io.resolveLocalFileSystemURL(
checkPath,
(entry) => {
resolve(true);
},
(err) => {
resolve(false);
}
);
});
},
// 删除文件
deleteFile(filePath) {
return new Promise((resolve, reject) => {
plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
entry.remove(() => {
resolve(true);
}, (error) => {
reject(error);
});
}, (error) => {
// 文件不存在也算成功
resolve(false);
});
});
},
// 获取当前日志文件路径
async getCurrentLogFilePath() {
try {
// 确保已初始化
await this.init();
if (logFileEntry) {
return logFileEntry.toLocalURL();
}
return null;
} catch (error) {
console.error('获取日志文件路径失败:', error);
return null;
}
},
// 从本地存储获取日志文件引用
getStoredLogFileEntry() {
try {
const entryInfo = uni.getStorageSync(LOG_ENTRY_KEY);
if (entryInfo && entryInfo.fullPath) {
return entryInfo;
}
} catch (error) {
console.error('读取缓存失败:', error);
}
return null;
},
// 存储日志文件引用到本地存储
storeLogFileEntry(fileEntry) {
try {
const cacheInfo = {
fullPath: fileEntry.fullPath,
name: fileEntry.name,
toLocalURL: fileEntry.toLocalURL(),
toURL: fileEntry.toURL(),
timestamp: Date.now()
};
uni.setStorageSync(LOG_ENTRY_KEY, cacheInfo);
} catch (error) {
console.error('缓存日志文件信息失败:', error);
}
},
// 清空存储的日志文件引用
clearStoredLogFileEntry() {
try {
uni.removeStorageSync(LOG_ENTRY_KEY);
logFileEntry = null;
} catch (error) {
console.error('清空日志文件缓存失败:', error);
}
},
// 从缓存信息恢复文件引用
async restoreFileEntry(cacheInfo) {
return new Promise((resolve, reject) => {
if (!cacheInfo || !cacheInfo.fullPath) {
reject(new Error('缓存信息无效'));
return;
}
// 使用 fullPath 或 toLocalURL 恢复文件引用
const filePath = cacheInfo.fullPath || cacheInfo.toLocalURL;
if (!filePath) {
reject(new Error('缓存中没有有效的文件路径'));
return;
}
plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
// 验证文件是否存在并可写
entry.createWriter((writer) => {
// 如果能创建writer,说明文件可访问
resolve(entry);
}, (writerError) => {
// 即使创建writer失败,也返回entry
resolve(entry);
});
}, (error) => {
reject(null);
});
});
},