Java 로그 필터로 HTTP 요청 구문 분석
javax.servlet.Filter구현하여 HTTP 요청 내용을 구문 분석하고 로그를 출력하는 편리한필터수업을 소개합니다.
이 로그 필터는, 로그 출력에 「java.util.logging.Logger」를 사용하고 있어, 로그 레벨을 변경하는 것으로 출력하는 정보를 변경할 수 있습니다.
javax.servlet.Filter란?
웹 어플리케이션 구축에 있어서, 메인이 되는 업무 처리 외에, 그 전후로 공통된 부차적인 처리를 실시할 필요가 있습니다.
예를 들면, 클라이언트의 인증이나, 권한 체크, 로깅등입니다.
이러한 부차적인 처리를 각각의 리소스로 코딩하면, 소프트웨어의 보수성을 방해하는 큰 원인이 되어, 메인터넌스의 곤란한 어플리케이션이 되어 버립니다.
javax.servlet.Filter 를 구현하면, 리퀘스가 서블릿 클래스에 건네지기 전에, 이 부차적인 처리를, 공통적으로 실행시킬 수가 있습니다.
또 필터의 설정은 web.xml로 할 수 있으므로, 소스 코드를 변경하지 않고, 필터를 추가하거나 삭제하는 것이 유연하게 할 수 있습니다.
로그 필터를 사용해보기
HTTP 요청에 필터를 통해 HTTP 요청을 구문 분석하는 로그 필터를 실제로 사용해 봅시다.
이번에는Tomcat처음부터 examples 응용 프로그램을 사용합니다.
다음 설정을 수행하면 이 필터를 즉시 실행할 수 있습니다.
LogFilter LogFilter logging.Level FINE LogFilter /*
※Struts 프레임워크를 사용했을 경우의 url-pattern은 이하가 됩니다.
*.do
Sessions Example 화면을 실행한 로그 출력 결과를 확인해 보십시오.
출력할 로그 정보와 로그 레벨
● 로그 출력 내용
로그 정보 | 로그 레벨 |
---|---|
쿠키 정보 | FINE |
HTTP 헤더 정보 | FINE |
HTTP 기타 정보 | FINE |
HTTP 요청 매개변수 | CONFIG |
request 범위의 객체 | CONFIG |
session 범위의 객체 | CONFIG |
요청 전후의 메모리 사용량 | CONFIG |
화면 전환 정보 | INFO |
로그 레벨은 다음과 같이 구분하면 좋을 것입니다.
- FINE ... 가장 상세한 로그를 출력합니다. HTTP 요청을 자세히 파싱하려는 경우 설정합니다.
- CONFIG・・・약간의 상세한 로그를 출력합니다. 개발 기간 동안이 수준으로 설정하는 것이 좋습니다.
- INFO ···· 화면 천이 정보만을 출력합니다.
● 로그 레벨 변경 방법
초기화 매개변수 logging.Level을 설정하여 로그의 출력 레벨을 변경할 수 있습니다.
예:
logging.Level INFO
소스 코드
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* javax.servlet.Filter를 구현하여 HTTP 요청의 내용을 구문 분석하고 로그를 출력합니다.
* Filter 클래스.
*/
public class LogFilter implements Filter {
private static Logger logger =
Logger.getLogger(LogFilter.class.getName());
private static final String LINE_SEPA =
System.getProperty(“line.separator”);
private static final String NEXT_PAGE = “LogFilter.NEXT_PAGE”;
/**
* 이 로그 필터를 초기화합니다.
* @param mapping
*/
public void init (FilterConfig mapping) {
String str = mapping.getInitParameter("logging.Level");
System.out.println(“로그 수준을”+str+”로 설정합니다.”);
Level level = null;
try {
level = Level.parse(str);
} catch (Exception e) {
e.printStackTrace();
level = Level.INFO;
}
LogManager.getLogManager().reset();
Handler handler = new CustomConsoleHandler();
handler.setFormatter(new CustomFormatter());
handler.setLevel (level);
logger.setLevel(level);
logger.getParent().addHandler(handler);
}
/**
* 로그를 출력하는 필터입니다.
* @param request 처리중인 HTTP 요청
* @param response 생성 중 HTTP 응답
* @param 체인
*/
public void doFilter (ServletRequest _request, ServletResponse _response,
FilterChain 체인) throws IOException, ServletException {
// ———————————————————— 『 전처리 』
HttpServletRequest request = (HttpServletRequest) _request;
HttpServletResponse response = (HttpServletResponse) _response;
if (logger.isLoggable(Level.CONFIG)) {
logger.config (
“============ Request Start !! ”
+”Thread ID:”
+ Thread.currentThread().hashCode()
+ ” ========================================================”);
}
// 메모리 사용량
String actionMemory = null;
if (logger.isLoggable(Level.CONFIG)) {
actionMemory = getMemoryInfo(” ”
+ new Time(System.currentTimeMillis()) + "요청[이전]");
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("쿠키 정보" + getCookieInfo(request));
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("HTTP 헤더 정보" + getHeadersInfo(request));
}
if (logger.isLoggable(Level.FINE)) {
로거
.fine(“HTTP 기타 정보” + getRequestOtherInfo(request));
}
if (logger.isLoggable(Level.CONFIG)) {
String reqlog = getRequestParametersInfo(request);
logger.config(“HTTP 요청 파라미터” + reqlog);
}
if (logger.isLoggable(Level.CONFIG)) {
logger.config(“request 범위의 객체”
+ getRequestAttributeInfo(request));
}
if (logger.isLoggable(Level.CONFIG)) {
String sessionlog = getSessionInfo(request,true);
logger.config ( "session 범위의 객체 (요청 처리 전)"
+ sessionlog);
}
// 다음 필터 호출
chain.doFilter (request, response);
// ———————————————————— 『후처리』
if (logger.isLoggable(Level.CONFIG)) {
String sessionlog = getSessionInfo(request,false);
logger.config(“session 범위의 객체(요청 처리 후)”)
+ sessionlog);
}
// 메모리 사용량
if (logger.isLoggable(Level.CONFIG)) {
actionMemory = "요청 전후의 메모리 사용량"+LINE_SEPA
+actionMemory +LINE_SEPA
+ getMemoryInfo(” ” + new Time(System.currentTimeMillis())
+ "요청 [후]");
logger.config(actionMemory+LINE_SEPA);
}
// 화면 전환 정보
if (logger.isLoggable(Level.INFO)) {
String nextPage = (String) request.getAttribute(NEXT_PAGE);
if (nextPage == null || nextPage.length() == 0) {
nextPage = request.getRequestURI();
}
logger.info(“NEXT_PAGE=[” + nextPage + “], ”
+ “IP_ADDRESS=[” + request.getRemoteAddr() + “], ”
+ “SESSION_ID=[” + request.getSession().getId() + “], ”
+ “USER-AGENT=[” + request.getHeader(“user-agent”) + “]”);
}
if (logger.isLoggable(Level.CONFIG)) {
logger.config (
“============ Request End !! ”
+”Thread ID:”+ Thread.currentThread().hashCode()
+ ” =========================================================”
+LINE_SEPA+LINE_SEPA);
}
}
/**
*
*/
public void destroy() {
}
// ----------- 이하 프라이빗 메소드
private static String getMemoryInfo (String message) {
DecimalFormat dFromat = new DecimalFormat(“#,###KB”);
long free = Runtime.getRuntime().freeMemory() / 1024;
long total = Runtime.getRuntime().totalMemory() / 1024;
long max = Runtime.getRuntime().maxMemory() / 1024;
long used = total – free;
String msg = message + ” : ” + “총 =” + dFromat.format(total) + “, ”
+ “사용량 =” + dFromat.format(used) + ” (” + (used * 100 / total)
+ “%), 사용 가능 최대 =” + dFromat.format(max);
return msg;
}
/**
* 모든 요청 헤더를 로그에 출력합니다.
*/
private static String getHeadersInfo (HttpServletRequest request) {
StringBuffer buff = new StringBuffer(LINE_SEPA);
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
buff.append(” “);
buff.append(headerName);
buff.append(“=”);
buff.append(request.getHeader(headerName));
buff.append(LINE_SEPA);
}
return buff.toString();
}
private static String getCookieInfo (HttpServletRequest request) {
StringBuffer buff = new StringBuffer();
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return “”;
}
for (int i = 0; i < cookies.length; i++) {
buff.append(“\n — Cookie[” + i + “] —\n”);
buff.append(” “);
buff.append(cookies[i].getName());
buff.append(“=”);
buff.append(cookies[i].getValue());
buff.append(LINE_SEPA);
buff.append(” “);
buff.append(“getVersion()”);
buff.append(“=”);
buff.append(cookies[i].getVersion());
buff.append(LINE_SEPA);
buff.append(” “);
buff.append(“getComment()”);
buff.append(“=”);
buff.append(cookies[i].getComment());
buff.append(LINE_SEPA);
buff.append(” “);
buff.append(“getDomain()”);
buff.append(“=”);
buff.append(cookies[i].getDomain());
buff.append(LINE_SEPA);
buff.append(” “);
buff.append(“getMaxAge()”);
buff.append(“=”);
buff.append(cookies[i].getMaxAge());
buff.append(LINE_SEPA);
buff.append(” “);
buff.append(“getPath()”);
buff.append(“=”);
buff.append(cookies[i].getPath());
buff.append(LINE_SEPA);
buff.append(” “);
buff.append(“getSecure()”);
buff.append(“=”);
buff.append(cookies[i].getSecure());
buff.append(LINE_SEPA);
}
return buff.toString();
}
private static String getRequestParametersInfo (HttpServletRequest request) {
StringBuffer buff = new StringBuffer(LINE_SEPA);
Map map = convertRequest(request);
TreeMap trr = new TreeMap(map);
Iterator itr = trr.keySet().iterator();
while (itr.hasNext()) {
String key = (String) itr.next();
buff.append(” “);
buff.append(key);
buff.append(“=”);
Object value = map.get(key);
String[] values = (String[]) value;
if (values.length == 1) {
buff.append(values[0]);
} else {
// String 배열은 변환한다
String strValue = stratum(values);
buff.append(strValue);
}
buff.append(LINE_SEPA);
}
return buff.toString();
}
private static String getRequestAttributeInfo (HttpServletRequest request) {
StringBuffer buff = new StringBuffer(LINE_SEPA);
Enumeration e = request.getAttributeNames();
while (e.hasMoreElements()) {
String name = (String) e.nextElement();
buff.append(” name=” + name + “, attributeClass= ”
+ request.getAttribute(name).getClass().getName()
+ “, toString() = ” + request.getAttribute(name)
+LINE_SEPA);
}
return buff.toString();
}
private static String getRequestOtherInfo (HttpServletRequest request) {
StringBuffer buff = new StringBuffer();
buff.append(LINE_SEPA);
buff.append(” getCharacterEncoding()=”);
buff.append(request.getCharacterEncoding());
buff.append(LINE_SEPA);
buff.append(” getContentLength()=”);
buff.append(request.getContentLength());
buff.append(LINE_SEPA);
buff.append(” getContentType()=”);
buff.append(request.getContentType());
buff.append(LINE_SEPA);
buff.append(” getLocale()=”);
buff.append(request.getLocale());
buff.append(LINE_SEPA);
buff.append(” getProtocol()=”);
buff.append(request.getProtocol());
buff.append(LINE_SEPA);
buff.append(” getRemoteAddr()=”);
buff.append(request.getRemoteAddr());
buff.append(LINE_SEPA);
buff.append(” getRemoteHost()=”);
buff.append(request.getRemoteHost());
buff.append(LINE_SEPA);
buff.append(” getScheme()=”);
buff.append(request.getScheme());
buff.append(LINE_SEPA);
buff.append(” getServerName()=”);
buff.append(request.getServerName());
buff.append(LINE_SEPA);
buff.append(” getServerPort()=”);
buff.append(request.getServerPort());
buff.append(LINE_SEPA);
buff.append(” isSecure()=”);
buff.append(request.isSecure());
buff.append(LINE_SEPA);
buff.append(” getAuthType()=”);
buff.append(request.getAuthType());
buff.append(LINE_SEPA);
buff.append(” getContextPath()=”);
buff.append(request.getContextPath());
buff.append(LINE_SEPA);
buff.append(” getMethod()=”);
buff.append(request.getMethod());
buff.append(LINE_SEPA);
buff.append(” getPathInfo()=”);
buff.append(request.getPathInfo());
buff.append(LINE_SEPA);
buff.append(” getPathTranslated()=”);
buff.append(request.getPathTranslated());
buff.append(LINE_SEPA);
buff.append(” getQueryString()=”);
buff.append(request.getQueryString());
buff.append(LINE_SEPA);
buff.append(” getRemoteUser()=”);
buff.append(request.getRemoteUser());
buff.append(LINE_SEPA);
buff.append(” getRequestedSessionId()=”);
buff.append(request.getRequestedSessionId());
buff.append(LINE_SEPA);
buff.append(” getRequestURI()=”);
buff.append(request.getRequestURI());
buff.append(LINE_SEPA);
buff.append(” getServletPath()=”);
buff.append(request.getServletPath());
buff.append(LINE_SEPA);
buff.append(” getUserPrincipal()=”);
buff.append(request.getUserPrincipal());
buff.append(LINE_SEPA);
buff.append(” isRequestedSessionIdFromCookie()=”);
buff.append(request.isRequestedSessionIdFromCookie());
buff.append(LINE_SEPA);
buff.append(” isRequestedSessionIdFromURL()=”);
buff.append(request.isRequestedSessionIdFromURL());
buff.append(LINE_SEPA);
buff.append(” isRequestedSessionIdValid()=”);
buff.append(request.isRequestedSessionIdValid());
buff.append(LINE_SEPA);
return buff.toString();
}
private static String getSessionInfo (HttpServletRequest request,boolean before) {
HttpSession session = request.getSession();
StringBuffer buff = new StringBuffer();
buff.append(LINE_SEPA);
if (before) {
buff.append(” session.isNew() = ” + session.isNew());
buff.append(LINE_SEPA);
buff.append(” session.getId() = ” + session.getId());
buff.append(LINE_SEPA);
}
Enumeration e = session.getAttributeNames();
while (e.hasMoreElements()) {
String sessionName = (String) e.nextElement();
String sessionClassName = session.getAttribute(sessionName)
.getClass().getName();
buff.append(” name =” + sessionName + “, value =”
+ session.getAttribute(sessionName) + “, attributeClass = ”
+ sessionClassName + LINE_SEPA);
}
return buff.toString();
}
private static Hashtable convertRequest (HttpServletRequest request) {
Hashtable tempHash = new Hashtable();
Enumeration e = request.getParameterNames();
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
String[] values = request.getParameterValues(key);
String[] parameterValues = new String[values.length];
for(int i = 0; i < values.length; i++) {
parameterValues[i] = convUnicode(values[i]);
}
tempHash.put(key, parameterValues);
}
return tempHash;
}
private static String stratum(Object value) {
if (value == null) {
return “null”;
} else if (value instanceof String[]) {
return convString((String[]) value);
} else {
return value.toString();
}
}
/**
* 문자열 배열 [strArray]의 내용을 아래와 같은 String로 반환합니다.
* “[temp1,temp2,temp3]”
* @param strArray 평가 대상의 String 배열
* @return 변환 후 문자열
*/
private static String convString (String [] strArray) {
if (strArray == null)
return null;
StringBuffer buff = new StringBuffer(“[“);
for (int i = 0; i < strArray.length; i++) {
buff.append(strArray[i] + “, “);
}
buff.delete(buff.length() – 2, buff.length());
buff.append(“]”);
return buff.toString();
}
/**
* [str]을 유니코드로 변환합니다.
* @param str
* @return
*/
private static String convUnicode (String str) {
if (str == null)
return null;
try {
return new String(str.getBytes(“8859_1”), “JISAutoDetect”);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
// ———————————————————————–
static class CustomFormatter extends Formatter {
static final String pattern = “yyyy/MM/dd HH:mm:ss”;
public synchronized String format(LogRecord record) {
StringBuffer buf = new StringBuffer();
// 날짜 및 시간 설정
Date date = new Date();
date.setTime(record.getMillis());
SimpleDateFormat formatter = new SimpleDateFormat(pattern);
buf.append(formatter.format(date));
buf.append(“:”);
// 레벨 설정
buf.append(“[” + record.getLevel().getName() + “]”);
buf.append(“:”);
buf.append(record.getMessage());
buf.append(LINE_SEPA);
return buf.toString();
}
}
static class CustomConsoleHandler extends StreamHandler {
public CustomConsoleHandler() {
super();
setOutputStream(System.out);
}
/**
* LogRecord를 발행합니다.
* 초기 상태에서는, 로깅의 요구는 Logger 오브젝트에
* 에 대해 행해져, 이 오브젝트는 LogRecord 를 초기화해
* 여기로 전송했습니다.
*
* @param record 로그 이벤트 설명. null 레코드는 단순히 무시됩니다.
* 그냥 알림이 발생하지 않습니다.
*/
public void publish (LogRecord record) {
super.publish(record);
flush();
}
/**
* StreamHandler.close를 재정의하고 플러시하지만,
* 출력 스트림은 닫지 않습니다. 즉, System.err은 닫히지 않습니다.
*/
public void close() {
flush();
}
}
}