Tomcat 访问日志源码分析与应用

2、运行日志,记录tomcat 运行、异常、错误信息。

Tomcat 的日志记录常会被 log4j 或 slf4j 取代,不过这里不讨论另外日志组件,很纯粹地说一下tomcat 原生的访问日志。关于运行日志的分析,有机会再另写一篇。对于访问日志,tomcat 定义了以下接口:

public interface AccessLog {

// 记录访问日志
    public void log(Request request, Response response, long time);

// ip
    public static final String REMOTE_ADDR_ATTRIBUTE =
        "org.apache.catalina.AccessLog.RemoteAddr";
    // 主机名
    public static final String REMOTE_HOST_ATTRIBUTE =
        "org.apache.catalina.AccessLog.RemoteHost";
    // 访问协议
    public static final String PROTOCOL_ATTRIBUTE =
        "org.apache.catalina.AccessLog.Protocol";
    // 端口号
    public static final String SERVER_PORT_ATTRIBUTE =
        "org.apache.catalina.AccessLog.ServerPort";

// 设置是否记录ip,主机名,协议,端口号
    public void setRequestAttributesEnabled(boolean requestAttributesEnabled);
    public boolean getRequestAttributesEnabled();
}


    一个默认的实现是 AccessLogValue(在 server.xml 配置的)。先看一下,如何配置和使用 AccessLogValue,在 $tomcat_home%/conf/server.xml 里,有一下代码:

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
              prefix="localhost_access_log." suffix=".txt"
              pattern="%h %l %u %t &quot;%r&quot; %s %b" />


    参数的含义如下:


className:访问日志的实现类(implements AccessLog)

directory: 日志的位置

prefix:日志名称的前缀

suffix:日志名称的后缀

pattern:日志模式的参数,(模式参数的设置可以参考附录)

更多参数的设置可以查看 AccessLogValue 的参数。

对于 pattern ,tomcat 提供了两种便捷的 pattern 简写:common:%h %l %u %t "%r" %s %b;combined - %h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"

因为上述配置的方式,所以我们常看到日志记录文件如下(在 $tomcat_home$/logs/),下面日期的产生,是代码产生的:

对于其他基础的字段设置的配置与源码编写,理解起来应该不大(类似平常地解释 xml 文件),下面重点讲一下的是,如果根据 patten 来写日志(建议先阅读以下附录):

pattern 写法有两种 %XXX 或 %{XXX}XX,使用代码分析分析 pattern,再根据 pattern 获取对应的信息,将信息写到一个 StringBuilder 即可。对 pattern 的分析如下:对于各种配置的参数a,A等,都应该属于一种 XXXElenment,另外对于空格或其他字符,增加一个 StringElement,那在分析 pattern 时,每遇到一个特殊的字符,就创建一个指定的 element,反之,创建一个 StringElement,对pattern 的分析如下:

List<AccessLogElement> list = new ArrayList<AccessLogElement>();
boolean replace = false;
StringBuilder buf = new StringBuilder();
for (int i = 0; i < pattern.length(); i++) {
    char ch = pattern.charAt(i);
    if (replace) {
        /*
        * 用来处理 '{',如果在之后没有遇上 '}',将这个 '{'忽略,不处理。
        * 处理一下三种情况:
        * %{xxx}i  头字段信息
        * %{xxx}c cookie 信息
        * %{xxx}r ServletRequest 的某个 attribute
        * %{xxx}s HttpSession 的某个 attribut
        */
        if ('{' == ch) {
            StringBuilder name = new StringBuilder();
            int j = i + 1;
            for (; j < pattern.length() && '}' != pattern.charAt(j); j++) {
                name.append(pattern.charAt(j));
            }
            if (j + 1 < pattern.length()) {
                // j+1,跳过字符 '}x'
                j++;
                list.add(createAccessLogElement(name.toString(),
                        pattern.charAt(j)));
                i = j; // 跳过 %{xxx}x
            } else {
                // 单个字符,如 a,直接创建对应的 Element
                list.add(createAccessLogElement(ch));
            }
        } else {
            list.add(createAccessLogElement(ch));
        }
        replace = false;
    } else if (ch == '%') {
        replace = true;
        list.add(new StringElement(buf.toString()));
        buf = new StringBuilder();
    } else {
        buf.append(ch);
    }
}
if (buf.length() > 0) {
    list.add(new StringElement(buf.toString()));
}


    通过上面的分析,我们就可以根据 pattern 得到需要的信息(存储在 list 里),对于各种 element 的创建如:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/9468bc6a221b2a22c19d6ccd7bcb44c8.html