Coder 爱翻译 How Tomcat Works 第七章
Chapter 7: Logger
日志是一个记录信息的组件。在Catalina中,它容器相关联的日志比其它组件相对简单得多。Tomcat在org.apache.catalina.logger包下提供多种类型的日志。
本章有三个部分:第一部分包括org.apache.catalina.Logger接口,所有的日志组件都必须实现这个接口。第二部分介绍了在Tomcat中的日志,第三章详细讲述了本章的应用程序使用Tomcat的日志。
The Logger Interface
一个日志必须实现org.apache.catalina.Logger接口:
Listing 7.1: The Logger interface 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); } Logger接口提供许多log方法供实现类选择调用。这些方法中最简单的方法是接受一个字符串,这个字符串被用来当做记录信息。
最后两个log方法接收一个详细记录级别。如果传递的这个数比这个类实例设置的详细记录级别低,这个信息就会被记录下来。否则,这个信息会被忽略。这里用public Static变量定义了5个详细记录级别:FATAL, ERROR, WARNING, INFORMATION和DEBUG。而getVerbosity和setVerbosity方法用来获取和设置相应的值。
此外,Logger接口有getContainer和setContainer方法把一个Logger实例和一个容器相关联起来。它也提供addPropertyChangeListener和removePropertyChangeListener方法,来增加和移除一个PropertyChangeListener。
当你看见在Tomcat中的logger类的实现,这些方法将会更清楚,容易理解。
Tomcat's Loggers
Tomcat提供三个日志记录类:FileLogger, SystemErrLogger和SystemOutLogger。这些类都在org.apache.catalina.logger包下面,且它们都继承自org.apache.catalina.logger.LoggerBase类。在Tomcat 4中,LoggerBase类实现了org.apache.catalina.Logger接口。在Tomcat 5中,它也实现了Lifecycle和MbeanRegistration。类图:

The LoggerBase Class
在Tomcat 5中,LoggerBase类相当复杂,因为它包含了创建MBeans的代码。这里我们看Tomcat 4中的LoggerBase类。
在Tomcat 4中,LoggerBase类是一个抽象类,它提供Logger接口的所有方法实现,除了
log(String msg)方法。
public abstract void log(String msg);
这个方法重载是在子类中记录信息。而其他所有的log方法重载都调用这个重载。因为每一个子类把记录信息记录到不同的地方,所以这个在LoggerBase类的方法重载为空。
现在看看这个类的详细记录等级。它通过一个protected的叫做verbosity的变量来定义的。ERROR是它的默认值:
protected int verbosity = ERROR;
详细记录等级可以通过调用setVerbosity方法来改变等级。传递下面的字符串:FATAL, ERROR, WARNING, INFORMATION或DEBUG。
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方法接收一个整型作为详细记录等级。在这些方法重载中,如果传递给详细记录等级的值比实例的详细记录的等级低时,log(String message)这个方法重载会被调用。
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类的三个子类,你将看到log(String message)方法重载的实现。
The SystemOutLogger Class
LoggerBase类的子类提供了log(String message)方法重载的实现。每一个接收的message会被传递给System.out.println方法。
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); } } The SystemErrLogger Class
这个类和SystemOutLogger相似。除了传递给log(String message)方法的重载的message参数的调用,message会传递给System.err.println()方法。
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); } } The FileLogger Class
FileLogger类是LoggerBase的最复杂的子类。它把接收到的信息写入一个文件。每个信息可以被标记上时间戳。当首先初始化时,这个类的实例创建一个文件,它的名字包含当天的日期信息。如果日期变化了,它将为这个新日期创建一个新的文件,然后记录下所有信息。这个类实例允许你添加一个前缀和后缀给日志文件的名字。
在Tomcat 4中。FileLogger类实现了Lifecycle接口,所以它可以被实现了org.apache.catalina.Lifecycle接口的任意组件来启动和停止它。在Tomcat 5中,LoggerBase类(FileLogger的父类)实现了Lifecycle。
在tomcat 4中LoggerBase(继承Lifecycle接口)类的start和stop方法只是触发了监听器相关的生命周期事件的启动和停止事件。注意stop方法也调用了private 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方法。
Listing 7.7: The log method public void log(String msg) { // Construct the timestamp we will use, if reguested 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方法接收一个message,并写入到日志文件。在FileLogger实例的生命周期期间,log方法可能打开和关闭多个日志文件。典型地,如果日期变化了,log方法通过关闭当前文件,并打开一个新的文件来交替日志文件。让我们看下open、close和log方法怎么工作。
The open method
open方法在制定的目录中创建一个新的log文件。
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) { 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来记录message。
writer = new PrintWriter(new FileWriter(pathname, true), true);
The close method
close方法刷新PrintWriter的writer,刷新它的内容,关闭PrintWriter,把PrintWriter设置为null,并设置日期为一个空字符串。
Listing 7.9: The close method private void close() { if (writer == null) return; writer.flush(); writer.close(); writer = null; date = ""; } The log method
log方法通过创建一个java.sql.Timestamp类的实例开始。这个Timestamp类是一个java.util.Date类的轻微包装。在log方法里实例化Timestamp类的目是为了更容易获取当前日期。log方法用传递一个用long长整型表示的当前时间给Timestamp类的构造器来构建一个TimeStamp实例。
Timestamp ts = new Timestamp(System.currentTimeMillis());
使用Timestamp的toString方法,你可以获取当前日期的字符串表现形式。toString方法的输出下面格式:
yyyy-mm-dd hh:mm: SS.fffffffff
fffffffff表示从00:00:00经过的毫秒数。要获取日期和小时,log方法调用substring方法:
String tsString = ts.toString().substring(0, 19);
然后,从tsString获取日期部分,log方法使用下面方法:
String tsDate = tsString.substring(0, 10);
然后log方法比较tsDate和初始化为空字符串的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,它给信息加上时间戳前缀(tsString)。否则,它记录的信息,不加时间戳前缀。
// Log this message, timestamped if necessary if (writer != null) { if (timestamp) { writer.println(tsString + " " + msg); } else { writer.println(msg); } }The Application
和第六章的应用程序类似。除了你有一个SimpleContext对象相关的FileLogger。
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.calalina.loggor.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(); } } }第七章 完