背景:咱們公司每週僅有兩個時間點要更新線上程序,因此每到那兩個時間點,運維的人就多得不得了。在更新程序後,咱們須要驗證,這時候若是新功能報錯,咱們只能經過運維的人去查看控制檯日誌信息,因爲人多,找人調出日誌信息就成爲一件很麻煩的事。再者,查不到錯誤日誌輸出,也不利於咱們開發調試程序。基於此類緣由,我作了一個頁面,用來讀取控制檯日誌輸出。html
考慮到日誌文件可能會很大,若是一次讀完,對於內存開銷和響應速度都百害而無一利,因此我藉助於java.io.RandomAccessFile類,這個類能夠經過控制指針位置,來讀取任意位置的文件內容。java
/** * 分頁讀取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對象。web
json數據已經傳到了前臺,那就簡單了,這裏就不展現前臺代碼了。ajax
總結:spring
一、構造方法:RandomAccessFile有兩個構造方法json
(1) RandomAccessFile(File file, String mode)
session
(2) RandomAccessFile(String filepath, String mode)
mvc
mode參數表示打開文件方式,其值及含義以下:app
值框架 |
含意 |
---|---|
"r" | 以只讀方式打開。調用結果對象的任何 write 方法都將致使拋出 IOException 。 |
"rw" | 打開以便讀取和寫入。若是該文件尚不存在,則嘗試建立該文件。 |
"rws" | 打開以便讀取和寫入,對於 "rw",還要求對文件的內容或元數據的每一個更新都同步寫入到底層存儲設備。 |
"rwd" | 打開以便讀取和寫入,對於 "rw",還要求對文件內容的每一個更新都同步寫入到底層存儲設備。 |
二、文件長度屬性:同java.io.File對象同樣的length
三、指針定位方法:
public void seek(long pos) throws IOException {}
參數:pos
pos- 從文件開頭以字節爲單位測量的偏移量位置,在該位置設置文件指針。
(1) pos位於 [0, length] 之間,超出範圍則報錯;
(2) 通常在讀取文件時,不要將pos指向length,由於pos指向length表示文件已讀完,這時再調用read方法則會拋出異常,若是是反向讀取文件,能夠設置pos=length-1,這表示下一次read獲得的事最後一個字符;
(3) 定義了多種read方法,用於讀取不一樣類型的數據,具體請查看API
四、讀取文件時要注意指針位置:
(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結果作了簡單處理,若是你有更好的中文亂碼解決方案,也請你能留言告訴我。