juli

默认情况下,Tomcat使用自身的juli作为Tomcat内部的日志处理系统.它的源码,在Tomcat源码结构下的org.apache.juli包下.

juli与JDK logger的关系

juli是继承自JDK的logger的.通过源码可以确认这一点:

public class ClassLoaderLogManager extends LogManager {
}

如果想要开启juli日志自身的调试,可以设置一个JVM系统属性org.apache.juli.ClassLoaderLogManager.debug=true即可。

juli日志构架

ClassLoaderLogManager

JULI的核心类,负责加载配置文件,以及初始化配置文件里的Handlers, Logger等信息.

可以看到,它的构造函数:

    public ClassLoaderLogManager() {
        super();
        try {
            Runtime.getRuntime().addShutdownHook(new Cleaner());
        } catch (IllegalStateException ise) {
            // We are probably already being shutdown. Ignore this error.
        }
    }

在JVM退出时,添加了一个shutdownHook,来进行日志框架自身的清理工作.它调用了LogManager.shutdown()方法.

它的公共方法有如下:

ClassLoaderLogManager # 构造函数
isUseShutdownHook # 判断是否使用shutdown勾子
setUseShutdownHook # 设置是否使用shutdown勾子
addLogger # 添加一个Logger
getLogger # 获取一个Logger
getLoggerNames # 获取所有的Logger名字
getProperty # 获取属性
readConfiguration # 读取配置文件 logging.properties,并初始化Logger
readConfiguration # 读取配置文件 logging.properties,并初始化Logger
reset # 重置所有logger
shutdown # 清理所有logger

readConfiguration(ClassLoder)

它读取logging.properties,并创建一个RootLogger(名字为”“,即空字符串,不是NULL哈).并创建一个ClassLoaderLogInfo对象(与指定的ClassLoader匹配)

创建完成后,它再调用readConfiguration(InputStream is, ClassLoader classLoader)f方法,将logging.properties作为InputStream,再传入classLoader参数来调用继续初始化其他的Logger.

readConfiguration(InputStream is, ClassLoader classLoader)

获取根据参数(classLoader)来获取ClassLoaderLogInfo对象,看它的数据结构可知,它是整个ClassLoder的所有Loger结构对象.

    protected static final class ClassLoaderLogInfo {
        final LogNode rootNode;
        final Map<String, Logger> loggers = new ConcurrentHashMap<>();
        final Map<String, Handler> handlers = new HashMap<>();
        final Properties props = new Properties();

        ClassLoaderLogInfo(final LogNode rootNode) {
            this.rootNode = rootNode;
        }

    }

包含RootLogger(它保存在rootNode节点中),以及其他Loggers,Handlers,以及logging.propertiespros对象.

经过上面的处理,我们再来看下之后的逻辑:

  1. 根据logging.properties获取.handlers属性的值来获取所有的handlers对象.以默认的Tomcat的logging.properties为例,它的配置如下:

.handlers = 1catalina.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler

.handlers:它是RootLogger默认的Handler

  1. 根据logging.properties获取handlers属性的值来获取非RootLoggerhandlers对象.Tomcat中默认有以下几个:

handlers = 1catalina.org.apache.juli.AsyncFileHandler, 2localhost.org.apache.juli.AsyncFileHandler, 3manager.org.apache.juli.AsyncFileHandler, 4host-manager.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler

  1. 如果handlers的值为空(null),则将handlers的字符串进行处理.分别获取以下的值(这里以1catalina.org.apache.juli.AsyncFileHandler为例子,其他类似)

handlerName1catalina.org.apache.juli.AsyncFileHandler

handlerClassNamecatalina.org.apache.juli.AsyncFileHandler(它会删除handlerName数字开头的部分.剩下的就是handlerClassName)

prefix1catalina.

然后创建一个指定的handlerClassNameHandler,然后保存到ClassLoaderLogInfo对象中的handlers字段里,handlers属性是一个Map<String,Hander>,它的Key就是handlerName(这里的值为1catalina.org.apache.juli.AsyncFileHandler,value就是根据handlerClassName创建出来的Handler对象.

其他的handler以此类推.

到这里,根据配置文件初始化RootLogger以及配置文件里指定的Handler都已经初始化完毕了.

LogFactory

private static final Log log = LogFactory.getLog(Bootstrap.class);

我们来看看这Log到底是如何实现的,上面这一行代码,获取了一个org.apache.juli.logging.Log对象。它是通过org.apache.juli.logging.LogFactory来获取一个实例的.整个调用栈为:

LogFactory.getLog(name) -> LogFactory.getInstance(name) -> DirectJDKLog.getInstance(name) -> new DirectJDKLog(name)

然后,在DirectJDKLog对象里,使用了JDK中的Logger来进行日志打印.

调用JDK的Logger时,会触发初始化juli的ClassLoaderLogManager(主要加载配置文件).(因为Tomcat启动时,会指定两个日志参数:

-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=/ihome/java/tomcat/apache-tomcat-8.0.36-src/conf/logging.properties

Log Level

java.util.logging.Level类记录了每个Log Level以及它们对应的int值.比较Leve时,就是通过比较它们的intValue来比较大小的.

org.apache.juli.logging.Log

它实质上是通过持有JDKjava.util.logging.Logger来实现日志打印的.所以,我们来看看这个JDK的Logger对象. 化繁为简,它主要有以下这几个(这里只是列出主要部分):

private volatile LogManager manager; # 所属的LogManager, 因为JDK里,允许自定义LogManager,即Tomcat上面启动时指定的参数, -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
private String name; # Logger的名字 
private final CopyOnWriteArrayList<Handler> handlers = new CopyOnWriteArrayList<>(); # 这个就是Logger的Handler, 日志就是通过它来控制输出的,比如输出到文件,终端,或者Socket等.这个在初始化 ClassLoaderLogManager 时加载.

更新

2016-7-7

今天再次调试了一下, 发现org.apache.juli.ClassLoaderLogManager.readConfiguration(ClassLoader classLoader)这个方法, 发现它加载的次数为:

  1. AppClassLoader.即加载以下以这些类:
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/charsets.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/deploy.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/ext/cldrdata.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/ext/dnsns.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/ext/jaccess.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/ext/jfxrt.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/ext/localedata.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/ext/nashorn.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/ext/sunec.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/ext/sunjce_provider.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/ext/sunpkcs11.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/ext/zipfs.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/javaws.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/jce.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/jfr.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/jfxswt.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/jsse.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/management-agent.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/plugin.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/resources.jar
--> url --> file:/ihome/java/jdk/jdk1.8.0_60/jre/lib/rt.jar
--> url --> file:/ihome/java/tomcat/apache-tomcat-7.0.70-src/target/classes/
--> url --> file:/home/sky/.m2/repository/org/apache/ant/ant/1.7.0/ant-1.7.0.jar
--> url --> file:/home/sky/.m2/repository/org/apache/ant/ant-launcher/1.7.0/ant-launcher-1.7.0.jar
--> url --> file:/home/sky/.m2/repository/wsdl4j/wsdl4j/1.6.2/wsdl4j-1.6.2.jar
--> url --> file:/home/sky/.m2/repository/javax/xml/jaxrpc-api/1.1/jaxrpc-api-1.1.jar
--> url --> file:/home/sky/.m2/repository/org/eclipse/jdt/core/compiler/ecj/4.5/ecj-4.5.jar
--> url --> file:/ihome/java/ide/idea-IU-145.258.11/lib/idea_rt.jar

这一次,一般会找不到logging.properties, 然后它会进入到以下判断中:

if ((is == null) && (classLoader == ClassLoader.getSystemClassLoader())) {
            String configFileStr = System.getProperty("java.util.logging.config.file");
            if (configFileStr != null) {
                try {
                    is = new FileInputStream(replace(configFileStr));
                } catch (IOException e) {
                    System.err.println("Configuration error");
                    e.printStackTrace();
                }
            }
            // Try the default JVM configuration
            if (is == null) {
                File defaultFile = new File(new File(System.getProperty("java.home"), "lib"), 
                    "logging.properties");
                try {
                    is = new FileInputStream(defaultFile);
                } catch (IOException e) {
                    System.err.println("Configuration error");
                    e.printStackTrace();
                }
            }
        }

这个就是加载Tomcat默认的logging.properties日志配置文件.

  1. URLClassLoader. 这个没有加载任何资源

  2. WebappClassLoader

这个classloder会加载webapps目录下每一个应用, 即每一个应用,都会加载一次,并且读取该应用下的classpath中的logging.properties日志配置文件.它加载的资源为:

webapps/appname/WEB-INF/classes目录和 webapps/appname/WEB-INF/lib目录

Tomcat启动时报错

SEVERE: Error listenerStart 或者 SEVERE: Error filterStart 等

可以利用上面的说明, 在出现问题的应用上,假设你的应用叫demo, 那么,就可以在目录:webapps/demo/WEB-INF/classes下添加一个名为logging.properties的文件, 文件的内容为:

handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler

############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################

org.apache.juli.FileHandler.level = FINE
org.apache.juli.FileHandler.directory = ${catalina.base}/logs
org.apache.juli.FileHandler.prefix = error-debug.

java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

这时,Tomcat就会在控制台里打印详细的报错信息了.

参考资料

CSDN qingkangxu

iteye xpenxpen