概述
日志系统是一个记录信息的组件。在 Catalina 中,日志系统是一个跟容器相关联且相对简单的组件。Tomcat 在 org.apache.catalina.logger 包中提供了多个不同的日志系统。本章的应用程序在 ex07.pyrmont 包中。SimpleContext 和Bootstrap 是从第六章中修改得到。
本章有三节组成,第一节介绍了所有日志系统都要实现的 org.apache.catalina.Logger 接口。第二节介绍了 Tomcat 中的日志系统,第三节详细讲解了本章的例子,该例子基于 Tomcat 的日志系统。
7.1 Logger接口
一个日志系统必须实现 org.apache.catalina.Logger 接口,该接口如Listing7.1 所示
Listing 7.1: Logger 接口
package org.apache.catalina;
import java.beans.PropertyChangeListener;
public interface Logger {
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;
public Container getContainer();
public void setContainer(Container container);
public String getInfo();
public int getVerbosity();
public void setVerbosity(int verbosity);
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);
}
日志接口提供了日志系统要实现的方法,最简单的方法是接受一个字符串并将其记录,最后两个方法会接受一个冗余级别(verbosity level),如果传递的数字低于该类的实例设置的冗余级别,就将信息记录下来,否则就忽略信息。使用静态变量定义了五个冗余级别:FATAL, ERROR, WARNING, INFORMATION,和 DEBUG。getVerbosity() 和 setVerbosity() 分别用来获得和设置冗余级别。
另外,日志接口还有 getContainer() 和 setContainer() 方法用来将日志系统跟容器关联起来。还有 addPropertyChangeListener() 和removePropertyChangeListener() 方法添加和删除PropertyChangeListener。
我们看了 Tomcat 中日志系统实现之后就会清楚这些方法了。
7.2 Tomcat的日志记录器
Tomcat 提供了3种日志系统,它们分别是 FileLogger, SystemErrLogger和SystemOutLogger。这些类可以在 org.apache.catalina.logger 包中找到,它们都继承了 org.apache.catalina.logger.LoggerBase 类。在 Tomcat 4 中LoggerBase 实现了 org.apache.catalina.Logger 接口,在 Tomcat 5 中,它还实现了 Lifecycle 接口和 MBeanRegistration 接口(20 章介绍)。
它们的 UML 结构图如图 7.1 所示:
图7.1: Tomcat’s Loggers
7.2.1 LoggerBase类
在 Tomcat5 中,LoggerBase 类由于集成了 MBeans 而比较复杂,所以本章看的是Tomcat4 中的 LoggerBase 类。等讨论完第 20 章后,就好理解 Tomcat5 中的LoggerBase 类了。在 Tomcat 4 中,LoggerBase 类是一个抽象类,它实现了 Logger 接口中除log(String msg)之外的所有方法:
public abstract void log(String msg);
该方法需要在子类进行覆盖(overload),所有的其它的 log() 方法都调用了该方法。因为每一个子类都将信息记录到不同的地方,所以该方法在LoggerBase 中北留空。
现在来看该类的冗余级别。它被定义为一个protected修饰的verbosity的变量,默认值为 ERROR。
protected int verbosity = ERROR;
冗余级别可以使用 setVerbosity() 方法改变,传递这些字符串给方法即可 FATAL,ERROR, WARNING, INFORMATION或 DEBUG。Listing7.2 展示了 LoggerBase 类setVerbosity()方法:
Listing 7.2: The setVerbosity method
public void setVerbosityLevel(String verbosity) {
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(String message)重载方法。 Listing 7.3提供了这些方法重载:
Listing 7.3: The log method overloads that accept verbosity
public void log(String message, int verbosity) {
if (this.verbosity >= verbosity)
log(message);
}
public void log(String message, Throwable throwable, int verbosity) {
if (this.verbosity >= verbosity)
log(message, throwable);
}
在后边小节介绍的 LoggerBase 的3个子类中,可以看到 log(String message)方法重载实现。
7.2.2 SystemOutLogger类
SystemOutLogger 作为 LoggerBase 的子类提供了 log(String message)方法的实现。每一次收到的信息都被传递给 System.out.println()方法,SystemOutLogger类如 Listing7.4 所示。
Listing 7.4: The SystemOutLogger Class
package org.apache.catalina.logger;
public class SystemOutLogger extends LoggerBase {
protected static final String info ="org.apache.catalina.logger.SystemOutLogger/1.0";
public void log(String msg) {
System.out.println(msg);
}
}
7.2.3 SystemErrLogger类
SystemErrLogger 类跟 SystemOutLogger 类十分相似,只是它覆盖 log(String message)方法时使用是 System.erro.println()方法。SystemErrLogger类如 Listing7.5。
Listing 7.5: The SystemErrLogger class
package org.apache.catalina.logger;
public class SystemErrLogger extends LoggerBase {
protected static final String info = "org.apache.catalina.logger.SystemErrLogger/1.0";
public void log(String msg) {
System.err.println(msg);
}
}
7.2.4 FileLogger类
FileLogger 是 LoggerBase 类的子类中最复杂而精致的。它将从关联容器收到的信息写到文件中,每个信息可以选择性的加上时间戳。在第一次实例化时,该类的实例会创建一个文件,该文件的名字带有日期信息。如果日期改变了,它会创建一个新的文件并把信息写在里面。类的实例允许在日志文件的名字上添加前缀和后缀。
在 Tomcat4 中,FileLogger 类实现了 Lifecycle 接口,所以它可以跟其它实现org.apache.catalina.Lifecycle 接口的组件一样启动和停止。在 Tomcat5 中,它是实现了 Lifecycle 接口的 LoggerBase 类的子类。
Tomcat 4 中 LoggerBase 类的 start() 和stop() 方法仅仅实现触发了监听器“感兴趣的”文件日志的开始和停止事件。这两个方法如 Listing7.6 所示,注意 stop()方法调用了该类的关闭日志文件的私有方法close()方法。关闭方法会在本节后边的内容中介绍。
Listing 7.6: The start and stop methods
public void start() throws LifecycleException {
// Validate and update our current component state
if (started)
throw new LifecycleException (sm.getString("fileLogger.alreadyStarted"));
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
}
public void stop() throws LifecycleException {
// Validate and update our current component state
if (!started)
throw new LifecycleException(sm.getString("fileLogger.notStarted"));
lifecycle.fireLifecycleEvent(STOP__EVENT, null);
started = false;
close ();
}
FileLogger 类中最重要的方法是 log()方法,如 Listing7.7 所示。
Listing 7.7: The log method
public void log(String msg) {
// Construct the timestamp we will use, if requested
Timestamp ts = new Timestamp(System.currentTimeMillis());
String tsString = ts.toString().substring(0, 19);
String tsDate = tsString.substring(0, 10);
// If the date has changed, switch log files
if (!date.equals(tsDate)) {
synchronized (this) {
if (!date.equals(tsDate)) {
close();
date = tsDate;
open();
}
}
}
// Log this message, timestamped if necessary
if (writer != null) {
if (timestamp) {
writer.println(tsString + " " + msg);
} else {
writer.println(msg);
}
}
}
log()方法接受一个消息并把消息写到日志文件中。在 FileLogger 实例的生命周期中,log() 方法可以打开或关闭多个日志文件。典型的如,如果日期改变了的话,log()方法则关闭当前文件并打开一个新文件。接下来,让我们看看 open()、close() 和 log() 这些方法是如何工作的。
7.2.4.1 open()方法
如 Listing7.8 所示,open()方法在指定目录中创建一个新日志文件。
Listing 7.8: The open method
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(new FileWriter(pathname, true), true);
} catch (IOException e) {
writer = null;
}
}
open()方法首先检查假设它应该在其中创建日志的目录文件已存在。 如果目录不存在,该方法也会创建目录。 目录地址存储在类变量directory中。
File dir = new File(directory);
if (!dir.isAbsolute())
dir = new File(System.getProperty("catalina.base"), directory);
dir.mkdirs();
然后组成该文件的路径名,由目录路径、前缀、日期和后缀组成:
try (
String pathname = dir.getAbsolutePath() + File.separator +prefix + date + suffix;
接下来,它构造一个java.io.PrintWriter的实例,该实例参数writer是一个含写入路径名的java.io.FileWriter对象。 然后PrintWriter实例分配给类变量writer。 log()方法使用writer来记录消息。
writer = new PrintWriter(new FileWriter(pathname, true), true);
7.2.4.2 close()方法
close()方法清空 PrintWriter 变量 writer,然后关闭 PrintWriter 并将 writer设置为 null,并将 date 设置为空字符串。该方法如 Listing7.9。
Listing 7.9: The close method
private void close() {
if (writer == null)
return;
writer.flush();
writer.close();
writer = null;
date = "";
}
7.2.4.3 log()方法
log()方法首先创建一java.sql.Timestamp 类实例,该类是 java.util.Date的瘦包装器 (Date对象比较重)。初始化Timestamp的目的是为了更容易得到当前时间。在该方法中,将当前时间的 long 格式传递给 Timestamp 类并构建 Timestamp 类实例:
Timestamp ts = new Timestamp(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);
接下来比较 tsData 和 String 变量 date 的值,如果 tsDate 和 date 值不同,它关闭当前日志文件,将tsDate值赋给date并打开一个新日志文件。
// If the date has changed, switch log files
if (!date.equals(tsDate)) {
synchronized (this) {
if (!date.equals(tsDate)) {
close();
date = tsDate;
open();
}
}
}
最后,log()方法将 PrintWriter 实例的输出流写入到日志文件中。如果布尔变量timestamp 的值为true,将 timestamp(tsString)的值作为前缀,否则不使用前缀:
// Log this message, timestamped if necessary
if (writer != null) {
if (timestamp) {
writer.println(tsString + " " + msg);
}else {
writer.println(msg);
}
}
7.3 应用Demo
该章的应用程序跟第6章的程序很相似,只是多了个跟 SimpleContext 对象相关联的 FileLogger。这个改变可以在 ex07.pyrmont.startup.Bootstrap 类的main()方法里找到,如 Listing7.10 所示。注意要仔细看高亮的代码。
Listing 7.10: The Bootstrap class
package ex07.pyrmont.startup;
import ex07.pyrmont.core.SimpleContext;
import ex07.pyrmont.core.SimpleContextLifecycleListener;
import ex07.pyrmont.core.SimpleContextMapper;
import ex07.pyrmont.core.SimpleLoader;
import ex07.pyrmont.core.SimpleWrapper;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.logger.FileLogger;
import org.apache.catalina.Mapper;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
public final class Bootstrap {
public static void main(String[] args) {
Connector connector = new HttpConnector();
Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");
Loader loader = new SimpleLoader();
Context context = new SimpleContext();
context.addChild(wrapper1);
context.addChild(wrapper2);
Mapper mapper = new SimpleContextMapper();
mapper.setProtocol("http");
LifecycleListener listener = new SimpleContextLifecycleListener();
((Lifecycle) context).addLifecycleListener(listener);
context.addMapper(mapper);
context.setLoader(loader);
// context.addServletMapping(pattern, name);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");
// ------ add logger --------
System.setProperty("catalina.base", System.getProperty("user.dir"));
FileLogger logger = new FileLogger();
logger.setPrefix("FileLog_");
logger.setSuffix(".txt");
logger.setTimestamp(true);
logger.setDirectory("webroot");
context.setLogger(logger);
//---------------------------
connector.setContainer(context);
try {
connector.initialize();
((Lifecycle) connector).start();
((Lifecycle) context).start();
// make the application wait until we press a key.
System.in.read();
((Lifecycle) context).stop();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
7.4 小结
在本章中,我们了解学习了记录器组件,审查介绍了org.apache.catalina.Logger接口,并仔细查看了Tomcat中3个对此接口的实现类。 另外,本章应用Demo演示使用了FileLogger类——在Tomcat中最高级的记录器。