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

Tomcat 日志可以分为两类:

1、访问日志,记录访问的时间、来源、资料等相关信息(ServletRequest 可以获取的信息,都可以记录);

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/),下面日期的产生,是代码产生的:

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

    对于其他基础的字段设置的配置与源码编写,理解起来应该不大(类似平常地解释 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 的创建如:

·  /*
* 根据 pattern,创建以下六种类型的信息之一:
* %{xxx}i  获取header 的某个 attribute
* %{xxx}c  获取cookie 的某个 attribute
* %{xxx}o  获取response 的某个 attribute
* %{xxx}r  获取request 的某个 attribute
* %{xxx}s  获取session 的某个 attribute
* %{xxx}t  获取dateAndTime 的某个 attribute
*/
protected AccessLogElement createAccessLogElement(String attribute, char pattern) {
    switch (pattern) {
    case 'i':
        return new HeaderElement(attribute);
    case 'c':
        return new CookieElement(attribute);
    case 'o':
        return new ResponseHeaderElement(attribute);
    case 'r':
        return new RequestAttributeElement(attribute);
    case 's':
        return new SessionAttributeElement(attribute);
    case 't':
        return new DateAndTimeElement(attribute);
    default:
        return new StringElement("???");
    }
}

    常规 element 的创建:

protected AccessLogElement createAccessLogElement(char pattern) {
  switch (pattern) {
  case 'a':
      return new RemoteAddrElement();
  case 'A':
      return new LocalAddrElement();
  case 'b':
      return new ByteSentElement(true);
  case 'B':
      return new ByteSentElement(false);
  case 'D':
      return new ElapsedTimeElement(true);
  case 'F':
      return new FirstByteTimeElement();
  case 'h':
      return new HostElement();
  case 'H':
      return new ProtocolElement();
  case 'l':
      return new LogicalUserNameElement();
  case 'm':
      return new MethodElement();
  case 'p':
      return new LocalPortElement();
  case 'q':
      return new QueryElement();
  case 'r':
      return new RequestElement();
  case 's':
      return new HttpStatusCodeElement();
  case 'S':
      return new SessionIdElement();
  case 't':
      return new DateAndTimeElement();
  case 'T':
      return new ElapsedTimeElement(false);
  case 'u':
      return new UserElement();
  case 'U':
      return new RequestURIElement();
  case 'v':
      return new LocalServerNameElement();
  case 'I':
      return new ThreadNameElement();
  default:
      return new StringElement("???" + pattern + "???");
  }
}


    对于各种 element,这里只给出其中几个,其他的类似:

//accessElement 接口
protected interface AccessLogElement {
  public void addElement(StringBuilder buf, Date date, Request request,
          Response response, long time);
}
//sessionElement %{xxx}s
protected static class SessionAttributeElement implements AccessLogElement {
    private final String header;

    public SessionAttributeElement(String header) {
        this.header = header;
    }

    @Override
    public void addElement(StringBuilder buf, Date date, Request request,
            Response response, long time) {
        Object value = null;
        if (null != request) {
            HttpSession sess = request.getSession(false);
            if (null != sess) {
                value = sess.getAttribute(header);
            }
        } else {
            value = "??";
        }
        if (value != null) {
            if (value instanceof String) {
                buf.append((String) value);
            } else {
                buf.append(value.toString());
            }
        } else {
            buf.append('-');
        }
    }
}

// queryElement %q
protected static class QueryElement implements AccessLogElement {
    @Override
    public void addElement(StringBuilder buf, Date date, Request request,
            Response response, long time) {
        String query = null;
        if (request != null) {
            query = request.getQueryString();
        }
        if (query != null) {
            buf.append('?');
            buf.append(query);
        }
    }
}

相关推荐