利用Node.js如何实现文件循环覆写


Posted in Javascript onApril 05, 2019

前言

这次编写Node.js项目的时候用到了日志模块,其中碰到了一个小问题。

这是一个定时执行可配置自动化任务的项目,所以输出信息会不断增加,也就意味着日志文件会随时间不断增大。如果对日志文件大小不加以控制,那么服务器的磁盘迟早会被撑满。所以限制文件大小是有必要的。

最理想的控制方式就是当文件大小超过限制时,清除最先记录的数据。类似一个FIFO的队列。

# 删除前面的数据
- 1 xxx
 ......
 100 abc
# 文件末尾追加数据
+ 101 xxxx

log4js的file rolling

一提到记录日志很多Node.js开发者肯定会找到log4js,先来看看log4js是怎么处理这个问题的。

log4js分为很多appenders(可以理解为记录日志的媒介),file rolling功能可以通过函数来进行配置。

file rolling功能有两种方式:日期和文件大小。

要控制文件大小,当然选择后者。

为了测试这个功能是否满足我们要求,写一段循环代码来写日志。

const log4js = require('log4js')
// 配置log4js
log4js.configure({
 appenders: {
 everything: {
 type: 'file',
 filename: 'a.log',
 maxLogSize: 1000,
 backups: 0
 },
 },
 categories: {
 default: {
 appenders: ['everything'],
 level: 'debug'
 }
 }
});
const log = log4js.getLogger();
for (let i = 0; i < 41; i++) {
 const str = i.toString().padStart(6, '000000');
 log.debug(str);
}

执行之后生成两个文件a.log和a.log.1。

其中a.log.1有20行数据,实际大小1kb,a.log只有1行数据。

虽然确实控制了文件大小,但是会带来两个问题:

  • 额外产生一个备份文件,总占用磁盘空间会超过文件限制。
  • 日志文件内容的大小是变动的,查询日志的时候很可能需要联合备份文件进行查询(比如上面的情况日志文件只有1行数据)。

推测log4js的实现逻辑可能是下面这样:

  • 检查日志文件是否达到限制大小,如果达到则删除备份文件,否则继续写入日志文件。
  • 重命名日志文件为备份文件。

这显然不能完全满足需求。

字符串替换?

如果要在内存中完成循环覆写操作就比较简单了,使用字符串或Buffer的即可完成。

  • 添加字符串/Buffer长度,如果超过大小则截取。
  • 写入并覆盖日志文件。

但是有一个很大的问题:占用内存。

比如限制文件大小为1GB,有10个日志文件同时写入,那么至少占用10GB内存空间!

内存可是比磁盘空间更宝贵的,如此明显的性能问题,显然也不是最优解决方式。

file roll

按照需求可以把实现步骤拆成两步:

  • 追加最新的数据到文件末尾。(Node.js的fs模块有相应函数)
  • 删除文件开头超出限制部分。(Node.js没有响应函数)

这两步不分先后顺序,但是Node.js没有提供API来删除文件开头部分,只提供了修改文件指定位置的函数。

既然无法删除文件开头部分内容,那么我们就换个思路,只保留文件末尾部分内容(不超出大小限制)。

什么?这不是一个意思么?

略有区别~

删除是在原有文件上进行的操作,而保留内容可以借助临时文件来进行操作。

所以思路变成:

  1. 创建一个临时文件,临时文件的内容来自于日志文件。
  2. 往临时文件中增加数据。
  3. 将临时文件中符合文件大小限制的内容,从后往前(采取偏移量的形式)进行读取并复制到日志文件进行覆盖。
  4. 为了不占用额外的磁盘空间,写操作完成后删除临时文件。

这样就不会出现像log4js一样日志文件内容不全的现象,也不会保留额外的临时文件。但是对IO的操作会增加~
对于写操作可以采取tail命令来实现,最终实现代码如下:

private write(name: string, buf?: Buffer | string) {
 // append buf to tmp file
 const tmpName = name.replace(/(.*\/)(.*$)/, '$1_\.$2\.tmp');
 if (!existsSync(tmpName)) {
 copyFileSync(name, tmpName);
 }
 buf && appendFileSync(tmpName, buf);
 // if busy, wait
 if (this.stream && this.stream.readable) {
 this.needUpdateLogFile[name] = true;
 } else {
 try {
  execSync(`tail -c ${limit} ${tmpName} > ${name}`);
  try {
  if (this.needUpdateLogFile[name]) {
   this.needUpdateLogFile[name] = false;
   this.write(name);
  } else {
   existsSync(tmpName) && unlinkSync(tmpName);
  }
  } catch (e) {
  console.error(e);
  }
 } catch (e) {
  console.error(e);
 }
 }
}

总结

完成这个功能有两点感悟:

  1. 量变引起质变。当数据量变大时,很多简单的处理方式就不可以用了,比如写文件,如果直接使用writeFile会占用大量内存甚至有可能内存都不够用。所以要通过合适的方式进行拆分,拆分过程中又会碰到各种问题,比如本文中截取文件内容的要求。
  2. 学会借力。君子性非异也善假于物也~当无法在单个点完成操作的时候可以借助外部条件来实现,比如在本文中使用临时文件来保存数据内容。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JQUBar 基于JQUERY的柱状图插件
Nov 23 Javascript
javascript字符串拼接的效率问题
Dec 25 Javascript
JavaScript String.replace函数参数实例说明
Jun 06 Javascript
实现网页页面跳转的几种方法(meta标签、js实现、php实现)
May 20 Javascript
深入理解JavaScript系列(31):设计模式之代理模式详解
Mar 03 Javascript
JS实现文字放大效果的方法
Mar 03 Javascript
AngularJS 过滤器的简单实例
Jul 27 Javascript
Angular动态添加、删除输入框并计算值实例代码
Mar 29 Javascript
ionic实现底部分享功能
May 11 Javascript
Vue 滚动行为的具体使用方法
Sep 13 Javascript
彻底搞懂JavaScript中的apply和call方法(必看)
Sep 18 Javascript
基于模板引擎Jade的应用(详解)
Dec 12 Javascript
详解JavaScript栈内存与堆内存
Apr 04 #Javascript
jQuery中实现text()的方法
Apr 04 #jQuery
基于 jQuery 实现键盘事件监听控件
Apr 04 #jQuery
详解微信图片防盗链“此图片来自微信公众平台 未经允许不得引用”的解决方案
Apr 04 #Javascript
基于Vue+elementUI实现动态表单的校验功能(根据条件动态切换校验格式)
Apr 04 #Javascript
vue学习笔记五:在vue项目里面使用引入公共方法详解
Apr 04 #Javascript
JavaScript多种页面刷新方法小结
Apr 04 #Javascript
You might like
德劲1103的维修打理经验
2021/03/02 无线电
增加反向链接的101个方法 站长推荐
2007/01/31 PHP
php仿QQ验证码的实例分析
2013/07/01 PHP
php一些错误处理的方法与技巧总结
2013/08/10 PHP
使用Zttp简化Guzzle 调用
2017/07/02 PHP
PHP array_shift()用法实例分析
2019/01/07 PHP
基于laravel where的高级使用方法
2019/10/10 PHP
JavaScript 嵌套函数指向this对象错误的解决方法
2010/03/15 Javascript
THREE.JS入门教程(2)着色器-上
2013/01/24 Javascript
javascript禁用键盘功能键让右击及其他键无效
2013/10/09 Javascript
jquery 循环显示div的示例代码
2013/10/18 Javascript
JS获取select的value和text值的简单实例
2014/02/26 Javascript
在jquery中的ajax方法怎样通过JSONP进行远程调用
2014/04/04 Javascript
JavaScript合并两个数组并去除重复项的方法
2015/06/13 Javascript
zepto中使用swipe.js制作轮播图附swipeUp,swipeDown不起效果问题
2015/08/27 Javascript
Javascript 详解封装from表单数据为json串进行ajax提交
2017/03/29 Javascript
利用webstrom调试Vue.js单页面程序的方法教程
2017/06/06 Javascript
解决vue的 v-for 循环中图片加载路径问题
2018/09/03 Javascript
webpack@v4升级踩坑(小结)
2018/10/08 Javascript
vue实现类似淘宝商品评价页面星级评价及上传多张图片功能
2018/10/29 Javascript
解决layui表格的表头不滚动的问题
2019/09/04 Javascript
[51:39]DOTA2-DPC中国联赛 正赛 Magma vs LBZS BO3 第二场 2月7日
2021/03/11 DOTA
编写Python脚本来实现最简单的FTP下载的教程
2015/05/04 Python
python实现二叉查找树实例代码
2018/02/08 Python
python中break、continue 、exit() 、pass终止循环的区别详解
2019/07/08 Python
Pytorch之保存读取模型实例
2019/12/30 Python
Python破解BiliBili滑块验证码的思路详解(完美避开人机识别)
2020/02/17 Python
纯css3制作的火影忍者写轮眼开眼至轮回眼及进化过程实例
2014/11/11 HTML / CSS
BIBLOO波兰:捷克的一家在线服装店
2018/03/09 全球购物
自荐信要包含哪些内容
2013/11/06 职场文书
学生逃课万能检讨书2000字
2015/02/17 职场文书
小学生红领巾广播稿
2015/08/19 职场文书
教师师德承诺书2016
2016/03/25 职场文书
求职信:会计求职的写作技巧
2019/04/24 职场文书
Java spring定时任务详解
2021/10/05 Java/Android
Win11无法访问设备和打印机 如何解决页面空白
2022/04/09 数码科技