日志框架Log4j源码解析(2)
上一篇Log4j源码解析我们分析了Logger、Appender。相信读者应该已经搞清楚了这两者的对应关系,一个是面向用户(Logger),一个是面向输出(控制台、文件、远程)。今天我们来看看根据配置文件进行初始化。首先我们先看一段Log4j的配置。
log4j.rootLogger=debug,stdout,info,debug,warn,error
#log4j.threshold=fatal
#console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n
log4j.appender.info=org.apache.log4j.DailyRollingFileAppender
log4j.appender.info.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.info.File=./log/info.log
log4j.appender.info.Append=true
log4j.appender.info.Threshold=INFO
log4j.appender.info.layout=org.apache.log4j.PatternLayout
log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender
log4j.appender.debug.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.debug.File=./log/debug.log
log4j.appender.debug.Append=true
log4j.appender.debug.Threshold=DEBUG
log4j.appender.debug.layout=org.apache.log4j.PatternLayout
log4j.appender.debug.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
log4j.appender.warn=org.apache.log4j.DailyRollingFileAppender
log4j.appender.warn.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.warn.File=./log/warn.log
log4j.appender.warn.Append=true
log4j.appender.warn.Threshold=WARN
log4j.appender.warn.layout=org.apache.log4j.PatternLayout
log4j.appender.warn.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
#error
log4j.appender.error = org.apache.log4j.DailyRollingFileAppender
log4j.appender.error.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.error.File = ./log/error.log
log4j.appender.error.Append = true
log4j.appender.error.Threshold = ERROR
log4j.appender.error.layout = org.apache.log4j.PatternLayout
log4j.appender.error.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
log4j.logger.org.apache.log4j.LogManager=error,test
log4j.appender.test = com.erely.appender.MyAppender
log4j.appender.test.Append=true
log4j.appender.test.layout = org.apache.log4j.PatternLayout
log4j.appender.test.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
log4j.appender.test.filter.MyFilter=com.erely.filter.MyFilter
我们先简单分析一下配置文件,首先是log4j.rootLogger节点。这个节点也是一个Logger,是所有Logger的父亲,如果一个Logger没有配置Appender就会默认继承父类的Appender,根节点的值的第一个是Logger的日志级别,后面的就是定义的一些Appender,比如info
log4j.appender.info=org.apache.log4j.DailyRollingFileAppender
log4j.appender.info.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.info.File=./log/info.log
log4j.appender.info.Append=true
log4j.appender.info.Threshold=INFO
log4j.appender.info.layout=org.apache.log4j.PatternLayout
log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
这里边就定义了实现类,对应的文件、格式化、格式化正则表达等内容。在加载的时候应该是先找到rootLogger设置级别然后添加对应的Appender。可能我们要对某个类打日志单独处理。可以看到最后面加了一个LogManager的类的Logger,和rootLogger一样理解就可以,应该是比较简单的。接下来我们就通过源码来分析。在Log4j中获取Logger是通过LogManager来获取的。调用这个类的使用,该类的静态方法对配置文件进行了解析。在分析这些代码之前我们先看一看LoggerRepository接口的实现类Hierarchy
private LoggerFactory defaultFactory; //Logger工厂
private Vector listeners; //监听集合
Hashtable ht; //存储Logger实例
Logger root; //根Logger
RendererMap rendererMap;
int thresholdInt; //级别int
Level threshold;//日志级别
boolean emittedNoAppenderWarning = false; //Appender为空是否警告完毕 只会警告一次
boolean emittedNoResourceBundleWarning = false; //国际化资源为空是否警告
private ThrowableRenderer throwableRenderer = null; //
public
Logger getLogger(String name, LoggerFactory factory) {
CategoryKey key = new CategoryKey(name); //通过name创建一个位置的Key
Logger logger;
synchronized(ht) { //线程安全
Object o = ht.get(key); //从ht中获取Logger
if(o == null) { //如果没有找到
logger = factory.makeNewLoggerInstance(name); //建造新的logger实例
logger.setHierarchy(this); //设置Logger中的Logger容器为当前类
ht.put(key, logger);//将该Logger放入ht中
updateParents(logger); //设置Logger的父类
return logger; //返回
} else if(o instanceof Logger) { //如果找到了直接返回
return (Logger) o;
} else if (o instanceof ProvisionNode) { //如果获取到的类是在创建父Logger的时候使用的占用符,同时父Loggername对应的是子Logger的实例在这里需要设置对应的关系
logger.setHierarchy(this);
ht.put(key, logger);
updateChildren((ProvisionNode) o, logger); //设置子Logger
updateParents(logger); //更新父Logger
return logger;
}
else {
return null;
}
}
}
final private void updateParents(Logger cat) {
String name = cat.name;
int length = name.length();
boolean parentFound = false;
for(int i = name.lastIndexOf('.', length-1); i >= 0;
i = name.lastIndexOf('.', i-1)) { //按.进行循环每次获取前一个
String substr = name.substring(0, i);
CategoryKey key = new CategoryKey(substr); // 构建父Logger的name
Object o = ht.get(key); //获取
if(o == null) { //如果为空
ProvisionNode pn = new ProvisionNode(cat); //创建一个占用
ht.put(key, pn); //将父key和子Logger放入ht中
} else if(o instanceof Category) { //如果存在父Logger则设置
parentFound = true;
cat.parent = (Category) o;
break; // no need to update the ancestors of the closest ancestor
} else if(o instanceof ProvisionNode) { //如果父Logger已经是ProvisionNode类型,则继续添加
((ProvisionNode) o).addElement(cat);
} else {
Exception e = new IllegalStateException("unexpected object type " +
o.getClass() + " in ht.");
e.printStackTrace();
}
}
if(!parentFound) //如果没有 则设置父Logger为根节点
cat.parent = root;
}
static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties"; //默认配置文
static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml"; //xml配置文件
static final public String DEFAULT_CONFIGURATION_KEY="log4j.configuration"; //配置key
static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass";
public static final String DEFAULT_INIT_OVERRIDE_KEY =
"log4j.defaultInitOverride";
static private Object guard = null;
static private RepositorySelector repositorySelector; //Logger仓库选择器
static {
// By default we use a DefaultRepositorySelector which always returns 'h'.
Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); //初始化默认级别debug 初始化Logger容器
repositorySelector = new DefaultRepositorySelector(h);//创建Logger容器选择器
String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,
null); //查找系统变量中log4j.defaultInitOverride的值
if(override == null || "false".equalsIgnoreCase(override)) {
String configurationOptionStr = OptionConverter.getSystemProperty(
DEFAULT_CONFIGURATION_KEY,
null);//
String configuratorClassName = OptionConverter.getSystemProperty(
CONFIGURATOR_CLASS_KEY,
null);
URL url = null;
//看这里
if(configurationOptionStr == null) { //如果log4j.configuration为空 则去查找xml文件
url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE); //载入log4j.xml
if(url == null) {
url = Loader.getResource(DEFAULT_CONFIGURATION_FILE); //如果没有则载入log4j.properties文件
}
} else { //如果配置了log4j.configuration内容
try {
url = new URL(configurationOptionStr); //查找内容
} catch (MalformedURLException ex) {
url = Loader.getResource(configurationOptionStr);
}
}
if(url != null) { //如果文件URL不为空
LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
try {
//加载方法 入口
OptionConverter.selectAndConfigure(url, configuratorClassName,
LogManager.getLoggerRepository()); //处理配置文件 TODO
} catch (NoClassDefFoundError e) {
LogLog.warn("Error during default initialization", e);
}
} else {
LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");
}
} else {
LogLog.debug("Default initialization of overridden by " +
DEFAULT_INIT_OVERRIDE_KEY + "property.");
}
}
static
public
void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
Configurator configurator = null; //配置接口
String filename = url.getFile(); //获取文件名
if(clazz == null && filename != null && filename.endsWith(".xml")) { //如果文件是xml结尾并且配置类为空设置默认的解析类
clazz = "org.apache.log4j.xml.DOMConfigurator";
}
if(clazz != null) { //使用自定义加载器加载或者xml加载器
LogLog.debug("Preferred configurator class: " + clazz);
configurator = (Configurator) instantiateByClassName(clazz,
Configurator.class,
null);
if(configurator == null) {
LogLog.error("Could not instantiate configurator ["+clazz+"].");
return;
}
} else {
configurator = new PropertyConfigurator(); //配置属性具体实现
}
//主要看这个
configurator.doConfigure(url, hierarchy); //加载配置文件
}
public
void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
Properties props = new Properties();
LogLog.debug("Reading configuration from URL " + configURL);
InputStream istream = null;
URLConnection uConn = null;
try {
uConn = configURL.openConnection();
uConn.setUseCaches(false);
istream = uConn.getInputStream();
props.load(istream); //载入所有属性
}
catch (Exception e) {
if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
LogLog.error("Could not read configuration file from URL [" + configURL
+ "].", e);
LogLog.error("Ignoring configuration file [" + configURL +"].");
return;
}
finally {
if (istream != null) {
try {
istream.close();
} catch(InterruptedIOException ignore) {
Thread.currentThread().interrupt();
} catch(IOException ignore) {
} catch(RuntimeException ignore) {
}
}
}
//加载入口
doConfigure(props, hierarchy); //处理配置
}
public
void doConfigure(Properties properties, LoggerRepository hierarchy) {
repository = hierarchy;
String value = properties.getProperty(LogLog.DEBUG_KEY); //获取log.debug key的值 (log4j内部是否debug打印日志),如果为ture打印,false不打印
if(value == null) {
value = properties.getProperty("log4j.configDebug");
if(value != null)
LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
}
if(value != null) { //设置标志
LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true)); //设置内部日志是否打印
}
//
// if log4j.reset=true then
// reset hierarchy
String reset = properties.getProperty(RESET_KEY); //获取log4j.reset 如果设置为true,重置logger容器
if (reset != null && OptionConverter.toBoolean(reset, false)) {
hierarchy.resetConfiguration(); //重置Logger容器 默认创建rootLogger
}
String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
properties);//设置logger容器总阀值,低于阀值将不打印日志。如果没有配置,默认设置为最低级别ALL
if(thresholdStr != null) {
hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
(Level) Level.ALL)); //设置总阈值
LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
}
//上面都是在处理内部打印日志 相关
configureRootCategory(properties, hierarchy); //处理rootLogger appender
configureLoggerFactory(properties); //处理Logger工厂
parseCatsAndRenderers(properties, hierarchy); //处理其他节点 非根节点
LogLog.debug("Finished configuring.");
// We don't want to hold references to appenders preventing their
// garbage collection.
registry.clear();
}
void configureRootCategory(Properties props, LoggerRepository hierarchy) {
String effectiveFrefix = ROOT_LOGGER_PREFIX; //log4j.rootLogger前缀
String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props); //从配置文件从获取值
if(value == null) {
value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props); //获取值log4j.rootCategory
effectiveFrefix = ROOT_CATEGORY_PREFIX; //设置前缀
}
if(value == null)
LogLog.debug("Could not find root logger information. Is this OK?");
else {
Logger root = hierarchy.getRootLogger(); //获取跟Logger
synchronized(root) {
parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value); //处理属性
}
}
}
void parseCategory(Properties props, Logger logger, String optionKey,
String loggerName, String value) {
LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
StringTokenizer st = new StringTokenizer(value, ","); //,分隔
if(!(value.startsWith(",") || value.equals(""))) {
if(!st.hasMoreTokens())
return;
String levelStr = st.nextToken(); //获取下一个值(这里是第一个)
LogLog.debug("Level token is [" + levelStr + "].");
if(INHERITED.equalsIgnoreCase(levelStr) ||
NULL.equalsIgnoreCase(levelStr)) {
if(loggerName.equals(INTERNAL_ROOT_NAME)) {
LogLog.warn("The root logger cannot be set to null.");
} else {
logger.setLevel(null);
}
} else {
logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG)); //设置根Logger日志级别
}
LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
}
logger.removeAllAppenders(); //移除所有已存在的Appender
Appender appender;
String appenderName;
while(st.hasMoreTokens()) { //遍历所有值
appenderName = st.nextToken().trim();
if(appenderName == null || appenderName.equals(","))
continue;
LogLog.debug("Parsing appender named \"" + appenderName +"\".");
appender = parseAppender(props, appenderName); //解析appender
if(appender != null) {
logger.addAppender(appender); //Logger添加所有Appender
}
}
}
Appender parseAppender(Properties props, String appenderName) {
Appender appender = registryGet(appenderName); //从注册器中获取appender
if((appender != null)) { //如果不为空则说明已经有直接返回
LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
return appender;
}
// Appender was not previously initialized.
String prefix = APPENDER_PREFIX + appenderName; //拼接前缀log4j.appender.+ 配置的名字
String layoutPrefix = prefix + ".layout"; //log4j.appender.+ 配置的名字+ .layout 获取打印规则
appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
org.apache.log4j.Appender.class,
null); //创建appender
if(appender == null) { //如果找不到直接返回空并打印错误日志
LogLog.error(
"Could not instantiate appender named \"" + appenderName+"\".");
return null;
}
appender.setName(appenderName); //设置Appender名字
if(appender instanceof OptionHandler) {
if(appender.requiresLayout()) {
Layout layout = (Layout) OptionConverter.instantiateByKey(props,
layoutPrefix,
Layout.class,
null); //获取Layout类
if(layout != null) {
appender.setLayout(layout); //appender设置Layout
LogLog.debug("Parsing layout options for \"" + appenderName +"\".");
//configureOptionHandler(layout, layoutPrefix + ".", props);
PropertySetter.setProperties(layout, props, layoutPrefix + ".");
LogLog.debug("End of parsing for \"" + appenderName +"\".");
}
}
final String errorHandlerPrefix = prefix + ".errorhandler"; //获取错误处理
String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
if (errorHandlerClass != null) {
ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
errorHandlerPrefix,
ErrorHandler.class,
null);
if (eh != null) {
appender.setErrorHandler(eh); //Appender设置错误处理类
LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\".");
parseErrorHandler(eh, errorHandlerPrefix, props, repository);
final Properties edited = new Properties();
final String[] keys = new String[] {
errorHandlerPrefix + "." + ROOT_REF,
errorHandlerPrefix + "." + LOGGER_REF,
errorHandlerPrefix + "." + APPENDER_REF_TAG
};
for(Iterator iter = props.entrySet().iterator();iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
int i = 0;
for(; i < keys.length; i++) {
if(keys[i].equals(entry.getKey())) break;
}
if (i == keys.length) {
edited.put(entry.getKey(), entry.getValue());
}
}
PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
LogLog.debug("End of errorhandler parsing for \"" + appenderName +"\".");
}
}
//configureOptionHandler((OptionHandler) appender, prefix + ".", props);
PropertySetter.setProperties(appender, props, prefix + "."); //设置相关属性
LogLog.debug("Parsed \"" + appenderName +"\" options.");
}
parseAppenderFilters(props, appenderName, appender);//设置过滤器
registryPut(appender); //这里将生成的appender放入Hashtable,以便于下次获取,不用重新生成
return appender;
}
protected void configureLoggerFactory(Properties props) {
String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,
props); //获取log4j.loggerFactory工厂
if(factoryClassName != null) {
LogLog.debug("Setting category factory to ["+factoryClassName+"].");
loggerFactory = (LoggerFactory)
OptionConverter.instantiateByClassName(factoryClassName,
LoggerFactory.class,
loggerFactory);
PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + "."); //设置工厂类及属性
}
}
protected
void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
Enumeration enumeration = props.propertyNames(); //获取所有属性
while(enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement(); //获取属性名
if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) { //如果是以log4j.category.或log4j.logger.开始
String loggerName = null;
if(key.startsWith(CATEGORY_PREFIX)) {
loggerName = key.substring(CATEGORY_PREFIX.length()); //获取得到LoggerName
} else if(key.startsWith(LOGGER_PREFIX)) {
loggerName = key.substring(LOGGER_PREFIX.length());
}
String value = OptionConverter.findAndSubst(key, props); //获取值
Logger logger = hierarchy.getLogger(loggerName, loggerFactory); //通过loggerName获取Logger
synchronized(logger) {
parseCategory(props, logger, key, loggerName, value); //处理Logger 和根节点一样处理
parseAdditivityForLogger(props, logger, loggerName); //是否继承父类的Appender
}
} else if(key.startsWith(RENDERER_PREFIX)) { //如果log4j.renderer.开始 这个是处理自定义的消息实体
String renderedClass = key.substring(RENDERER_PREFIX.length());
String renderingClass = OptionConverter.findAndSubst(key, props);
if(hierarchy instanceof RendererSupport) {
RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
renderingClass);
}
} else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
if (hierarchy instanceof ThrowableRendererSupport) {
ThrowableRenderer tr = (ThrowableRenderer)
OptionConverter.instantiateByKey(props,
THROWABLE_RENDERER_PREFIX,
org.apache.log4j.spi.ThrowableRenderer.class,
null);
if(tr == null) {
LogLog.error(
"Could not instantiate throwableRenderer.");
} else {
PropertySetter setter = new PropertySetter(tr);
setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
}
}
}
}
}
上面提到消息实体,一般情况下我们打印日志的消息是String类型的,但是如果有一些特殊要求,当消息不再是String类型的而是某个自定义的类。那么我们要对这个消息进行处理就是上面log4j.renderer.来进行配置,最后还有一个是异常的这里笔者就不介绍了有兴趣的读者可以去看看源码。
到这里我们已经对整个加载过程做了分析,整体过程为
1.初始话Logger容器类,找到对应的配置文件。
2.根据配置文件的类型使用不同的处理器去处理配置文件。
3.先处理内部日志打印相关信息,然后在处理用户日志相关信息
4.处理rootLogger,主要包括设置日志级别、处理相关联的Appender
5.处理日志工厂(功能是创建Logger)。
6.处理另外的属性包括用户定义的Logger、自定义消息处理器Render、异常等。