前面说过,实现value的阀会在管道里面的内部类StandardPiplineValueContext调用。那么我们来看看几个Value的实现。首先是AccessLogValue类.
tomcat可以通过server.xml配置系统日志。
比如:
<?xml version="1.0" encoding="utf-8"?>
<Context displayName="test"
docBase="test"
path="/test"
reloadable="true"
workDir="work/Catalina/localhost/test">
<Valve className="org.apache.catalina.valves.AccessLogValve" rotatable="true"
directory="logs" prefix="localhost_test_access_log." suffix=".log"
pattern="combined" resolveHosts="false"/>
</Context>
下面会对这些标签进行解读
public final class AccessLogValve
extends ValveBase
implements Lifecycle {
// ----------------------------------------------------------- Constructors
/**
* Construct a new instance of this class with default property values.
*/
public AccessLogValve() {
super();
setPattern("common");
}
// ----------------------------------------------------- Instance Variables
/**
* The as-of date for the currently open log file, or a zero-length
* string if there is no open log file.
*/
private String dateStamp = "";
/**
* The directory in which log files are created.
*/
private String directory = "logs";
/**
* The descriptive information about this implementation.
*/
protected static final String info =
"org.apache.catalina.valves.AccessLogValve/1.0";
/**
* The lifecycle event support for this component.
*/
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
/**
* The set of month abbreviations for log messages.
*/
protected static final String months[] =
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
/**
* If the current log pattern is the same as the common access log
* format pattern, then we'll set this variable to true and log in
* a more optimal and hard-coded way.
*/
//日志是common格式
private boolean common = false;
/**
* For the combined format (common, plus useragent and referer), we do
* the same
*/
//日志是combined格式
private boolean combined = false;
/**
* The pattern used to format our access log lines.
*/
//A formatting layout identifying the various information fields from the request and response to be
// logged, or the word common or combined to select a standard format. See below for more information
//on configuring this attribute. Note that the optimized access does only support common and combined as the value for this attribute.
//主要参数,声明日志类型。主要有common和combined
//common的值:%h %l %u %t %r %s %b
//combined的值:%h %l %u %t %r %s %b %{Referer}i %{User-Agent}i
private String pattern = null;
/**
* The prefix that is added to log file filenames.
*/
//日志名前缀
private String prefix = "access_log.";
/**
* Should we rotate our log file? Default is true (like old behavior)
*/
// 默认为true,默认的设置使得你的tomcat生成的文件命为prefix(前缀)+.+时间(一般是按天算)+.+suffix(后缀)
// 为false的话,tomcat会忽略时间,不会新生成文件,最后导致你的文件超级大.
private boolean rotatable = true;
/**
* The string manager for this package.
*/
private StringManager sm =
StringManager.getManager(Constants.Package);
/**
* Has this component been started yet?
*/
private boolean started = false;
/**
* The suffix that is added to log file filenames.
*/
//文件后缀名
private String suffix = "";
/**
* The PrintWriter to which we are currently logging, if any.
*/
private PrintWriter writer = null;
/**
* A date formatter to format a Date into a date in the format
* "yyyy-MM-dd".
*/
private SimpleDateFormat dateFormatter = null;
/**
* A date formatter to format Dates into a day string in the format
* "dd".
*/
private SimpleDateFormat dayFormatter = null;
/**
* A date formatter to format a Date into a month string in the format
* "MM".
*/
private SimpleDateFormat monthFormatter = null;
/**
* Time taken formatter for 3 decimal places.
*/
private DecimalFormat timeTakenFormatter = null;
/**
* A date formatter to format a Date into a year string in the format
* "yyyy".
*/
private SimpleDateFormat yearFormatter = null;
/**
* A date formatter to format a Date into a time in the format
* "kk:mm:ss" (kk is a 24-hour representation of the hour).
*/
private SimpleDateFormat timeFormatter = null;
/**
* The system timezone.
*/
private TimeZone timezone = null;
/**
* The time zone offset relative to GMT in text form when daylight saving
* is not in operation.
*/
private String timeZoneNoDST = null;
/**
* The time zone offset relative to GMT in text form when daylight saving
* is in operation.
*/
private String timeZoneDST = null;
/**
* The system time when we last updated the Date that this valve
* uses for log lines.
*/
private Date currentDate = null;
/**
* When formatting log lines, we often use strings like this one (" ").
*/
private String space = " ";
/**
* Resolve hosts.
*/
//如果这个值是true的话,tomcat会将这个服务器IP地址通过DNS转换为主机名,如果是false,就直接写服务器IP地址
private boolean resolveHosts = false;
/**
* Instant when the log daily rotation was last checked.
*/
private long rotationLastChecked = 0L;
/**
* Are we doing conditional logging. default false.
*/
private String condition = null;
/**
* Date format to place in log file name. Use at your own risk!
*/
private String fileDateFormat = null;
// ------------------------------------------------------------- Properties
/**
* Return the directory in which we create log files.
*/
//获得存放日志的目录
public String getDirectory() {
return (directory);
}
/**
* Set the directory in which we create log files.
*
* @param directory The new log file directory
*/
//设置创建存放日志的目录
public void setDirectory(String directory) {
this.directory = directory;
}
/**
* Return descriptive information about this implementation.
*/
public String getInfo() {
return (info);
}
/**
* Return the format pattern.
*/
public String getPattern() {
return (this.pattern);
}
/**
* Set the format pattern, first translating any recognized alias.
*
* @param pattern The new pattern
*/
//设置不同的日志格式
public void setPattern(String pattern) {
if (pattern == null)
pattern = "";
if (pattern.equals(Constants.AccessLog.COMMON_ALIAS))
pattern = Constants.AccessLog.COMMON_PATTERN;
if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS))
pattern = Constants.AccessLog.COMBINED_PATTERN;
this.pattern = pattern;
if (this.pattern.equals(Constants.AccessLog.COMMON_PATTERN))
common = true;
else
common = false;
if (this.pattern.equals(Constants.AccessLog.COMBINED_PATTERN))
combined = true;
else
combined = false;
}
/**
* Return the log file prefix.
*/
public String getPrefix() {
return (prefix);
}
/**
* Set the log file prefix.
*
* @param prefix The new log file prefix
*/
//设置文件前缀名
public void setPrefix(String prefix) {
this.prefix = prefix;
}
/**
* Should we rotate the logs
*/
public boolean isRotatable() {
return rotatable;
}
/**
* Set the value is we should we rotate the logs
*
* @param rotatable true is we should rotate.
*/
public void setRotatable(boolean rotatable) {
this.rotatable = rotatable;
}
/**
* Return the log file suffix.
*/
public String getSuffix() {
return (suffix);
}
/**
* Set the log file suffix.
*
* @param suffix The new log file suffix
*/
//设置后缀名
public void setSuffix(String suffix) {
this.suffix = suffix;
}
/**
* Set the resolve hosts flag.
*
* @param resolveHosts The new resolve hosts value
*/
//判断是否设置服务器的名称
public void setResolveHosts(boolean resolveHosts) {
this.resolveHosts = resolveHosts;
}
/**
* Get the value of the resolve hosts flag.
*/
public boolean isResolveHosts() {
return resolveHosts;
}
/**
* Return whether the attribute name to look for when
* performing conditional loggging. If null, every
* request is logged.
*/
//这个参数不太实用,可以设置任何值,比如设置成condition="tkq",那么只有当ServletRequest.getAttribute("tkq")为空的时候,才会被记录下来
public String getCondition() {
return condition;
}
/**
* Set the ServletRequest.attribute to look for to perform
* conditional logging. Set to null to log everything.
*
* @param condition Set to null to log everything
*/
public void setCondition(String condition) {
this.condition = condition;
}
/**
* Return the date format date based log rotation.
*/
public String getFileDateFormat() {
return fileDateFormat;
}
/**
* Set the date format date based log rotation.
*/
public void setFileDateFormat(String fileDateFormat) {
this.fileDateFormat = fileDateFormat;
}
// --------------------------------------------------------- Public Methods
/**
* Log a message summarizing the specified request and response, according
* to the format specified by the <code>pattern</code> property.
*
* @param request Request being processed
* @param response Response being processed
* @param context The valve context used to invoke the next valve
* in the current processing pipeline
*
* @exception IOException if an input/output error has occurred
* @exception ServletException if a servlet error has occurred
*/
public void invoke(Request request, Response response,
ValveContext context)
throws IOException, ServletException {
// Pass this request on to the next valve in our pipeline
long t1=System.currentTimeMillis();//设置初始时间
context.invokeNext(request, response);//调用下一个阀
long t2=System.currentTimeMillis();//设置调用阀后的时间
long time=t2-t1;
//不为空直接返回,比如设置成condition="tkq",那么只有当ServletRequest.getAttribute("tkq")为空的时候,才会被记录下来
if (condition!=null &&
null!=request.getRequest().getAttribute(condition)) {
return;
}
Date date = getDate();//获取时间
StringBuffer result = new StringBuffer();
// Check to see if we should log using the "common" access log pattern
//做一些信息的记录,比如IP,时间,协议等等的记录
if (common || combined) {
String value = null;
ServletRequest req = request.getRequest();
HttpServletRequest hreq = null;
if (req instanceof HttpServletRequest)
hreq = (HttpServletRequest) req;
if (isResolveHosts())
result.append(req.getRemoteHost());
else
result.append(req.getRemoteAddr());
result.append(" - ");
if (hreq != null)
value = hreq.getRemoteUser();
if (value == null)
result.append("- ");
else {
result.append(value);
result.append(space);
}
result.append("[");
result.append(dayFormatter.format(date)); // Day
result.append('/');
result.append(lookup(monthFormatter.format(date))); // Month
result.append('/');
result.append(yearFormatter.format(date)); // Year
result.append(':');
result.append(timeFormatter.format(date)); // Time
result.append(space);
result.append(getTimeZone(date)); // Time Zone
result.append("] \"");
result.append(hreq.getMethod());
result.append(space);
result.append(hreq.getRequestURI());
if (hreq.getQueryString() != null) {
result.append('?');
result.append(hreq.getQueryString());
}
result.append(space);
result.append(hreq.getProtocol());
result.append("\" ");
result.append(((HttpResponse) response).getStatus());
result.append(space);
int length = response.getContentCount();
if (length <= 0)
value = "-";
else
value = "" + length;
result.append(value);
//如果是combined为true的话,会多%{Referer}i %{User-Agent}i 这几项信息
if (combined) {
result.append(space);
result.append("\"");
String referer = hreq.getHeader("referer");
if(referer != null)
result.append(referer);
else
result.append("-");
result.append("\"");
result.append(space);
result.append("\"");
String ua = hreq.getHeader("user-agent");
if(ua != null)
result.append(ua);
else
result.append("-");
result.append("\"");
}
}
//如果定义自己的默认的模式
else {
// Generate a message based on the defined pattern
boolean replace = false;
for (int i = 0; i < pattern.length(); i++) {
char ch = pattern.charAt(i);
if (replace) {
/* For code that processes {, the behavior will be ... if I
* do not enounter a closing } - then I ignore the {
*/
if ('{' == ch){
StringBuffer name = new StringBuffer();
int j = i + 1;
for(;j < pattern.length() && '}' != pattern.charAt(j); j++) {
name.append(pattern.charAt(j));
}
//如果pattern的格式是{....}.. 即"}"后面还有花括号的话,那么将解析"}"后面的这个字符来进行讨论
if (j+1 < pattern.length()) {//如果j+1小于pattern的长度
/* the +1 was to account for } which we increment now */
j++;
result.append(replace(name.toString(),
pattern.charAt(j),
request,
response));
i=j; /*Since we walked more than one character*/
} else {//如果pattern的格式是{....},那么需要对{}里面的每个字符进行讨论
//D'oh - end of string - pretend we never did this
//and do processing the "old way"
result.append(replace(ch, date, request, response, time));
}
} else {
result.append(replace(ch, date, request, response,time ));
}
replace = false;
} else if (ch == '%') {
replace = true;
} else {
result.append(ch);
}
}
}
log(result.toString(), date);//记录日记
}
----------------------------------------------------------------------------------------------------------
/**
* Return the replacement text for the specified "header/parameter".
*
* @param header The header/parameter to get
* @param type Where to get it from i=input,c=cookie,r=ServletRequest,s=Session
* @param request Request being processed
* @param response Response being processed
*/
private String replace(String header, char type, Request request,
Response response) {
Object value = null;
ServletRequest req = request.getRequest();
HttpServletRequest hreq = null;
if (req instanceof HttpServletRequest)
hreq = (HttpServletRequest) req;
//判断type
switch (type) {
case 'i':
if (null != hreq)
value = hreq.getHeader(header);//获得Header作为value返回
else
value= "??";
break;
/*
// Someone please make me work
case 'o':
break;
*/
case 'c':
Cookie[] c = hreq.getCookies();//获得cookie
for (int i=0; c != null && i < c.length; i++){
if (header.equals(c[i].getName())){//如果我们自己设置的pattern的header等于Cookie中某一个name,那么将得到Cookie中的value作为新的value返回
value = c[i].getValue();
break;
}
}
break;
case 'r':
if (null != hreq)
value = hreq.getAttribute(header);//获得pattern的header的属性作为vaule返回
else
value= "??";
break;
case 's':
if (null != hreq) {
HttpSession sess = hreq.getSession(false);
if (null != sess)
value = sess.getAttribute(header);//获得session作为value返回
}
break;
default:
value = "???";
}
/* try catch in case toString() barfs */
//将value转成字符串
try {
if (value!=null)
if (value instanceof String)
return (String)value;
else
return value.toString();
else
return "-";
} catch(Throwable e) {
return "-";
}
}
----------------------------------------------------------------------------------------------------------------------------
/**
* Return the replacement text for the specified pattern character.
*
* @param pattern Pattern character identifying the desired text
* @param date the current Date so that this method doesn't need to
* create one
* @param request Request being processed
* @param response Response being processed
*/
/*
%a
这是记录访问者的IP
%A
这是记录本地服务器的IP
%t
这就是时间啦
%r
请求的链接
%s
响应状态
%u
得到了验证的访问者,否则就是"-"
%U
访问的URL地址
%b
这是发送信息的字节数,不涵括http头,如果字节数为0的话,显示为-
%B
Bytes sent, excluding HTTP headers
%h
这个就是服务器名称了,如果resolveHosts为false的话,这里就是IP地址了
%H
访问者使用的协议,这里是HTTP/1.1
%l
Remote logical username from identd (可能这样翻译:记录浏览者进行身份验证时提供的名字)
%m
访问的方式,是GET还是POST
%v
服务器名称
%D
Time taken to process the request, in millis,应该是访问发生的时间,以毫秒记
%T
Time taken to process the request, in seconds,应该是访问发生的时间,以秒记
*/
private String replace(char pattern, Date date, Request request,
Response response, long time) {
String value = null;
ServletRequest req = request.getRequest();
HttpServletRequest hreq = null;
if (req instanceof HttpServletRequest)
hreq = (HttpServletRequest) req;
ServletResponse res = response.getResponse();
HttpServletResponse hres = null;
if (res instanceof HttpServletResponse)
hres = (HttpServletResponse) res;
if (pattern == 'a') {
value = req.getRemoteAddr();
} else if (pattern == 'A') {
try {
value = InetAddress.getLocalHost().getHostAddress();
} catch(Throwable e){
value = "127.0.0.1";
}
} else if (pattern == 'b') {
int length = response.getContentCount();
if (length <= 0)
value = "-";
else
value = "" + length;
} else if (pattern == 'B') {
value = "" + response.getContentLength();
} else if (pattern == 'h') {
value = req.getRemoteHost();
} else if (pattern == 'H') {
value = req.getProtocol();
} else if (pattern == 'l') {
value = "-";
} else if (pattern == 'm') {
if (hreq != null)
value = hreq.getMethod();
else
value = "";
} else if (pattern == 'p') {
value = "" + req.getServerPort();
} else if (pattern == 'D') {
value = "" + time;
} else if (pattern == 'q') {
String query = null;
if (hreq != null)
query = hreq.getQueryString();
if (query != null)
value = "?" + query;
else
value = "";
} else if (pattern == 'r') {
StringBuffer sb = new StringBuffer();
if (hreq != null) {
sb.append(hreq.getMethod());
sb.append(space);
sb.append(hreq.getRequestURI());
if (hreq.getQueryString() != null) {
sb.append('?');
sb.append(hreq.getQueryString());
}
sb.append(space);
sb.append(hreq.getProtocol());
} else {
sb.append("- - ");
sb.append(req.getProtocol());
}
value = sb.toString();
} else if (pattern == 'S') {
if (hreq != null)
if (hreq.getSession(false) != null)
value = hreq.getSession(false).getId();
else value = "-";
else
value = "-";
} else if (pattern == 's') {
if (hres != null)
value = "" + ((HttpResponse) response).getStatus();
else
value = "-";
} else if (pattern == 't') {
StringBuffer temp = new StringBuffer("[");
temp.append(dayFormatter.format(date)); // Day
temp.append('/');
temp.append(lookup(monthFormatter.format(date))); // Month
temp.append('/');
temp.append(yearFormatter.format(date)); // Year
temp.append(':');
temp.append(timeFormatter.format(date)); // Time
temp.append(' ');
temp.append(getTimeZone(date)); // Timezone
temp.append(']');
value = temp.toString();
} else if (pattern == 'T') {
value = timeTakenFormatter.format(time/1000d);
} else if (pattern == 'u') {
if (hreq != null)
value = hreq.getRemoteUser();
if (value == null)
value = "-";
} else if (pattern == 'U') {
if (hreq != null)
value = hreq.getRequestURI();
else
value = "-";
} else if (pattern == 'v') {
value = req.getServerName();
} else {
value = "???" + pattern + "???";
}
if (value == null)
return ("");
else
return (value);
}
-------------------------------------------------------------------------------------------------------------------
下面再来看看log方法:
/**
* Log the specified message to the log file, switching files if the date
* has changed since the previous log call.
*
* @param message Message to be logged
* @param date the current Date object (so this method doesn't need to
* create a new one)
*/
public void log(String message, Date date) {
if (rotatable){
// Only do a logfile switch check once a second, max.
long systime = System.currentTimeMillis();//获得当前的时间
if ((systime - rotationLastChecked) > 1000) {
// We need a new currentDate
currentDate = new Date(systime);
rotationLastChecked = systime;
// Check for a change of date
String tsDate = dateFormatter.format(currentDate);
// If the date has changed, switch log files
//如果当前时间不等于之前的时间,那么更新日志文件名
if (!dateStamp.equals(tsDate)) {
synchronized (this) {//需要进行同步,防止多个线程进行操作,其实servlet容器的invoke方法其实是由Connector连接器中调用servlet实例的invoke方法的,而
//Connector连接器是多线程的,那么这里自然而然需要进行同步
if (!dateStamp.equals(tsDate)) {//这里还需要进行重复一次的判断,这里十分重要,要不多线程就会不安全。好好品味一下
close();//将之前打开的文件关闭
dateStamp = tsDate;//更新时间
open();//打开文件
}
}
}
}
}
// Log this message
if (writer != null) {
writer.println(message);//向日志中写入信息
}
}
-------------------------------------------------------------------------------------------------------------
// -------------------------------------------------------- Private Methods
//关闭文件
/**
* Close the currently open log file (if any)
*/
private synchronized void close() {
if (writer == null)
return;
writer.flush();
writer.close();
writer = null;
dateStamp = "";
}
---------------------------------------------------------------------------------------------------------------
/**
* Open the new log file for the date specified by <code>dateStamp</code>.
*/
private synchronized void open() {
// Create the directory if necessary
File dir = new File(directory);
if (!dir.isAbsolute())//如果创建的文件不是绝对路径
dir = new File(System.getProperty("catalina.base"), directory);//那么通过System.getProperty("catalina.base")获得父目录,然后在此父目录创建子目录
dir.mkdirs();
// Open the current log file
try {
String pathname;
// If no rotate - no need for dateStamp in fileName
if (rotatable){//对文件名字进行命名
pathname = dir.getAbsolutePath() + File.separator +
prefix + dateStamp + suffix;
} else {
pathname = dir.getAbsolutePath() + File.separator +
prefix + suffix;
}
writer = new PrintWriter(new FileWriter(pathname, true), true);//创建文件
} catch (IOException e) {
writer = null;
}
}
这个类的主要方法就是这几个了,还剩几个生命周期的函数,这些放在生命周期那节来看。剩下就是一些小函数了,请自己查阅!
tomcat可以通过server.xml配置系统日志。
比如:
<?xml version="1.0" encoding="utf-8"?>
<Context displayName="test"
docBase="test"
path="/test"
reloadable="true"
workDir="work/Catalina/localhost/test">
<Valve className="org.apache.catalina.valves.AccessLogValve" rotatable="true"
directory="logs" prefix="localhost_test_access_log." suffix=".log"
pattern="combined" resolveHosts="false"/>
</Context>
下面会对这些标签进行解读
public final class AccessLogValve
extends ValveBase
implements Lifecycle {
// ----------------------------------------------------------- Constructors
/**
* Construct a new instance of this class with default property values.
*/
public AccessLogValve() {
super();
setPattern("common");
}
// ----------------------------------------------------- Instance Variables
/**
* The as-of date for the currently open log file, or a zero-length
* string if there is no open log file.
*/
private String dateStamp = "";
/**
* The directory in which log files are created.
*/
private String directory = "logs";
/**
* The descriptive information about this implementation.
*/
protected static final String info =
"org.apache.catalina.valves.AccessLogValve/1.0";
/**
* The lifecycle event support for this component.
*/
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
/**
* The set of month abbreviations for log messages.
*/
protected static final String months[] =
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
/**
* If the current log pattern is the same as the common access log
* format pattern, then we'll set this variable to true and log in
* a more optimal and hard-coded way.
*/
//日志是common格式
private boolean common = false;
/**
* For the combined format (common, plus useragent and referer), we do
* the same
*/
//日志是combined格式
private boolean combined = false;
/**
* The pattern used to format our access log lines.
*/
//A formatting layout identifying the various information fields from the request and response to be
// logged, or the word common or combined to select a standard format. See below for more information
//on configuring this attribute. Note that the optimized access does only support common and combined as the value for this attribute.
//主要参数,声明日志类型。主要有common和combined
//common的值:%h %l %u %t %r %s %b
//combined的值:%h %l %u %t %r %s %b %{Referer}i %{User-Agent}i
private String pattern = null;
/**
* The prefix that is added to log file filenames.
*/
//日志名前缀
private String prefix = "access_log.";
/**
* Should we rotate our log file? Default is true (like old behavior)
*/
// 默认为true,默认的设置使得你的tomcat生成的文件命为prefix(前缀)+.+时间(一般是按天算)+.+suffix(后缀)
// 为false的话,tomcat会忽略时间,不会新生成文件,最后导致你的文件超级大.
private boolean rotatable = true;
/**
* The string manager for this package.
*/
private StringManager sm =
StringManager.getManager(Constants.Package);
/**
* Has this component been started yet?
*/
private boolean started = false;
/**
* The suffix that is added to log file filenames.
*/
//文件后缀名
private String suffix = "";
/**
* The PrintWriter to which we are currently logging, if any.
*/
private PrintWriter writer = null;
/**
* A date formatter to format a Date into a date in the format
* "yyyy-MM-dd".
*/
private SimpleDateFormat dateFormatter = null;
/**
* A date formatter to format Dates into a day string in the format
* "dd".
*/
private SimpleDateFormat dayFormatter = null;
/**
* A date formatter to format a Date into a month string in the format
* "MM".
*/
private SimpleDateFormat monthFormatter = null;
/**
* Time taken formatter for 3 decimal places.
*/
private DecimalFormat timeTakenFormatter = null;
/**
* A date formatter to format a Date into a year string in the format
* "yyyy".
*/
private SimpleDateFormat yearFormatter = null;
/**
* A date formatter to format a Date into a time in the format
* "kk:mm:ss" (kk is a 24-hour representation of the hour).
*/
private SimpleDateFormat timeFormatter = null;
/**
* The system timezone.
*/
private TimeZone timezone = null;
/**
* The time zone offset relative to GMT in text form when daylight saving
* is not in operation.
*/
private String timeZoneNoDST = null;
/**
* The time zone offset relative to GMT in text form when daylight saving
* is in operation.
*/
private String timeZoneDST = null;
/**
* The system time when we last updated the Date that this valve
* uses for log lines.
*/
private Date currentDate = null;
/**
* When formatting log lines, we often use strings like this one (" ").
*/
private String space = " ";
/**
* Resolve hosts.
*/
//如果这个值是true的话,tomcat会将这个服务器IP地址通过DNS转换为主机名,如果是false,就直接写服务器IP地址
private boolean resolveHosts = false;
/**
* Instant when the log daily rotation was last checked.
*/
private long rotationLastChecked = 0L;
/**
* Are we doing conditional logging. default false.
*/
private String condition = null;
/**
* Date format to place in log file name. Use at your own risk!
*/
private String fileDateFormat = null;
// ------------------------------------------------------------- Properties
/**
* Return the directory in which we create log files.
*/
//获得存放日志的目录
public String getDirectory() {
return (directory);
}
/**
* Set the directory in which we create log files.
*
* @param directory The new log file directory
*/
//设置创建存放日志的目录
public void setDirectory(String directory) {
this.directory = directory;
}
/**
* Return descriptive information about this implementation.
*/
public String getInfo() {
return (info);
}
/**
* Return the format pattern.
*/
public String getPattern() {
return (this.pattern);
}
/**
* Set the format pattern, first translating any recognized alias.
*
* @param pattern The new pattern
*/
//设置不同的日志格式
public void setPattern(String pattern) {
if (pattern == null)
pattern = "";
if (pattern.equals(Constants.AccessLog.COMMON_ALIAS))
pattern = Constants.AccessLog.COMMON_PATTERN;
if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS))
pattern = Constants.AccessLog.COMBINED_PATTERN;
this.pattern = pattern;
if (this.pattern.equals(Constants.AccessLog.COMMON_PATTERN))
common = true;
else
common = false;
if (this.pattern.equals(Constants.AccessLog.COMBINED_PATTERN))
combined = true;
else
combined = false;
}
/**
* Return the log file prefix.
*/
public String getPrefix() {
return (prefix);
}
/**
* Set the log file prefix.
*
* @param prefix The new log file prefix
*/
//设置文件前缀名
public void setPrefix(String prefix) {
this.prefix = prefix;
}
/**
* Should we rotate the logs
*/
public boolean isRotatable() {
return rotatable;
}
/**
* Set the value is we should we rotate the logs
*
* @param rotatable true is we should rotate.
*/
public void setRotatable(boolean rotatable) {
this.rotatable = rotatable;
}
/**
* Return the log file suffix.
*/
public String getSuffix() {
return (suffix);
}
/**
* Set the log file suffix.
*
* @param suffix The new log file suffix
*/
//设置后缀名
public void setSuffix(String suffix) {
this.suffix = suffix;
}
/**
* Set the resolve hosts flag.
*
* @param resolveHosts The new resolve hosts value
*/
//判断是否设置服务器的名称
public void setResolveHosts(boolean resolveHosts) {
this.resolveHosts = resolveHosts;
}
/**
* Get the value of the resolve hosts flag.
*/
public boolean isResolveHosts() {
return resolveHosts;
}
/**
* Return whether the attribute name to look for when
* performing conditional loggging. If null, every
* request is logged.
*/
//这个参数不太实用,可以设置任何值,比如设置成condition="tkq",那么只有当ServletRequest.getAttribute("tkq")为空的时候,才会被记录下来
public String getCondition() {
return condition;
}
/**
* Set the ServletRequest.attribute to look for to perform
* conditional logging. Set to null to log everything.
*
* @param condition Set to null to log everything
*/
public void setCondition(String condition) {
this.condition = condition;
}
/**
* Return the date format date based log rotation.
*/
public String getFileDateFormat() {
return fileDateFormat;
}
/**
* Set the date format date based log rotation.
*/
public void setFileDateFormat(String fileDateFormat) {
this.fileDateFormat = fileDateFormat;
}
// --------------------------------------------------------- Public Methods
/**
* Log a message summarizing the specified request and response, according
* to the format specified by the <code>pattern</code> property.
*
* @param request Request being processed
* @param response Response being processed
* @param context The valve context used to invoke the next valve
* in the current processing pipeline
*
* @exception IOException if an input/output error has occurred
* @exception ServletException if a servlet error has occurred
*/
public void invoke(Request request, Response response,
ValveContext context)
throws IOException, ServletException {
// Pass this request on to the next valve in our pipeline
long t1=System.currentTimeMillis();//设置初始时间
context.invokeNext(request, response);//调用下一个阀
long t2=System.currentTimeMillis();//设置调用阀后的时间
long time=t2-t1;
//不为空直接返回,比如设置成condition="tkq",那么只有当ServletRequest.getAttribute("tkq")为空的时候,才会被记录下来
if (condition!=null &&
null!=request.getRequest().getAttribute(condition)) {
return;
}
Date date = getDate();//获取时间
StringBuffer result = new StringBuffer();
// Check to see if we should log using the "common" access log pattern
//做一些信息的记录,比如IP,时间,协议等等的记录
if (common || combined) {
String value = null;
ServletRequest req = request.getRequest();
HttpServletRequest hreq = null;
if (req instanceof HttpServletRequest)
hreq = (HttpServletRequest) req;
if (isResolveHosts())
result.append(req.getRemoteHost());
else
result.append(req.getRemoteAddr());
result.append(" - ");
if (hreq != null)
value = hreq.getRemoteUser();
if (value == null)
result.append("- ");
else {
result.append(value);
result.append(space);
}
result.append("[");
result.append(dayFormatter.format(date)); // Day
result.append('/');
result.append(lookup(monthFormatter.format(date))); // Month
result.append('/');
result.append(yearFormatter.format(date)); // Year
result.append(':');
result.append(timeFormatter.format(date)); // Time
result.append(space);
result.append(getTimeZone(date)); // Time Zone
result.append("] \"");
result.append(hreq.getMethod());
result.append(space);
result.append(hreq.getRequestURI());
if (hreq.getQueryString() != null) {
result.append('?');
result.append(hreq.getQueryString());
}
result.append(space);
result.append(hreq.getProtocol());
result.append("\" ");
result.append(((HttpResponse) response).getStatus());
result.append(space);
int length = response.getContentCount();
if (length <= 0)
value = "-";
else
value = "" + length;
result.append(value);
//如果是combined为true的话,会多%{Referer}i %{User-Agent}i 这几项信息
if (combined) {
result.append(space);
result.append("\"");
String referer = hreq.getHeader("referer");
if(referer != null)
result.append(referer);
else
result.append("-");
result.append("\"");
result.append(space);
result.append("\"");
String ua = hreq.getHeader("user-agent");
if(ua != null)
result.append(ua);
else
result.append("-");
result.append("\"");
}
}
//如果定义自己的默认的模式
else {
// Generate a message based on the defined pattern
boolean replace = false;
for (int i = 0; i < pattern.length(); i++) {
char ch = pattern.charAt(i);
if (replace) {
/* For code that processes {, the behavior will be ... if I
* do not enounter a closing } - then I ignore the {
*/
if ('{' == ch){
StringBuffer name = new StringBuffer();
int j = i + 1;
for(;j < pattern.length() && '}' != pattern.charAt(j); j++) {
name.append(pattern.charAt(j));
}
//如果pattern的格式是{....}.. 即"}"后面还有花括号的话,那么将解析"}"后面的这个字符来进行讨论
if (j+1 < pattern.length()) {//如果j+1小于pattern的长度
/* the +1 was to account for } which we increment now */
j++;
result.append(replace(name.toString(),
pattern.charAt(j),
request,
response));
i=j; /*Since we walked more than one character*/
} else {//如果pattern的格式是{....},那么需要对{}里面的每个字符进行讨论
//D'oh - end of string - pretend we never did this
//and do processing the "old way"
result.append(replace(ch, date, request, response, time));
}
} else {
result.append(replace(ch, date, request, response,time ));
}
replace = false;
} else if (ch == '%') {
replace = true;
} else {
result.append(ch);
}
}
}
log(result.toString(), date);//记录日记
}
----------------------------------------------------------------------------------------------------------
/**
* Return the replacement text for the specified "header/parameter".
*
* @param header The header/parameter to get
* @param type Where to get it from i=input,c=cookie,r=ServletRequest,s=Session
* @param request Request being processed
* @param response Response being processed
*/
private String replace(String header, char type, Request request,
Response response) {
Object value = null;
ServletRequest req = request.getRequest();
HttpServletRequest hreq = null;
if (req instanceof HttpServletRequest)
hreq = (HttpServletRequest) req;
//判断type
switch (type) {
case 'i':
if (null != hreq)
value = hreq.getHeader(header);//获得Header作为value返回
else
value= "??";
break;
/*
// Someone please make me work
case 'o':
break;
*/
case 'c':
Cookie[] c = hreq.getCookies();//获得cookie
for (int i=0; c != null && i < c.length; i++){
if (header.equals(c[i].getName())){//如果我们自己设置的pattern的header等于Cookie中某一个name,那么将得到Cookie中的value作为新的value返回
value = c[i].getValue();
break;
}
}
break;
case 'r':
if (null != hreq)
value = hreq.getAttribute(header);//获得pattern的header的属性作为vaule返回
else
value= "??";
break;
case 's':
if (null != hreq) {
HttpSession sess = hreq.getSession(false);
if (null != sess)
value = sess.getAttribute(header);//获得session作为value返回
}
break;
default:
value = "???";
}
/* try catch in case toString() barfs */
//将value转成字符串
try {
if (value!=null)
if (value instanceof String)
return (String)value;
else
return value.toString();
else
return "-";
} catch(Throwable e) {
return "-";
}
}
----------------------------------------------------------------------------------------------------------------------------
/**
* Return the replacement text for the specified pattern character.
*
* @param pattern Pattern character identifying the desired text
* @param date the current Date so that this method doesn't need to
* create one
* @param request Request being processed
* @param response Response being processed
*/
/*
%a
这是记录访问者的IP
%A
这是记录本地服务器的IP
%t
这就是时间啦
%r
请求的链接
%s
响应状态
%u
得到了验证的访问者,否则就是"-"
%U
访问的URL地址
%b
这是发送信息的字节数,不涵括http头,如果字节数为0的话,显示为-
%B
Bytes sent, excluding HTTP headers
%h
这个就是服务器名称了,如果resolveHosts为false的话,这里就是IP地址了
%H
访问者使用的协议,这里是HTTP/1.1
%l
Remote logical username from identd (可能这样翻译:记录浏览者进行身份验证时提供的名字)
%m
访问的方式,是GET还是POST
%v
服务器名称
%D
Time taken to process the request, in millis,应该是访问发生的时间,以毫秒记
%T
Time taken to process the request, in seconds,应该是访问发生的时间,以秒记
*/
private String replace(char pattern, Date date, Request request,
Response response, long time) {
String value = null;
ServletRequest req = request.getRequest();
HttpServletRequest hreq = null;
if (req instanceof HttpServletRequest)
hreq = (HttpServletRequest) req;
ServletResponse res = response.getResponse();
HttpServletResponse hres = null;
if (res instanceof HttpServletResponse)
hres = (HttpServletResponse) res;
if (pattern == 'a') {
value = req.getRemoteAddr();
} else if (pattern == 'A') {
try {
value = InetAddress.getLocalHost().getHostAddress();
} catch(Throwable e){
value = "127.0.0.1";
}
} else if (pattern == 'b') {
int length = response.getContentCount();
if (length <= 0)
value = "-";
else
value = "" + length;
} else if (pattern == 'B') {
value = "" + response.getContentLength();
} else if (pattern == 'h') {
value = req.getRemoteHost();
} else if (pattern == 'H') {
value = req.getProtocol();
} else if (pattern == 'l') {
value = "-";
} else if (pattern == 'm') {
if (hreq != null)
value = hreq.getMethod();
else
value = "";
} else if (pattern == 'p') {
value = "" + req.getServerPort();
} else if (pattern == 'D') {
value = "" + time;
} else if (pattern == 'q') {
String query = null;
if (hreq != null)
query = hreq.getQueryString();
if (query != null)
value = "?" + query;
else
value = "";
} else if (pattern == 'r') {
StringBuffer sb = new StringBuffer();
if (hreq != null) {
sb.append(hreq.getMethod());
sb.append(space);
sb.append(hreq.getRequestURI());
if (hreq.getQueryString() != null) {
sb.append('?');
sb.append(hreq.getQueryString());
}
sb.append(space);
sb.append(hreq.getProtocol());
} else {
sb.append("- - ");
sb.append(req.getProtocol());
}
value = sb.toString();
} else if (pattern == 'S') {
if (hreq != null)
if (hreq.getSession(false) != null)
value = hreq.getSession(false).getId();
else value = "-";
else
value = "-";
} else if (pattern == 's') {
if (hres != null)
value = "" + ((HttpResponse) response).getStatus();
else
value = "-";
} else if (pattern == 't') {
StringBuffer temp = new StringBuffer("[");
temp.append(dayFormatter.format(date)); // Day
temp.append('/');
temp.append(lookup(monthFormatter.format(date))); // Month
temp.append('/');
temp.append(yearFormatter.format(date)); // Year
temp.append(':');
temp.append(timeFormatter.format(date)); // Time
temp.append(' ');
temp.append(getTimeZone(date)); // Timezone
temp.append(']');
value = temp.toString();
} else if (pattern == 'T') {
value = timeTakenFormatter.format(time/1000d);
} else if (pattern == 'u') {
if (hreq != null)
value = hreq.getRemoteUser();
if (value == null)
value = "-";
} else if (pattern == 'U') {
if (hreq != null)
value = hreq.getRequestURI();
else
value = "-";
} else if (pattern == 'v') {
value = req.getServerName();
} else {
value = "???" + pattern + "???";
}
if (value == null)
return ("");
else
return (value);
}
-------------------------------------------------------------------------------------------------------------------
下面再来看看log方法:
/**
* Log the specified message to the log file, switching files if the date
* has changed since the previous log call.
*
* @param message Message to be logged
* @param date the current Date object (so this method doesn't need to
* create a new one)
*/
public void log(String message, Date date) {
if (rotatable){
// Only do a logfile switch check once a second, max.
long systime = System.currentTimeMillis();//获得当前的时间
if ((systime - rotationLastChecked) > 1000) {
// We need a new currentDate
currentDate = new Date(systime);
rotationLastChecked = systime;
// Check for a change of date
String tsDate = dateFormatter.format(currentDate);
// If the date has changed, switch log files
//如果当前时间不等于之前的时间,那么更新日志文件名
if (!dateStamp.equals(tsDate)) {
synchronized (this) {//需要进行同步,防止多个线程进行操作,其实servlet容器的invoke方法其实是由Connector连接器中调用servlet实例的invoke方法的,而
//Connector连接器是多线程的,那么这里自然而然需要进行同步
if (!dateStamp.equals(tsDate)) {//这里还需要进行重复一次的判断,这里十分重要,要不多线程就会不安全。好好品味一下
close();//将之前打开的文件关闭
dateStamp = tsDate;//更新时间
open();//打开文件
}
}
}
}
}
// Log this message
if (writer != null) {
writer.println(message);//向日志中写入信息
}
}
-------------------------------------------------------------------------------------------------------------
// -------------------------------------------------------- Private Methods
//关闭文件
/**
* Close the currently open log file (if any)
*/
private synchronized void close() {
if (writer == null)
return;
writer.flush();
writer.close();
writer = null;
dateStamp = "";
}
---------------------------------------------------------------------------------------------------------------
/**
* Open the new log file for the date specified by <code>dateStamp</code>.
*/
private synchronized void open() {
// Create the directory if necessary
File dir = new File(directory);
if (!dir.isAbsolute())//如果创建的文件不是绝对路径
dir = new File(System.getProperty("catalina.base"), directory);//那么通过System.getProperty("catalina.base")获得父目录,然后在此父目录创建子目录
dir.mkdirs();
// Open the current log file
try {
String pathname;
// If no rotate - no need for dateStamp in fileName
if (rotatable){//对文件名字进行命名
pathname = dir.getAbsolutePath() + File.separator +
prefix + dateStamp + suffix;
} else {
pathname = dir.getAbsolutePath() + File.separator +
prefix + suffix;
}
writer = new PrintWriter(new FileWriter(pathname, true), true);//创建文件
} catch (IOException e) {
writer = null;
}
}
这个类的主要方法就是这几个了,还剩几个生命周期的函数,这些放在生命周期那节来看。剩下就是一些小函数了,请自己查阅!