我们在项目中,一般这样使用slf4j来记录日志:
看回1处的getILoggerFactory()方法,再调用getLogget方法,知道是使用工厂模式来创建Logger对象。
下面源码StaticLoggerBinder构造方法中new了工厂对象Log4jLoggerFactory
对源码的“5处”,其中LogManager先get到log4j的Logger log4jLogger对象
看看LogManager的getLogger方法里面其实调用了getLoggerRepository(),取得LoggerRepository对象再getLogger(name)得到Logger对象的。
可以先看看getLoggerRepository()方法
7处的repositorySelector在LogManager类加载时的static语块已经new了一个DefaultRepositorySelector对象,即6处,并设置了LoggerRepository-Hierarchy对象,设置日志的DEBUG级别
去看看61处new一个Hierarchy对象:
在11处updateParents方法:
到处,就算是已经创建好log4j的Logger对象log4jLogger了。
回到源码的"51处",用log4jLogger创建一个适配器=>slf4j的Logger对象,并保存于org.slf4j.impl.Log4jLoggerFactory的ConcurrentHashMap对象loggerMap,下次则直接使用,不必再new。
[org.apache.log4j.ConsoleAppender, org.apache.log4j.DailyRollingFileAppender]
其实重点看下log4j是如何初始化的,在这里我们按配置文件是log4j.properties来看。
比如log4j.properties内容:
在上面LogManager的源码"62处",调用了OptionConverter.selectAndConfigure(...)方法来配置log4j,在这里调用了PropertyConfigurator.doConfigure方法
doConfigure方法会配置RootLogger的Appender:
org.apache.log4j.ConsoleAppender和org.apache.log4j.DailyRollingFileAppender。
所以又回到Category的callAppenders方法的11处
在13处,调用13处会调用Appender的doAppend方法,AppenderSkeleton是Appender的实现抽象类,
所以会先调用AppenderSkeleton的doAppend方法:
1.Filter.DENY,不打印日志,方法结束;
2.Filter.ACCEPT,可以打印日志,退出循环;
3.Filter.NEUTRAL,中立状态,交给下一个Filter来决定。
最后"15处"会调用append抽象方法,由AppenderSkeleton的继承类来实现append方法。
打印到控制台:
public class ConsoleAppender extends WriterAppender {}
打印到文件(日期滚动):
public class DailyRollingFileAppender extends FileAppender {}
public class FileAppender extends WriterAppender {}
public class WriterAppender extends AppenderSkeleton {}
如果使用异步日志,log4j的配置文件需要使用log4j.xml,异步日志使用的Appender是org.apache.log4j.AsyncAppender
public class AsyncAppender extends AppenderSkeleton implements AppenderAttachable {}
可以看出异步日志Appender也是继承AppenderSkeleton的,
下面我自己重写了异步日志的Appender:
https://github.com/jjavaboy/lam-nio/blob/master/lam-nio-core/src/main/java/lam/log/LamScheduleAsyncAppender.java
log4j.xml配置文件:
https://github.com/jjavaboy/lam-nio/blob/master/lam-schedule/src/main/resources/log4j.xml
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Test.class);
从这个日志Logger创建入手来看看源码:
org.slf4j.LoggerFactory.getLogger(String name){
ILoggerFactory iLoggerFactory = getILoggerFactory(); //1处
return iLoggerFactory.getLogger(name);
}
或者
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName()); //2处
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
2处也会调用getLogger(String name)方法。
看回1处的getILoggerFactory()方法,再调用getLogget方法,知道是使用工厂模式来创建Logger对象。
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) { //3处
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION: //4处
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
在3处,如果状态是未初化的,则先进行初始化,初始化成功后,在4处,使用单例模式得到StaticLoggerBinder对象,获取Logger的工厂对象。
下面源码StaticLoggerBinder构造方法中new了工厂对象Log4jLoggerFactory
private StaticLoggerBinder() {
loggerFactory = new Log4jLoggerFactory();
try {
@SuppressWarnings("unused")
Level level = Level.TRACE;
} catch (NoSuchFieldError nsfe) {
Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
}
}
所以知道在上面源码中的"1处"取到的ILoggerFactory工厂对象是org.slf4j.impl.Log4jLoggerFactory的实例,接着看看Log4jLoggerFactory.getLogger(String name)
public class Log4jLoggerFactory implements ILoggerFactory {
// key: name (String), value: a Log4jLoggerAdapter;
ConcurrentMap<String, Logger> loggerMap;
public Log4jLoggerFactory() {
loggerMap = new ConcurrentHashMap<String, Logger>();
}
public Logger getLogger(String name) {
Logger slf4jLogger = loggerMap.get(name);
if (slf4jLogger != null) {
return slf4jLogger;
} else {
org.apache.log4j.Logger log4jLogger;
if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) //ROOT_LOGGER_NAME = "ROOT"
log4jLogger = LogManager.getRootLogger();
else
log4jLogger = LogManager.getLogger(name); //5处
Logger newInstance = new Log4jLoggerAdapter(log4jLogger); //51处
Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
return oldInstance == null ? newInstance : oldInstance;
}
}
}
Log4jLoggerFacotry的Map loggerMap保存着org.slf4j.Logger的key-value映射关系。
对源码的“5处”,其中LogManager先get到log4j的Logger log4jLogger对象
public class LogManager {
/**
* @deprecated This variable is for internal use only. It will
* become package protected in future versions.
* */
static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
/**
* @deprecated This variable is for internal use only. It will
* become private in future versions.
* */
static final public String DEFAULT_CONFIGURATION_KEY="log4j.configuration";
/**
* @deprecated This variable is for internal use only. It will
* become private in future versions.
* */
static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass";
/**
* @deprecated This variable is for internal use only. It will
* become private in future versions.
*/
public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride";
static private RepositorySelector repositorySelector;
static {
// By default we use a DefaultRepositorySelector which always returns 'h'.
Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); //61处
repositorySelector = new DefaultRepositorySelector(h); //6处
/** Search for the properties file log4j.properties in the CLASSPATH. */
String override = OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY, null);
// if there is no default init override, then get the resource
// specified by the user or the default config file.
if(override == null || "false".equalsIgnoreCase(override)) {
//使用 -Dlog4j.configuration=log4j.properties 来指定log4j的配置文件
String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);
String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);
URL url = null;
// if the user has not specified the log4j.configuration
// property, we search first for the file "log4j.xml" and then
// "log4j.properties"
if(configurationOptionStr == null) {
//如果没有指定log4j的配置文件,先加载log4j.xml
url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
if(url == null) {
//没有log4j.xml,则加载log4j.properties
url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
}
} else {
try {
url = new URL(configurationOptionStr);
} catch (MalformedURLException ex) {
// so, resource is not a URL:
// attempt to get the resource from the class path
url = Loader.getResource(configurationOptionStr);
}
}
// If we have a non-null url, then delegate the rest of the
// configuration to the OptionConverter.selectAndConfigure
// method.
if(url != null) {
LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
try {
//配置log4j日志
OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository()); //62处
} 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 LoggerRepository getLoggerRepository() {
if (repositorySelector == null) { //7处
repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository());
guard = null;
Exception ex = new IllegalStateException("Class invariant violation");
String msg = "log4j called after unloading, see http://logging.apache.org/log4j/1.2/faq.html#unload.";
if (isLikelySafeScenario(ex)) {
LogLog.debug(msg, ex);
} else {
LogLog.error(msg, ex);
}
}
return repositorySelector.getLoggerRepository(); //8处
}
public static Logger getLogger(final String name) {
// Delegate the actual manufacturing of the logger to the logger repository.
return getLoggerRepository().getLogger(name);
}
}
LogManager里,先看 -Dlog4j.configuration=log4j.properties 来指定log4j的配置文件,如果没有指定log4j的配置文件,先加载log4j.xml,没有log4j.xml,则加载log4j.properties。
看看LogManager的getLogger方法里面其实调用了getLoggerRepository(),取得LoggerRepository对象再getLogger(name)得到Logger对象的。
可以先看看getLoggerRepository()方法
7处的repositorySelector在LogManager类加载时的static语块已经new了一个DefaultRepositorySelector对象,即6处,并设置了LoggerRepository-Hierarchy对象,设置日志的DEBUG级别
去看看61处new一个Hierarchy对象:
public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {
private LoggerFactory defaultFactory;
private Vector listeners;
Hashtable ht;
Logger root;
RendererMap rendererMap;
int thresholdInt;
Level threshold;
boolean emittedNoAppenderWarning = false;
boolean emittedNoResourceBundleWarning = false;
private ThrowableRenderer throwableRenderer = null;
public Hierarchy(Logger root) {
ht = new Hashtable();
listeners = new Vector(1);
this.root = root; //log4j.Logger
// Enable all level levels by default.
setThreshold(Level.ALL);
this.root.setHierarchy(this); //62处
rendererMap = new RendererMap();
defaultFactory = new DefaultCategoryFactory();
}
在62处的setHierarchy(LoggerRepository repository)方法是Category的方法,而log4j.Logger继承了Category:
// Categories need to know what Hierarchy they are in
protected LoggerRepository repository;
final void setHierarchy(LoggerRepository repository) {
this.repository = repository;
}
所以8处getLoggerRepository()得到的是Hierarchy对象。
public class DefaultRepositorySelector implements RepositorySelector {
final LoggerRepository repository;
public DefaultRepositorySelector(LoggerRepository repository) {
this.repository = repository;
}
}
所以getLoggerRepository().getLogger(name)就是调用了Hierarchy的getLogger(String name)方法,看看下面的9处
public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {
private Vector listeners;
Hashtable ht;
Logger root;
public Hierarchy(Logger root) {
ht = new Hashtable();
listeners = new Vector(1);
this.root = root;
// Enable all level levels by default.
setThreshold(Level.ALL);
this.root.setHierarchy(this);
rendererMap = new RendererMap();
defaultFactory = new DefaultCategoryFactory();
}
public Logger getLogger(String name) { //9处
return getLogger(name, defaultFactory);
}
public Logger getLogger(String name, LoggerFactory factory) {
//System.out.println("getInstance("+name+") called.");
CategoryKey key = new CategoryKey(name);
// Synchronize to prevent write conflicts. Read conflicts (in
// getChainedLevel method) are possible only if variable
// assignments are non-atomic.
Logger logger;
synchronized(ht) {
Object o = ht.get(key);
if(o == null) {
logger = factory.makeNewLoggerInstance(name); //10处
logger.setHierarchy(this);
ht.put(key, logger);
updateParents(logger); //11处
return logger;
} else if(o instanceof Logger) {
return (Logger) o;
} else if (o instanceof ProvisionNode) {
//System.out.println("("+name+") ht.get(this) returned ProvisionNode");
logger = factory.makeNewLoggerInstance(name);
logger.setHierarchy(this);
ht.put(key, logger);
updateChildren((ProvisionNode) o, logger);
updateParents(logger);
return logger;
}
else {
// It should be impossible to arrive here
return null; // but let's keep the compiler happy.
}
}
}
在10处创建org.apache.log4j.Logger对象,并保存在Hashtable ht里<name, org.apache.log4j.Logger>。
在11处updateParents方法:
private void updateParents(Logger cat) {
String name = cat.name;
int length = name.length();
boolean parentFound = false;
//System.out.println("UpdateParents called for " + name);
// if name = "w.x.y.z", loop thourgh "w.x.y", "w.x" and "w", but not "w.x.y.z"
for(int i = name.lastIndexOf('.', length-1); i >= 0;
i = name.lastIndexOf('.', i-1)) {
String substr = name.substring(0, i);
//System.out.println("Updating parent : " + substr);
CategoryKey key = new CategoryKey(substr); // simple constructor
Object o = ht.get(key);
// Create a provision node for a future parent.
if(o == null) {
//System.out.println("No parent "+substr+" found. Creating ProvisionNode.");
ProvisionNode pn = new ProvisionNode(cat);
ht.put(key, pn);
} else if(o instanceof Category) {
parentFound = true;
cat.parent = (Category) o;
//System.out.println("Linking " + cat.name + " -> " + ((Category) o).name);
break; // no need to update the ancestors of the closest ancestor
} else if(o instanceof ProvisionNode) {
((ProvisionNode) o).addElement(cat);
} else {
Exception e = new IllegalStateException("unexpected object type " +
o.getClass() + " in ht.");
e.printStackTrace();
}
}
// If we could not find any existing parents, then link with root.
if(!parentFound)
cat.parent = root;
}
看这里源码,比如为w.x.y.z.Foo类创建的Logger,则还会创建它的上一级w.x.y...
到处,就算是已经创建好log4j的Logger对象log4jLogger了。
回到源码的"51处",用log4jLogger创建一个适配器=>slf4j的Logger对象,并保存于org.slf4j.impl.Log4jLoggerFactory的ConcurrentHashMap对象loggerMap,下次则直接使用,不必再new。
public final class Log4jLoggerAdapter extends MarkerIgnoringBase implements LocationAwareLogger, Serializable {
private static final long serialVersionUID = 6182834493563598289L;
final transient org.apache.log4j.Logger logger;
/**
* Following the pattern discussed in pages 162 through 168 of "The complete
* log4j manual".
*/
final static String FQCN = Log4jLoggerAdapter.class.getName();
// Does the log4j version in use recognize the TRACE level?
// The trace level was introduced in log4j 1.2.12.
final boolean traceCapable;
// WARN: Log4jLoggerAdapter constructor should have only package access so
// that
// only Log4jLoggerFactory be able to create one.
Log4jLoggerAdapter(org.apache.log4j.Logger logger) {
this.logger = logger;
this.name = logger.getName();
traceCapable = isTraceCapable();
}
//其他省略...
}
刚开始new出Log4jLoggerAdapter对象newInstance时,它包含的org.apache.log4j.Logger logger的相关属性:
{
aai = null;
additve = true;
level = null;
parent = org.apache.log4j.spi.RootLogger;
repository = org.apache.log4j.Hierarchy;
resourceBundle = null;
}
看看Category的callAppenders方法:
public void callAppenders(LoggingEvent event) {
int writes = 0;
for(Category c = this; c != null; c=c.parent) {
// Protected against simultaneous call to addAppender, removeAppender,...
synchronized(c) {
if(c.aai != null) { //11处
writes += c.aai.appendLoopOnAppenders(event);
}
if(!c.additive) {
break;
}
}
}
if(writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
对于11处,当前log4j.Logger的aai为null,会循环回上级parent,即RootLogger,而RootLogger的AppenderAttachableImpl aai不为空。
[org.apache.log4j.ConsoleAppender, org.apache.log4j.DailyRollingFileAppender]
其实重点看下log4j是如何初始化的,在这里我们按配置文件是log4j.properties来看。
比如log4j.properties内容:
log4j.rootLogger = info, stdout, D
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %d{MM-dd HH:mm:ss}:%p(%L) %c - %m%n
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = logs/stdout.log
log4j.appender.D.DatePattern = '.'yyyy-MM-dd
log4j.appender.D.Append = true
log4j.appender.D.Encoding=UTF-8
log4j.appender.D.Threshold = INFO
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %d{MM-dd HH:mm:ss}:%p(%L) %c - %m%n
log4j.rootLogger的info是表示日志级别,stdout和D才表示日志需要输出到对应的Appender。
在上面LogManager的源码"62处",调用了OptionConverter.selectAndConfigure(...)方法来配置log4j,在这里调用了PropertyConfigurator.doConfigure方法
doConfigure方法会配置RootLogger的Appender:
void configureRootCategory(Properties props, LoggerRepository hierarchy) {
String effectiveFrefix = ROOT_LOGGER_PREFIX;
String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
if(value == null) {
value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
effectiveFrefix = ROOT_CATEGORY_PREFIX;
}
if(value == null)
LogLog.debug("Could not find root logger information. Is this OK?");
else {
Logger root = hierarchy.getRootLogger(); //12处
synchronized(root) {
parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
}
}
}
在12处取到RootLogger,调用parseCategory方法将log4j.properties的配置Appender类,加入到RootLogger中,从上面的log4j.properties文件中,可以看出会加载两个Appender
org.apache.log4j.ConsoleAppender和org.apache.log4j.DailyRollingFileAppender。
所以又回到Category的callAppenders方法的11处
if(c.aai != null) {
writes += c.aai.appendLoopOnAppenders(event);
}
appendLoopOnAppenders方法:
public int appendLoopOnAppenders(LoggingEvent event) {
int size = 0;
Appender appender;
if(appenderList != null) {
size = appenderList.size();
for(int i = 0; i < size; i++) {
appender = (Appender) appenderList.elementAt(i);
appender.doAppend(event); //13处
}
}
return size;
}
打印日志时,会循环调用Appender,即是调用log4j.properties配置的那两个Appender
在13处,调用13处会调用Appender的doAppend方法,AppenderSkeleton是Appender的实现抽象类,
所以会先调用AppenderSkeleton的doAppend方法:
public abstract class AppenderSkeleton implements Appender, OptionHandler {
public synchronized void doAppend(LoggingEvent event) {
if(closed) {
LogLog.error("Attempted to append to closed appender named ["+name+"].");
return;
}
if(!isAsSevereAsThreshold(event.getLevel())) {
return;
}
Filter f = this.headFilter;
FILTER_LOOP:
while(f != null) { //14处
switch(f.decide(event)) {
case Filter.DENY: return;
case Filter.ACCEPT: break FILTER_LOOP;
case Filter.NEUTRAL: f = f.getNext();
}
}
this.append(event); //15处
}
abstract protected void append(LoggingEvent event);
}
在上面AppenderSkeleton的doAppend方法中的"14处",会查找链条Filter(相当于责任链模式),根据Filter的返回状态:
1.Filter.DENY,不打印日志,方法结束;
2.Filter.ACCEPT,可以打印日志,退出循环;
3.Filter.NEUTRAL,中立状态,交给下一个Filter来决定。
最后"15处"会调用append抽象方法,由AppenderSkeleton的继承类来实现append方法。
打印到控制台:
public class ConsoleAppender extends WriterAppender {}
打印到文件(日期滚动):
public class DailyRollingFileAppender extends FileAppender {}
public class FileAppender extends WriterAppender {}
public class WriterAppender extends AppenderSkeleton {}
如果使用异步日志,log4j的配置文件需要使用log4j.xml,异步日志使用的Appender是org.apache.log4j.AsyncAppender
public class AsyncAppender extends AppenderSkeleton implements AppenderAttachable {}
可以看出异步日志Appender也是继承AppenderSkeleton的,
下面我自己重写了异步日志的Appender:
https://github.com/jjavaboy/lam-nio/blob/master/lam-nio-core/src/main/java/lam/log/LamScheduleAsyncAppender.java
log4j.xml配置文件:
https://github.com/jjavaboy/lam-nio/blob/master/lam-schedule/src/main/resources/log4j.xml