之前,应用系统的数据库会有一张日志表,用于保存系统内部管理员的操作记录,用作日后审计。所以前端使用了jqtable这个jquery插件,用于日志的查询、分页显示、排序显示。由于日志都是记录在数据库表中,那么日志的格式就可以保证一致,而且使用SQL有可以做到对日志的关键字检索、排序和分页,所以后台只需要将数据查询出来封装到对象中,然后以JSON格式返给前端显示即可。
现在,新增了这样一个需求:由外部的一个系统通过syslog将日志文件保存到某一目录下(如:/var/log/iplogs/),当前的日志文件为iplog.log,由于日志量特别大,所以外部系统还会对日志文件进行监控,一旦达到某一大小,就会将该日志文件备份为iplog1.log,iplog2.log...,然后清空该日志文件。我这边需要做的就是将日志文件中需要符合约定的日志过滤出来,在前端做显示,而且也需要支持关键字检索、分页和排序,以及按格式显示。
难题出现了:
1. 如何快速的读取文件:日志是动态增长的,如何避免重复读取
2. 如果过滤日志:哪些日志是需要显示的
3. 对符合条件的日志的处理:对待显示的日志如何拆分为K-V的格式(类似数据库的列)
4. 检索、排序、分页的处理:在前端仍要支持这些操作
5. 日志如何清除:
分析第一点,首先需要一个可以快速读取文件的工具类,该工具类可以记录到已经读取到哪一行,下一次读取可以略过前面的内容,接着读取新增的日志内容。正好前几天,我的一个大学同学和我聊他工作时参加的一个比赛,正好做的是对动态增加的日志进行实时显示。而对日志文件的快速读取分析也是专门下功夫设计的。我们两个对了一下,非常符合我的业务需求,就用它来实现文件的读取。他是这么和我说的:最先想到的是java.io.RandomAccessFile这个类,该类可以实现对文件的随机访问,但是效率很低,读取文件耗时较大,不满足当时的业务需求。而java.io.BufferedReader是读取文件最快的类,但是它又无法支持随机访问。于是他们想到了nio,使用nio中的类对文件进行读取,有参考了RandomAccessFile和BufferedReader的源码,自己封装了一个读取文件的类,该类可以实现对文件的快速读取,而且可以按行读,并且记录读取到文件的的位置,以便下一次快速的定位。有了这个法宝,我下面的工作才可以顺利进行。所以平时和同行多交流,真的对扩展思维,开阔眼界大有帮助。
对于第二个问题,我们两边对日志的格式做了约定。我使用了ArrayList<String>保存符合显示条件的日志。这里需要注意初始化ArrayList的大小,需要根据实际场景来定,避免太小引起不必要扩容,进而影响效率。过滤的时候使用String的indexOf这个方法效率更好。
第三个问题,使用正则表达式将Key-Value按组拆分,然后保存到HashMap中,以便后边封装成对象。过程中也用到了substring这个方法,一定要注意避免引起内存泄漏(例如使用new String(str.subString(0,15))来避免)。
第四个问题,就是自己写了一个规则,应为日志是按时间倒排的,而ArrayList中保存的日志肯定是按文件中出现的顺序排列的,所以分页的时候,就需要对ArrayList从后开始取20条(假设每页显示20条),然后对这20条数据进行倒排即可。过滤其实就是遍历ArrayList,内存中顺序读取,然后过滤,对于几万条数据还是相当快的。
第五个问题就是清除日志,因为这种日志量很大,而且也不是特别重要,一般就是查看当时的部分日志,所以需要清理日志,以方便查看。我需要做的就是将日志文件删掉重建,而且让读取文件的工具类重新加载文件。这里还遇到一个小问题,就是日志文件重建后,syslog服务就无法继续往文件中写日志了。所以,在清除日志后, 还需要重新启动下syslog的服务。
在后期的调试中发现,如果syslog自动将日志备份后,我这边是没有办法得知的,这样的话,那个读取文件的工具类就很有可能出错,所以我在系统全局保存了/var/log/iplogs/下的文件个数。在每次读取文件时,都检查一下文件个数,如果检查到文件数量变化,那么工具类就重新读取文件,设置读取位置为0,0。
通过以上的处理,实现了和从数据库读取日志文件一样的效果。