Q2:クライアントのアクセスログを記録するには?

クライアントのアクセスログを記録する場合は、Servet APIにおけるフィルタを使う方法が便利です(もちろんApache等のWebサーバのアクセスログ出力に任せる方法もあり、そちらで行う方がよい場合もあるでしょう)。フィルタの解説は省略しますが、個々のクラスに埋め込む方法と比べて以下の利点があります。

サーブレットの共通の親クラスですべて出力する方法もありますが、JSP へのアクセスログを記録することはできません。もちろんアクセスログ以上の内容を記録するためにはこの様な方法が必要な場合があります。ただ、アクセスログの記録という点においてはフィルタのほうが便利で柔軟性があります。尚、フィルタは ServletAPI2.3 に対応したサーブレット・コンテナでしか使えません。ServletAPI2.2 ベースの Tomcat3.x では使用できません。

以下は Log4j によるロギングを組み込んだフィルタのサンプルです。

package logtest.web;

import org.apache.log4j.*;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

public final class LoggingFilter implements Filter {
    private static final Logger logger = Logger.getLogger(LoggingFilter.class);
    private FilterConfig filterConfig = null;

    public void destroy() {
        this.filterConfig = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
        if (filterConfig == null) {
            return;
        }

        if (request instanceof HttpServletRequest) {
            HttpServletRequest req = (HttpServletRequest) request;
            String sep = System.getProperty("line.separator");
            logger.info("RequestURL:" + req.getRequestURL() + sep +
                "Remote Host:" + req.getRemoteHost() + sep + "Remote Addr:" +
                req.getRemoteAddr() + sep + "Query String:" +
                req.getQueryString());
        }

        chain.doFilter(request, response);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }
}

HTTP リクエストが発生すると doFilter() が呼び出されます(呼び出されるようにするための設定は後述します)。doFilter() ではリクエストが HTTP リクエストであるかどうかを判別し、HTTP リクエストであれば、リクエスト情報をログとして出力しています。

chain.doFilter()で制御が実際のコンテンツ(Servlet, JSP, HTML等)に移ります。

このフィルタを Web アプリケーションに組み込むためには、web.xml にフィルタクラスと URL マッピングの設定を加えます。

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <filter>
        <filter-name>LoggingFilter</filter-name>
        <filter-class>logtest.web.LoggingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>LoggingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <servlet>
        <servlet-name>
            Logger
        </servlet-name>
        <servlet-class>
            logtest.web.LoggingServlet
        </servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>
            Logger
        </servlet-name>
        <url-pattern>
            /logger
        </url-pattern>
    </servlet-mapping>
</web-app>

まず <filter> 要素でフィルタの名称(LoggingFilter)とクラス名(logtest.web.LoggingFilter)を指定します。フィルタの名称は次の <filter-mapping> と関連付けることが目的ですので、任意の文字列でOKです。

次に <filter-mapping> 要素で、どの URL パターンに対してフィルタを動作させるかを決定します(要はサーブレットマッピングと同じです)。子要素の <url-pattern> で URL パターンを記述し、<filter-name> に実行するフィルタの名称(<filter> 要素で指定したもの)を記述します。上の例では"/*"が指定されているため、すべてのリクエストに対して LoggingFilter が動作することになります。

そのため、Servlet, JSP だけでなく HTML や JSP が参照する画像、スタイルシートについてもログが出力されてしまいます。例えば、サーブレット、JSP のみに絞りたい場合は、以下のように <filter-mapping> 要素を複数用意し、それぞれでフィルタを実行させる URL パターンを定義します。

<!-- 拡張子.jsp(JSP)の場合LoggingFilterを実行 -->
<filter-mapping>
    <filter-name>LoggingFilter</filter-name>
    <url-pattern>*.jsp</url-pattern>
</filter-mapping>
<!-- 拡張子.do(サーブレット)の場合LoggingFilterを実行 -->
<filter-mapping>
    <filter-name>LoggingFilter</filter-name>
    <url-pattern>*.do</url-pattern>
</filter-mapping>

上の例ではサーブレットの URL マッピングがすべて *.do にマッチすることを前提としています。フィルタでフィルタリングしやすいように URL を設定する必要があります。


トップページへ戻る