关于tomcat日志系统的实现:
日志对于容器来说,也是一个组件而已,其实很容易理解,对象在容器中创建、工作、销毁,所产生的信息以及错误是需要记录的,下面看看tomcat日志系统。
对于这些接口和类我们一一介绍:
Logger
public interface Logger { // ----------------------------------------------------- Manifest Constants public static final int FATAL = Integer.MIN_VALUE; public static final int ERROR = 1; public static final int WARNING = 2; public static final int INFORMATION = 3; public static final int DEBUG = 4; // ------------------------------------------------------------- Properties public Container getContainer(); public void setContainer(Container container); public String getInfo(); public int getVerbosity(); public void setVerbosity(int verbosity); // --------------------------------------------------------- Public Methods public void addPropertyChangeListener(PropertyChangeListener listener); public void log(String message); public void log(Exception exception, String msg); public void log(String message, Throwable throwable); public void log(String message, int verbosity); public void log(String message, Throwable throwable, int verbosity); public void removePropertyChangeListener(PropertyChangeListener listener); } |
接口中定义了日志级别的常量,日志级别可以通过setVerbosity(int verbosity);进行设置,并且声明了log重载方法。
LoggerBase
该类是一个抽象类,它实现了Logger接口中除log(String msg)之外的所有方法,该方法需要在子类中进行覆盖,所有的其他log方法都调用了该方法。因为每一个子类都将信息记录到不同的地方,所以该方法在LoggerBase中被留空。
现在来看该类的冗余级别。它被定义为一个 protected 的名为 verbosity 的变量,默认值为 ERROR。
protected int verbosity = ERROR;
冗余级别可以使用 setVerbosity 方法改变,传递这些字符串给方法即可 FATAL, ERROR, WARNING, INFORMATION, 或 DEBUG。Listing7.2 展示了LoggerBase 类中 setVobosity 方法的实现。
public void setVerbosityLevel(Stringverbosity) {
if("FATAL".equalsIgnoreCase(verbosity))
this.verbosity = FATAL;
else if("ERROR".egualsIgnoreCase(verbosity))
this.verbosity = ERROR;
else if("WARNING".equalsIgnoreCase(verbosity))
this.verbosity = WARNING;
else if("INFORMATION".equalsIgnoreCase(verbosity))
this.verbosity = INFORMATION;
else if("DEBUG".equalsIgnoreCase(verbosity))
this.verbosity = DEBUG;
}
有两个 log 方法会接受一个整型参数作为它的冗余级别。
public void log(String message, intverbosity) {
if (this.verbosity >= verbosity)
log(message);
}
public void log(String message, Throwablethrowable, int verbosity) {
if (this.verbosity >= verbosity)
log(message, throwable);
}
FileLogger
这个类将日志记录到文件中,它将从关联容器收到的信息写到文件中,每个信息可以选择性的加上时间戳。在第一次实例化的时候,该类的实例会创建一个文件,该文件的名字带有日期信息。如果日期改变了,它会创建一个新的文件并把信息写在里面。类的实例允许在日志文件的名字上添加前缀和后缀。在 Tomcat4 中,FileLogger 类实现了 Lifecycle 接口,所以它可以跟其它实现org.apache.catalina.Lifecycle 接口的组件一样启动和停止。在Tomcat5 中,
它是实现了 Lifecycle 接口的 LoggerBase 类的子类。Tomcat 4 中 LoggerBase 类的 start 和 stop 方法实现仅仅触发了监听器“感兴趣的”文件日志的开始和停止事件。注意 stop方法调用了该类的关闭日志文件的私有方法(close 方法)。
public void start() throwsLifecycleException {
// Validate and update our currentcomponent state
if (started)
throw new LifecycleException
(sm.getString("fileLogger.alreadyStarted"));
lifecycle.fireLifecycleEvent(START_EVENT,null);
started = true;
}
public void stop() throwsLifecycleException {
// Validate and update our currentcomponent state
if (!started)
throw new LifecycleException
(sm.getString("fileLogger.notStarted"));
lifecycle.fireLifecycleEvent(STOP__EVENT,null);
started = false;
close ();
}
FileLogger 类中最重要的方法是 log 方法,
public void log(String msg) {
// Construct the timestamp we will use, ifreguested
Timestamp ts = newTimestamp(System.currentTimeMillis());
String tsString = ts.toString().substring(0,19);
String tsDate = tsString.substring(0, 10);
// If the date has changed, switch logfiles
if (!date.equals(tsDate)) {
synchronized (this) {
if (!date.equals(tsDate)) {
close ();
date = tsDate;
open ();
}
}
}
// Log this message, timestamped ifnecessary
if (writer != null) {
if (timestamp) {
writer.println(tsString + " " +msg);
}else {
writer.println(msg);
}
}
}
log 方法接受一个消息并把消息写到日志文件中。在FileLogger 实例的生命周
期中,log 方法可以打开或关闭多个日志文件。如果日期改变了的话,log 方法
关闭当前文件并打开一个新文件。接下来看看 open、 close 和 log 这些方法是如
何工作的。
open 方法
open 方法在指定目录中创建一个新日志文件。
private void open() {
// Create the directory if necessary
File dir = new File(directory);
if (!dir.isAbsolute())
dir = new File(System.getProperty("catalina.base"),directory);
dir.mkdirs();
// Open the current log file
try {
String pathname = dir.getAbsolutePath() +File.separator +
prefix + date + suffix;
writer = new PrintWriter(newFileWriter(pathname, true), true);
}
catch (IOException e) {
writer = null;
}
}
open 方法首先应该创建日志的目录是否存在,如果目录不存在,则首先创建目
录。目录存放在该类的变量中。
File dir = new File(directory);
if (!dir.isAbsolute())
dir = newFile(System.getProperty("catalina.base"), directory);
dir.mkdirs();
然后组成该文件的路径名,由目录路径、前缀、日期和后缀组成。
try (
String pathname = dir.getAbsolutePath() +File.separator +
prefix + date + suffix;
接下来构造 java.io.PrintWriter 类的一个实例,然后将该 PringWriter 实例给
改了的变量 writer。log 方法使用 writer 来记录信息。
writer = new PrintWriter(newFileWriter(pathname, true), true);
The close method
Close 方法清空 PrintWriter 变量 writer,然后关闭PrintWriter 并将 writer
设置为 null,并将 date 设置为空字符串。
private void close() {
if (writer == null)
return;
writer.flush();
writer.close();
writer = null;
date = "";
}
log 方法
Log 方法首先创建一个java.sql.Timestamp 类的实例,该类是 java.util.Date
的瘦包装器 (thin wrapper)。初始化时间戳的目的是更容易的得到当前时间。
在该方法中,将当前时间的 long 格式传递给 Timestamp 类并构建 Timestamp 类
实例。
Timestamp ts = newTimestamp(System.currentTimeMillis());
使用 Timestamp 类的 toString 方法,可以得到当前时间的字符串表示形式,字
符串输出的形式如下格式:
yyyy-mm-dd hh:mm: SS.fffffffff
其中 fffffffff 表示从 00:00:00 开始的毫微秒。在方法中使用 subString 方法
得到日期和小时。
String tsString =ts.toString().substring(0, 19);
接下来使用如下语句得到日期:
String tsDate = tsString.substring(0, 10);
接下来 log 方法比较 tsData 和 String 变量 date 的值,如果 tsDate 和 date 的
值不同,它关闭当前日志文件,将 tsDate 的值赋给 date 并打开一个新日志文件。
// If the date has changed, switch logfiles
if (!date.equals(tsDate)) {
synchronized (this) {
if (!date.equals(tsDate)) {
close();
date = tsDate;
open();
}
}
}
最后,日志方法将 PrintWriter 实例的输出流写入到日志文件中。如果布尔变量
timestamp 的值为真,将timestamp(tsString)的值作为前缀,否则不使用前缀。
// Log this message, timestamped ifnecessary
if (writer != null) {
if (timestamp) {
writer.println(tsString + " " +msg);
}
else {
writer.println(msg);
}
}