How tomcat works——7 日志记录器

概述

日志系统是一个记录信息的组件。在 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中最高级的记录器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值