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 "%r" %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 的創建如:
· /*
* 根據 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("???");
}
}
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 + "???");
}
}
//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);
}
}
}
1、訪問者所使用的媒體,哪一個瀏覽器,pc 還是 移動2、獨立 ip 請求數(更具ip分析人羣的地域分佈)3、頁面的訪問量(那個頁面最後歡迎,響應時間如何)4、404 的請求 uri5、導致 500 的uri6、指定的用戶的訪問行爲7、訪問的流量統計等
remoteHostField = "remoteHost";
userField = "userName";
timestampField = "timestamp";
virtualHostField = "virtualHost";
methodField = "method";
queryField = "query";
statusField = "status";
bytesField = "bytes";
refererField = "referer";
userAgentField = "userAgent";
使用 JdbcAccessLogValue,需要做一下配置,在$tomcat_home%/conf/server.xml 添加如下配置:
<Valve className="org.apache.catalina.valves.JDBCAccessLogValve"
driverName="your_jdbc_driver"
connectionURL="your_jdbc_url"
pattern="common" resolveHosts="false"
/>
CREATE TABLE access (
id INT UNSIGNED AUTO_INCREMENT NOT NULL,
remoteHost CHAR(15) NOT NULL,
userName CHAR(15),
timestamp TIMESTAMP NOT NULL,
virtualHost VARCHAR(64) NOT NULL,
method VARCHAR(8) NOT NULL,
query VARCHAR(255) NOT NULL,
status SMALLINT UNSIGNED NOT NULL,
bytes INT UNSIGNED NOT NULL,
referer VARCHAR(128),
userAgent VARCHAR(128),
PRIMARY KEY (id),
INDEX (timestamp),
INDEX (remoteHost),
INDEX (virtualHost),
INDEX (query),
INDEX (userAgent)
);
http://www.blogjava.net/xmatthew/archive/2008/04/14/192450.html
張貼一張其博客的圖片:
附錄:
pattern 模式詳細的參數設置:
%a - 遠端IP地址
%A - 本地IP地址
%b - 發送的字節數,不包括HTTP頭,如果爲0,使用"-"
%B - 發送的字節數,不包括HTTP頭
%h - 遠端主機名(如果resolveHost=false,遠端的IP地址)
%H - 請求協議
%l - 從identd返回的遠端邏輯用戶名(總是返回 '-')
%m - 請求的方法(GET,POST,等)
%p - 收到請求的本地端口號
%q - 查詢字符串(如果存在,以 '?'開始)
%r - 請求的第一行,包含了請求的方法和URI
%s - 響應的狀態碼
%S - 用戶的session ID
%t - 日誌和時間,使用通常的Log格式
%u - 認證以後的遠端用戶(如果存在的話,否則爲'-')
%U - 請求的URI路徑
%v - 本地服務器的名稱
%D - 處理請求的時間,以毫秒爲單位
%T - 處理請求的時間,以秒爲單位
%{xxx}i 獲取header 的某個 attribute
%{xxx}c 獲取cookie 的某個 attribute
%{xxx}o 獲取response 的某個 attribute
%{xxx}r 獲取request 的某個 attribute
%{xxx}s 獲取session 的某個 attribute
%{xxx}t 獲取dateAndTime 的某個 attribute