Java ログフィルタでHTTPリクエストを解析
javax.servlet.Filterを実装してHTTPリクエストの内容を解析してログを出力する便利なFilterクラスを紹介します。
このログフィルタは、ログ出力に「java.util.logging.Logger」を使用しており、ログレベルを変更することで出力する情報を変更できます。
javax.servlet.Filterとは
Webアプリケーション構築する上で、メインとなる業務処理のほかに、その前後で共通した副次的な処理を行う必要があります。
例えば、クライアントの認証や、権限チェック、ロギング等です。
このような副次的な処理をそれぞれのリソースでコーディングすると、ソフトウェアの保守性を妨げる大きな原因となり、メンテナンスのしにくいアプリケーションになってしまいます。
javax.servlet.Filterを実装すれば、リクエスがサーブレットクラスに渡される前に、この副次的な処理を、共通して実行させることができます。
またフィルタの設定はweb.xmlで出来るので、ソースコードを変更することなく、フィルタを追加したり削除することが柔軟に出来ます。
ログフィルタを使ってみる
HTTPリクエストにフィルタを通して、HTTPリクエストを解析するログフィルタを実際に使ってみましょう。
今回はTomcatに最初からあるexamplesアプリケーションを使用します。
以下の設定を行うとこのフィルタはすぐに実行できます。
  <filter>
   <filter-name>LogFilter</filter-name>
   <filter-class>LogFilter</filter-class>
   <init-param>
    <param-name>logging.Level</param-name>
    <param-value>FINE</param-value>
   </init-param>
  </filter>
  <filter-mapping> 
   <filter-name>LogFilter</filter-name>
   <url-pattern>/*</url-pattern>
  </filter-mapping>
※Strutsフレームワークを使用した場合のurl-patternは以下になります。
<url-pattern>*.do</url-pattern>
Sessions Exampleの画面を実行したログ出力結果を確認してみてください。

出力するログ情報とログレベル
●ログ出力内容
| ログ情報 | ログレベル | 
|---|---|
| Cookie情報 | FINE | 
| HTTPヘッダ情報 | FINE | 
| HTTPその他の情報 | FINE | 
| HTTPリクエストパラメータ | CONFIG | 
| requestスコープのオブジェクト | CONFIG | 
| sessionスコープのオブジェクト | CONFIG | 
| リクエスト前後のメモリー使用量 | CONFIG | 
| 画面遷移情報 | INFO | 
ログレベルは以下のように使い分けると良いでしょう。
- FINE・・・もっとも詳細なログを出力します。HTTPリクエストを詳しく解析したい場合に設定します。
- CONFIG・・・やや詳細なログを出力します。開発期間中はこのレベルにしているとよいでしょう。
- INFO・・・・画面遷移情報のみを出力します。
●ログレベルの変更方法
初期化パラメータlogging.Levelを設定することで、ログの出力レベルを変更できます。
例:
   <init-param>
    <param-name>logging.Level</param-name>
    <param-value>INFO</param-value>
   </init-param>
ソースコード
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 chain
     */
    public void doFilter(ServletRequest _request, ServletResponse _response,
            FilterChain chain) 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(“Cookie情報” + getCookieInfo(request));
        }
        if (logger.isLoggable(Level.FINE)) {
            logger.fine(“HTTPヘッダ情報” + getHeadersInfo(request));
        }
        if (logger.isLoggable(Level.FINE)) {
            logger
                    .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();
        }
    }
}









