Web 에서 Linux/Unix 의 'tail -f logfile' 과 같이 보려고 인터넷을 찾아보다 마음에 드는게 없어서 직접 만들게 되었다.
Server 에서 tail 명령어를 사용할 때 grep 을 많이 사용해서 해당 기능도 추가했다.
모습은 다음과 같다.
사용방법
1. tail -f {} 부분에 실시간으로 확인 할 로그파일을 선택
2. grep {} 부분에 문자열 입력 시 해당 문자열이 매칭되는 경우에만 출력
3. grep -v {} 부분에 문자열 입력 시 해당 문자열이 매칭되는 경우에는 출력 제외
(grep 에 넣을 문자열은 정규표현식 지원된다.)
Source
https://github.com/1004lucifer/JSP_tail
<%-- Created by IntelliJ IDEA. User: 1004lucifer Date: 2015. 3. 5. Time: 오후 9:51 --%> <%@ page import="java.io.RandomAccessFile" %> <%@ page import="java.io.FileNotFoundException" %> <%@ page import="java.net.URLEncoder" %> <%@ page import="java.io.InputStreamReader" %> <%@ page import="java.io.BufferedReader" %> <%@ page import="java.util.List" %> <%@ page import="java.util.ArrayList" %> <%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %> <% String logPath = "/Users/1004lucifer/test/jsp/tail/"; String fileName = request.getParameter("log_filename") == null ? "" : request.getParameter("log_filename"); if ("".equals(fileName.trim()) == false) { fileName = logPath + fileName.trim().replaceAll("\\.\\.", ""); long preEndPoint = request.getParameter("preEndPoint") == null ? 0 : Long.parseLong(request.getParameter("preEndPoint") + ""); StringBuilder log = new StringBuilder(); long startPoint = 0; long endPoint = 0; RandomAccessFile file = null; try { file = new RandomAccessFile(fileName, "r"); endPoint = file.length(); startPoint = preEndPoint > 0 ? preEndPoint : endPoint < 2000 ? 0 : endPoint - 2000; file.seek(startPoint); String str; while ((str = file.readLine()) != null) { log.append(str); log.append("\n"); endPoint = file.getFilePointer(); file.seek(endPoint); } } catch (FileNotFoundException fnfe) { log.append("File does not exist."); fnfe.printStackTrace(); } catch (Exception e) { log.append("Sorry. An error has occurred."); e.printStackTrace(); } finally { try {file.close();} catch (Exception e) {} } out.print("{\"endPoint\":\"" + endPoint + "\", \"log\":\"" + URLEncoder.encode(new String(log.toString().getBytes("ISO-8859-1"),"UTF-8"), "UTF-8").replaceAll("\\+", "%20") + "\"}"); } else { List<String> fileList = new ArrayList<String>(); String line = null; BufferedReader br = null; Process ps = null; try { Runtime rt = Runtime.getRuntime(); ps = rt.exec(new String[]{"/bin/sh", "-c", "find "+ logPath + " -maxdepth 1 -type f -exec basename {} \\; | sort"}); br = new BufferedReader(new InputStreamReader(ps.getInputStream())); while( (line = br.readLine()) != null ) { fileList.add(line); } } catch (Exception e) { e.printStackTrace(); } finally { try { br.close(); } catch(Exception e) {} } %> <html> <head> <title></title> <script src="//code.jquery.com/jquery-1.11.2.min.js"></script> <style type="text/css"> * { margin: 0; padding: 0; } #header { position: fixed; top: 0; left: 50px; width: 100%; height: 10%; } #console { position: fixed; bottom: 0; width: 100%; height: 90%; background-color: black; color:white; font-size: 15px; } #runningFlag { color: red; } </style> <script type="text/javascript"> var endPoint = 0; var tailFlag = false; var fileName; var consoleLog; var grep; var grepV; var pattern; var patternV; var runningFlag; var match; $(document).ready(function() { consoleLog = $('#console'); runningFlag = $('#runningFlag'); function startTail() { runningFlag.html('Running'); fileName = $('#fileName').val(); grep = $.trim($('#grep').val()); grepV = $.trim($('#grepV').val()); pattern = new RegExp('.*'+grep+'.*\\n', 'g'); patternV = new RegExp('.*'+grepV+'.*\\n', 'g'); function requestLog() { if (tailFlag) { $.ajax({ type : 'POST', url : 'tail.jsp', // #### Caution: The name of the source file dataType : 'json', data : { 'log_filename' : fileName, 'preEndPoint' : endPoint }, success : function(data) { endPoint = data.endPoint == false ? 0 : data.endPoint; logdata = decodeURIComponent(data.log); if (grep != false) { match = logdata.match(pattern); logdata = match ? match.join('') : ''; } if (grepV != false) { logdata = logdata.replace(patternV, ''); } consoleLog.val(consoleLog.val() + logdata); consoleLog.scrollTop(consoleLog.prop('scrollHeight')); setTimeout(requestLog, 1000); } }); } } requestLog(); } $('#startTail').on('click', function() {tailFlag = true; startTail();}); $('#stopTail').on('click', function() { tailFlag = false; runningFlag.html('Stop'); }); $('#fileName').change(function() { tailFlag = false; endPoint = 0; runningFlag.html('Stop'); }); }); </script> </head> <body> <div id="header"> <h2>Log Tail</h2> tail -f <select id="fileName"> <% for (String file : fileList) { %> <option value="<%=file%>"><%=file%></option> <% } %> </select> | grep <input id="grep" type="text" /> | grep -v <input id="grepV" type="text" /> <br/> <input id="startTail" type="button" value="startTail" /> <input id="stopTail" type="button" value="stopTail" /> <span id="runningFlag">Stop</span><br/> </div> <textarea id="console"></textarea> </body> </html> <% } %>
PS.
다음의 기능을 지원한다.
1. UTF-8 인코딩으로 다국어 지원
2. 특정 디렉토리의 파일을 읽어온 후 소팅해서 Select 할 수 있게 보여준다.
3. 상위 디렉토리에 접근 할 수 없도록 ".." 문자열 치환
4. 큰 용량의 파일을 읽을 수 있도록 RandomAccessFile 사용 (2GB 이상의 로그 파일 테스트완료)
5. 페이지를 닫으면 더 이상 서버에 정보를 요청하지 않는다.
======================
2015.03.10 수정
1. 퍼포먼스 최적화
2. 서버요청 최소화
======================
2015.03.20 수정
1. grep 버그 수정
질문이 있어서 댓글 남깁니다.
답글삭제String str;를 try 안에 선언하셔서
str cannot be resolved 이런식으로
out.print 할때 str를 찾을 수 없다고 에러가 나는데
str을 try밖에 빼도 안되고
out.print를 try안에 넣으면 에러는 안나오는데
로그가 안뜹니다.
뭐가 문제인지 알려주실 수 있나요? ㅠㅠ
IntelliJ 에서 확인해보니 위 소스에서 따로 에러가 검출되지 않습니다.
삭제String str;
의 str이 try 밖에서 사용되지 않기에 문법적으로도 문제는 없습니다.
아마 IDE의 문제일수도 있지 않을까 의심이 되는데..
혹시 컴파일이 안되서 jsp페이지가 보여지지 않는 상황인가요??
안녕하세요. 혹시 해당 기능 다른 서버에 ssh 접속해서 이용할수있게하는방법도 있나요
답글삭제제가 만든 기능에선 그런 기능은 만들지 않았어요.
삭제그런 기능은 보안적으로 문제가 되지 않을까 싶네요.
안녕하세요. 제가 하고 싶은건 제가 접속 가능한 서버에서 어떠한 log안에 있는 내용을 편하게 보는 프로그램을 만들어보려고합니다.
삭제ex) tera term으로 들어가서 보던 작업들을 좀더 편하게 하려고요.
기본적으로 log를 vi로 들어가서 / 로 찾아서 하면 글자가 너무 많고 혼동이 와서 해당 글자가있는 줄만 출력해서 보게 만들려는게 최종적입니다.
해당 기능은 여기서 구현되어있지 않고 어느정도만 구현되어있는것으로 보입니다.
빠른 답변주셔서 너무감사합니다!
네~ 단순히 vi로 열어서 기존의 로그를 확인하는 용도가 아닌 새로 발생하는 로그를 지속적으로 출력하는
삭제tail 기능을 구현한거라 말씀주신건 안되겠네요. ㅎㅎ