背景:我们公司每周仅有两个时间点要更新线上程序,所以每到那两个时间点,运维的人就多得不得了。在更新程序后,我们需要验证,这时候如果新功能报错,我们只能通过运维的人去查看控制台日志信息,由于人多,找人调出日志信息就成为一件很麻烦的事。再者,查不到错误日志输出,也不利于我们开发调试程序。基于此类原因,我做了一个页面,用来读取控制台日志输出。
考虑到日志文件可能会很大,如果一次读完,对于内存开销和响应速度都百害而无一利,所以我借助于java.io.RandomAccessFile类,这个类可以通过控制指针位置,来读取任意位置的文件内容。
/** * 分页读取weblogic控制台日志 * @param logFileName 日志文件名字 * @param curPage 当前读取内容的页码,文件从末尾向开始读取,末尾所在页码为1 * @param row 每次读取内容行数 * @param charSet 读取内容的字符编码 * @param session * @param model * @return */ @RequestMapping("viewConsoleLog") @ResponseBody public String viewConsoleLog(@RequestParam(required = false, defaultValue = "nohup.out") String logFileName, @RequestParam(required = false, defaultValue = "1") int curPage, @RequestParam(required = false, defaultValue = "500") int row, @RequestParam(required = false, defaultValue = "GBK") String charSet, HttpSession session, Model model) { String rootPath = session.getServletContext().getRealPath("/"); Listlist = new ArrayList (); RandomAccessFile logFile = null; try { File rootDir = new File(rootPath); File binDir = new File(rootDir.getParentFile(), "bin"); //以只读方式打开日志文件 logFile = new RandomAccessFile(new File(binDir, logFileName), "r"); //计算要读取的行号,从末行开始读取,所以末行行号为0 int startRow = (curPage - 1) * row; int endRow = curPage * row - 1; //当前行号 int curRow = 0; //获取文件大小 long len = logFile.length(); //读取行 String line = null; if(len > 0) { //定位至文件尾部 long p = len; while(p-- > 0) { logFile.seek(p);//定位指针位置 if(curRow > endRow) { //如果已读行数达到指定行数,则不再读取 break; } if(logFile.readByte() == '\n') { if(curRow >= startRow && curRow <= endRow) { line = logFile.readLine();//读取到换行符,这里的换行符是上一行的换行符,所以这里永远不会打印第一行的内容 line = (line == null) ? "" : new String(line.getBytes("ISO-8859-1"), charSet);//如果是换行符,并且在指定行号内,就读取该行;如果是空行,这打印空行 list.add(line); } curRow++;//如果不在指定行号内,则跳过读取 } } } //读取文件首行 curRow++; if(curRow >= startRow && curRow <= endRow) { logFile.seek(0);//定位指针至文件开头 line = logFile.readLine();//读取到换行符,这里的换行符是上一行的换行符,所以这里永远不会打印第一行的内容 line = (line == null) ? "" : new String(line.getBytes("ISO-8859-1"), charSet);//如果是换行符,并且在指定行号内,就读取该行;如果是空行,这打印空行 list.add(line); } if(list.size() == 0) { //没有读取到任何数据,表示已经加载过了所有内容 model.addAttribute("isOver", true); } } catch (FileNotFoundException e) { e.printStackTrace(); model.addAttribute("msg", "找不到日志文件: " + logFileName); } catch (IOException e) { e.printStackTrace(); model.addAttribute("msg", "读取日志文件失败: " + logFileName); } finally { try { if(logFile != null) logFile.close(); } catch (IOException e) { e.printStackTrace(); } } model.addAttribute("content", list); model.addAttribute("curPage", curPage); return new Gson().toJson(model); }
这是一个分页查询日志信息的功能,它从日志文件末尾开始反向读取,后台是基于spring mvc框架开发的,该方法通过ajax请求,返回一个json对象。
json数据已经传到了前台,那就简单了,这里就不展示前台代码了。
总结:
1、构造方法:RandomAccessFile有两个构造方法
(1) RandomAccessFile(File file, String mode)
(2) RandomAccessFile(String filepath, String mode)
mode参数表示打开文件方式,其值及含义如下:
值 | 含意 |
---|---|
"r" | 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException 。 |
"rw" | 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 |
"rws" | 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 |
"rwd" | 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。 |
2、文件长度属性:同java.io.File对象一样的length
3、指针定位方法:
public void seek(long pos) throws IOException {} 参数: pos - 从文件开头以字节为单位测量的偏移量位置,在该位置设置文件指针。
(1) pos位于 [0, length] 之间,超出范围则报错;
(2) 一般在读取文件时,不要将pos指向length,因为pos指向length表示文件已读完,这时再调用read方法则会抛出异常,如果是反向读取文件,可以设置pos=length-1,这表示下一次read得到的事最后一个字符;
(3) 定义了多种read方法,用于读取不同类型的数据,具体请查看API
4、读取文件时要注意指针位置:
(1) 指针自动移动:每次调用read的时候,指针pos会自动移动到read的数据之后,这就表示,如果需要重复read某一段数据,那么每次read前都要手动调用一次seek(pos)方法;
(2) 反向读取需注意:在反向读取文件时,我这里使用了 if(logFile.readByte() == '\n') {} 来判断是否读取到了换行符,由于(1)的关系,在执行这个if之后,指针向后移动了一个字节长度,所以在if块中,我们可以直接调用readLine来获取下一行的数据,也正因为如此,我们在这个if块中,只能获取到前面存在换行符'\n'的数据,这就表明了,这里面永远不可能获取到第一行的数据(因为第一行前面没有行了,也就没有换行符'\n'了),所以这里对首行数据进行单独读取。
(3) 空行处理:line = logFile.readLine(); 如果读取了空行,则这里 line = null; (个人认为这是不对,原因很简单:既然是空行,就表示存在这个行,只是没有数据而已,所以个人认为应该是 "" 而不是 null);所以在这里不要直接使用line,小心报NullPointException哦;
(4) 字符集问题:readLine()存在中文乱码问题;我没有深入研究过是否能直接read中文,这里只是对read结果做了简单处理,如果你有更好的中文乱码解决方案,也请你能留言告诉我。
转载请注明出处: