uniapp本地存储日志

背景

我们的APP开发完成之后,在我们测试环境或者自测的时候都好好的,但是发布到生产环境客户使用总会出现一些奇奇怪怪的问题。这时候因为没在开发环境,我们无法查看到日志,所以我们需要手机到用户的操作日志然后上传,方便我们排查出问题。

实现

我们通过5+API提供的IO模块来进行文件的读写操作。主要实现如下功能:
1、日志打印
2、日志按天存储(同一天的日志会自动续写到文件)
3、日志删除
4、日志压缩

代码实现

主要通过plus.io.requestFileSystem对应用私有目录进行读写,完整代码如下:

// 新建logger.js文件
// 日志存放的文件夹目录
const LOG_DIR = '_doc/logs';
/**
 * 获取当前时间
 */
function getDayStr() {
	var y, m, d, h, mm, s;
	var date = new Date();
	y = date.getFullYear();
	m = date.getMonth() + 1;
	d = date.getDate();
	m = m < 10 ? '0' + m : m;
	d = d < 10 ? '0' + d : d;
	// //console.log('日期:',y,m,d)
	// return '20220607' // 生成指定日期
	return '' + y + m + d;
}
/**
 * 获取当前时间,yyyy-mm-dd hh:mm:ss
 * 用于记录日志的时间信息
 */
function getTimeStr() {
	var y, m, d, h, mm, s;
	var date = new Date();
	y = date.getFullYear();
	m = date.getMonth() + 1;
	d = date.getDate();
	h = date.getHours();
	mm = date.getMinutes();
	s = date.getSeconds();
	m = m < 10 ? '0' + m : m;
	d = d < 10 ? '0' + d : d;
	h = h < 10 ? '0' + h : h;
	mm = mm < 10 ? '0' + mm : mm;
	s = s < 10 ? '0' + s : s;
	var timeStr = y + '-' + m + '-' + d + ' ' + h + ':' + mm + ':' + s;
	return timeStr;
}
/**
 * 日志TXT的名称
 */
function getLogFileName() {
	const txt = LOG_DIR + '/' + getDayStr() + '.txt';
	// //console.log('TXT文件名称:',txt)
	return txt;
}
//用来保存写文件的promise
let promiseQueue = []
//是否有任务再执行
let isTaskRunning = false
/**
 * @param {Object} tag 标识
 * @param {Object} msg 空格
 */
function writeToTxt(tag) {
	let promise = new Promise((resolve, reject) => {
		let tasks = [];
		let msgs = '';
		for (var i = 1; i < arguments.length; i++) {
			const item = arguments[i];
			if (
				typeof item == 'string' ||
				typeof item == 'number' ||
				typeof item == 'boolean'
			) {
				msgs = msgs + '\t' + item;
			} else {
				msgs = msgs + '\t' + JSON.stringify(item);
			}
		}
		// 获取当前时间
		let txt_msg = getTimeStr() + '\t[' + tag + ']\t' + msgs + '\n';
		tasks.push(txt_msg);
		resolve(tasks);
	})
	//先将日志写入任务promise和当时的时间压栈
	promiseQueue.push(promise)
	//如果没有写入任务执行
	if(!isTaskRunning && promiseQueue.length > 0){
		isTaskRunning = true
		runQueue()
	}
}
//执行队列
function runQueue(){
	promiseQueue.shift().then(res => {
		isTaskRunning = true
		clearTask(res).then(res => {
			if(promiseQueue.length > 0){
				runQueue()
			}else {
				isTaskRunning = false
			}
		})
	})
}
// 写入日志到日志文件
function clearTask(tasks) {
	// #ifdef APP-PLUS
	return new Promise((resolve, reject) => {
	    if (tasks.length === 0) {
	    	reject('日志内容为空')
	    }else {
			const txt_msg = tasks.join('');
			const fileName = getLogFileName();
			plus.io.requestFileSystem(
				plus.io.PRIVATE_DOC,
				fs => {
					fs.root.getFile(
						fileName, {
							create: true,
						},
						function(entry) {
							// 写入到本地
							entry.createWriter(
								function(writer) {
									writer.onwrite = function(e) {
										// console.log("Write data success!");
										// console.log('写入本地日志 >>>> ', txt_msg);
										resolve({
											msg: '写入本地日志完成',
											success: true
										})
									};
									writer.onerror = function(e) {
										reject({
											msg: '写入本地日志失败' + JSON.stringify(e),
											success: false
										})
										if (process.env.NODE_ENV === 'development'){
											console.eror(
												'写入本地日志失败 >>>> ',
												JSON.stringify(e),
												txt_msg
											);
										}
									};
									// Write data to the end of file.
									writer.seek(writer.length);
									writer.write(txt_msg);
								},
								function(e) {
									reject({
										msg: '创建写文件对象出错' + e.message,
										success: false
									})
									if (process.env.NODE_ENV === 'development') {
										console.error(e.message);
									}
								}
							);
						}
					);
				},
				function(e) {
					reject({
						msg: '访问应用沙盒出错' + JSON.stringify(e),
						success: false
					})
					if (process.env.NODE_ENV === 'development') {
						console.error('Request file system failed: ' + JSON.stringify(e));
					}
				}
			);
		}    
	});

	// #endif
}

/**
 * 压缩所有的日志为zip
 */
function zipLogDir(callback) {
	// #ifdef APP-PLUS
	var zipFile = '_doc/logs.zip';
	var targetPath = LOG_DIR;

	// 开始压缩文件
	if (process.env.NODE_ENV === 'development') {
		console.log('开始压缩', targetPath, zipFile);
	}
	plus.zip.compress(
		targetPath,
		zipFile,
		function(res) {
			if (process.env.NODE_ENV === 'development') {
				console.log('开始压缩 Compress success!', res);
			}
			if (callback) {
				callback({
					success: true,
					res,
					zipPath: zipFile,
				});
			}
		},
		function(error) {
			if (process.env.NODE_ENV === 'development') {
				console.error('开始压缩 Compress error!', error);
			}
			if (callback) {
				callback({
					success: false,
					error,
				});
			}
		}
	);
	// #endif
}
/**
 * 删除多少天之前的日志文件
 */
function removeFile(durationDay) {
	// #ifdef APP-PLUS
	return new Promise((resolve, reject) => {
		if (!durationDay || durationDay <= 0) {
			durationDay = 10;
		}
		var dirPath = LOG_DIR;
		plus.io.resolveLocalFileSystemURL(
			dirPath,
			function(entry) {
				//读取这个目录对象
				var directoryReader = entry.createReader();
				//读取这个目录下的所有文件
				directoryReader.readEntries(
					function(entries) {
						console.log('日志文件数量', entries.length);
						//如果有才操作
						if (entries.length > 0) {
							let now = getDayStr();
							for (let file of entries) {
								// 判断需要保留的日志
								let day = file.name.replace('.txt', '');
								if (parseInt(day) + parseInt(durationDay) < parseInt(now)) {
									console.log('需要删除的日志是', file.name);
									try {
										file.remove(
											function() {
												if (process.env.NODE_ENV === 'development') {
													console.error('删除日志成功', file.name);
												}
											},
											function(e) {
												if (process.env.NODE_ENV === 'development') {
													console.error('删除日志失败', file.name, e);
												}
											}
										);
									} catch (e) {
										if (process.env.NODE_ENV === 'development') {
											console.error('删除日志失败', file.name, e);
										}
									}
								} else {
									if (process.env.NODE_ENV === 'development') {
										console.log('保留的日志是', file.name);
									}
								}
							} // for
						} // if
						resolve();
					},
					function(e) {
						if (process.env.NODE_ENV === 'development') {
							console.log('读取文件失败:' + e.message);
						}

						resolve();
					}
				);
			},
			function(e) {
				if (process.env.NODE_ENV === 'development') {
					console.log('读取目录失败:' + e.message);
				}
				resolve();
			}
		);
	});
	// #endif
}

/**
 * 自定义TXT日志
 */
const logger = {
	/**
	 * @param {Object} msg 日志信息的字符串信息
	 */
	debug: function() {
		writeToTxt('DEBUG', ...arguments);
		//production
		if (process.env.NODE_ENV === 'development') {
			console.debug(...arguments);
		}
	},
	log: function() {
		writeToTxt('LOG', ...arguments);
		if (process.env.NODE_ENV === 'development') {
			console.log(...arguments);
		}
	},
	info: function() {
		writeToTxt('INFO', ...arguments);
		if (process.env.NODE_ENV === 'development') {
			console.info(...arguments);
		}
	},
	warn: function() {
		writeToTxt('WARN', ...arguments);
		if (process.env.NODE_ENV === 'development') {
			console.warn(...arguments);
		}
	},
	error: function() {
		writeToTxt('ERROR', ...arguments);
		if (process.env.NODE_ENV === 'development') {
			console.error(...arguments);
		}
	},
	/**
	 * @param {String} tag 日志信息的自定义信息
	 */
	tag: function(tag) {
		writeToTxt(tag, ...arguments);
		if (process.env.NODE_ENV === 'development') {
			console.log(...arguments);
		}
	},
	/**
	 * @param {Object} msg 日志信息的字符串信息
	 */
	network: function() {
		writeToTxt('NETWORK', ...arguments);
		if (process.env.NODE_ENV === 'development') {
			console.log(...arguments);
		}
	},
	/**
	 * @param {Object} msg 日志信息的字符串信息
	 */
	logIpExchange: function(msg) {
		writeToTxt('IpExchange', ...arguments);
		if (process.env.NODE_ENV === 'development') {
			console.log(...arguments);
		}
	},
	/**
	 * 压缩成zip,并返回路径
	 * @param {Object} callback
	 */
	zipLogDir,
	/**
	 * 删除多少${durationDay}天之前的日志文件
	 * @param {Object} durationDay 默认是10天
	 */
	removeFile,
	/**
	 * 主要使用方法。先移除
	 * @param {Object} callback
	 */
	removeFileAndZipLogDir(callback) {
		removeFile().then(() => {
			zipLogDir(callback);
		});
	},
};

export default logger;

使用

引入logger.js文件

//这里根据你自己路径来
import logger from '@/common/js/logger.js'

删除5天前的日志

logger.removeFile(5)

打印

logger.log('test')

查看生成log

我们这里使用的是hbx基座调试,这里的plus.io.PRIVATE_DOC对应的手机端文件管理器中Android/data/io.dcloud.HBuilder/apps/Hbuilder/doc/logs,其中doc/logs对应代码中的LOG_DIR变量。
在这里插入图片描述
在这里插入图片描述

到这里log已经成功存储到了手机中。

读取

我们保存log到本地之后,我们希望读取然后上传,依然使用的是plus.io.requestFileSystem这个API,参考上文的写法。遍历文件夹后可以在界面显示让用户手动上传,或者启动应用静默上传。

注意事项

日志本地存储只在APP平台生效,演示为Android平台,iOS平台未做测试。
优化:已针对评论区日志丢失问题做了并发队列控制。

尾巴

今天的文章就到这里了,希望能给大家帮助,如果喜欢我的文章,欢迎给我点赞,评论,关注,谢谢大家!

评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值