背景:我們公司每周僅有兩個時間點要更新線上程序,所以每到那兩個時間點,運維的人就多得不得了。在更新程序后,我們需要驗證,這時候如果新功能報錯,我們只能通過運維的人去查看控制台日志信息,由於人多,找人調出日志信息就成為一件很麻煩的事。再者,查不到錯誤日志輸出,也不利於我們開發調試程序。基於此類原因,我做了一個頁面,用來讀取控制台日志輸出。
考慮到日志文件可能會很大,如果一次讀完,對於內存開銷和響應速度都百害而無一利,所以我借助於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("/"); List<String> list = new ArrayList<String>(); 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結果做了簡單處理,如果你有更好的中文亂碼解決方案,也請你能留言告訴我。
轉載請注明出處:http://www.cnblogs.com/Sunw/p/3801145.html
