|
@@ -0,0 +1,237 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * Cordova 日志写入器
|
|
|
|
|
+ * 使用 cordova-plugin-file 将日志保存到设备本地文件系统
|
|
|
|
|
+ */
|
|
|
|
|
+export class CordovaLogWriter {
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ this.logFile = null;
|
|
|
|
|
+ this.isReady = false;
|
|
|
|
|
+ this.initPromise = this.initialize();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 文件大小限制:2MB
|
|
|
|
|
+ static get MAX_SIZE() {
|
|
|
|
|
+ return 2 * 1024 * 1024;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化日志写入器
|
|
|
|
|
+ * 获取文件系统并创建日志文件
|
|
|
|
|
+ */
|
|
|
|
|
+ async initialize() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 等待Cordova就绪
|
|
|
|
|
+ if (!window.cordova) {
|
|
|
|
|
+ throw new Error('Cordova not available');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 等待deviceready事件(如果还没触发)
|
|
|
|
|
+ if (!this.isDeviceReady()) {
|
|
|
|
|
+ await this.waitForDeviceReady();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取应用数据目录的文件系统
|
|
|
|
|
+ const fs = await this.getFileSystem();
|
|
|
|
|
+
|
|
|
|
|
+ // 查找当前可写的日志文件(考虑大小限制)
|
|
|
|
|
+ this.logFile = await this.findCurrentLogFile(fs);
|
|
|
|
|
+ this.isReady = true;
|
|
|
|
|
+
|
|
|
|
|
+ console.log('Cordova日志写入器初始化完成');
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Cordova日志初始化失败:', error);
|
|
|
|
|
+ // 不抛出错误,避免阻塞应用启动
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 检查设备是否就绪
|
|
|
|
|
+ */
|
|
|
|
|
+ isDeviceReady() {
|
|
|
|
|
+ return document.readyState === 'complete' &&
|
|
|
|
|
+ window.cordova &&
|
|
|
|
|
+ (typeof device !== 'undefined');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 等待Cordova deviceready事件
|
|
|
|
|
+ */
|
|
|
|
|
+ waitForDeviceReady() {
|
|
|
|
|
+ return new Promise((resolve) => {
|
|
|
|
|
+ if (this.isDeviceReady()) {
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const onDeviceReady = () => {
|
|
|
|
|
+ document.removeEventListener('deviceready', onDeviceReady);
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ document.addEventListener('deviceready', onDeviceReady, false);
|
|
|
|
|
+
|
|
|
|
|
+ // 超时保护(30秒)
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ document.removeEventListener('deviceready', onDeviceReady);
|
|
|
|
|
+ resolve(); // 即使超时也继续
|
|
|
|
|
+ }, 30000);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取文件系统
|
|
|
|
|
+ */
|
|
|
|
|
+ getFileSystem() {
|
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
|
+ window.resolveLocalFileSystemURL(
|
|
|
|
|
+ cordova.file.dataDirectory,
|
|
|
|
|
+ (fs) => resolve(fs),
|
|
|
|
|
+ (error) => reject(error)
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 生成日志文件名(按日期,支持索引)
|
|
|
|
|
+ */
|
|
|
|
|
+ getLogFileName(index = 0) {
|
|
|
|
|
+ const date = new Date().toISOString().slice(0, 10);
|
|
|
|
|
+ const paddedIndex = index.toString().padStart(3, '0');
|
|
|
|
|
+ return `${date}_${paddedIndex}.log`;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查找当前可写的日志文件(考虑文件大小限制)
|
|
|
|
|
+ */
|
|
|
|
|
+ async findCurrentLogFile(fs) {
|
|
|
|
|
+ let index = 0;
|
|
|
|
|
+ while (true) {
|
|
|
|
|
+ const fileName = this.getLogFileName(index);
|
|
|
|
|
+ try {
|
|
|
|
|
+ const fileEntry = await this.getLogFile(fs, fileName);
|
|
|
|
|
+ const fileInfo = await this.getFileInfo(fileEntry);
|
|
|
|
|
+ if (fileInfo.size < CordovaLogWriter.MAX_SIZE) {
|
|
|
|
|
+ return fileEntry; // 文件存在且大小合适
|
|
|
|
|
+ }
|
|
|
|
|
+ index++; // 文件已满,尝试下一个索引
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // 文件不存在,直接使用
|
|
|
|
|
+ return await this.getLogFile(fs, fileName);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取文件信息(用于检查大小)
|
|
|
|
|
+ */
|
|
|
|
|
+ async getFileInfo(fileEntry) {
|
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
|
+ fileEntry.file(
|
|
|
|
|
+ (file) => resolve(file),
|
|
|
|
|
+ (error) => reject(error)
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取或创建日志文件
|
|
|
|
|
+ */
|
|
|
|
|
+ async getLogFile(fs, fileName) {
|
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
|
+ fs.getFile(
|
|
|
|
|
+ fileName,
|
|
|
|
|
+ { create: true, exclusive: false },
|
|
|
|
|
+ (fileEntry) => resolve(fileEntry),
|
|
|
|
|
+ (error) => reject(error)
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 写入日志(支持文件轮转)
|
|
|
|
|
+ */
|
|
|
|
|
+ async writeLog(level, msg) {
|
|
|
|
|
+ await this.initPromise;
|
|
|
|
|
+
|
|
|
|
|
+ if (!this.isReady) {
|
|
|
|
|
+ console.warn('Cordova日志写入器未就绪,跳过日志写入');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查当前文件是否已满,如果是则切换到新文件
|
|
|
|
|
+ await this.ensureCurrentFileWritable();
|
|
|
|
|
+
|
|
|
|
|
+ if (!this.logFile) {
|
|
|
|
|
+ console.warn('无法获取可写日志文件,跳过日志写入');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const line = `[${new Date().toISOString()}] [${level}] ${msg}\n`;
|
|
|
|
|
+
|
|
|
|
|
+ return new Promise((resolve) => {
|
|
|
|
|
+ this.logFile.createWriter(
|
|
|
|
|
+ (writer) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ writer.seek(writer.length); // 移动到文件末尾进行追加
|
|
|
|
|
+ writer.write(line);
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('写入日志失败:', error);
|
|
|
|
|
+ resolve(); // 不抛出错误,避免阻塞应用
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ (error) => {
|
|
|
|
|
+ console.error('创建文件写入器失败:', error);
|
|
|
|
|
+ resolve(); // 不抛出错误,避免阻塞应用
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 确保当前文件可写(检查大小,必要时轮转)
|
|
|
|
|
+ */
|
|
|
|
|
+ async ensureCurrentFileWritable() {
|
|
|
|
|
+ if (!this.logFile) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const fileInfo = await this.getFileInfo(this.logFile);
|
|
|
|
|
+ if (fileInfo.size >= CordovaLogWriter.MAX_SIZE) {
|
|
|
|
|
+ // 当前文件已满,切换到新文件
|
|
|
|
|
+ const fs = await this.getFileSystem();
|
|
|
|
|
+ this.logFile = await this.findCurrentLogFile(fs);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn('检查文件大小时出错:', error);
|
|
|
|
|
+ // 出错时尝试重新获取文件系统
|
|
|
|
|
+ try {
|
|
|
|
|
+ const fs = await this.getFileSystem();
|
|
|
|
|
+ this.logFile = await this.findCurrentLogFile(fs);
|
|
|
|
|
+ } catch (fsError) {
|
|
|
|
|
+ console.error('重新获取文件系统失败:', fsError);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取日志文件路径(用于调试)
|
|
|
|
|
+ */
|
|
|
|
|
+ async getLogFilePath() {
|
|
|
|
|
+ await this.initPromise;
|
|
|
|
|
+ if (this.logFile) {
|
|
|
|
|
+ return this.logFile.toURL();
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 导出单例实例
|
|
|
|
|
+let cordovaWriterInstance = null;
|
|
|
|
|
+export function getCordovaLogWriter() {
|
|
|
|
|
+ if (!cordovaWriterInstance) {
|
|
|
|
|
+ cordovaWriterInstance = new CordovaLogWriter();
|
|
|
|
|
+ }
|
|
|
|
|
+ return cordovaWriterInstance;
|
|
|
|
|
+}
|