【log4j】【2】【log4j的原理】使用eclipse和jvm命令分析log4j的原理(1)
一、准备工作
安装好java,maven,eclipse.已有集成log4j的maven项目(使用maven可以便于加载和管理jar包,集成插件),log4j源码或者反编译软件(我使用的是反编译插件jad,集成到Eclipse)
二、查看类加载日志,方便寻找核心类
2.1 eclipse打印出日志
首先写好一个测试log4j的测试类
import org.apache.log4j.Logger;
import org.junit.Test;
public class Log4jTest {
@Test
public void maintest() {
Logger log = Logger.getLogger(Log4jTest.class);
log.debug("Hello world");
log.info("info test threshold");
}
}
使用junit4运行,运行之前加上jvm命令 -verbose,具体操作如图
右击>debug as>debug configuratio...
点击debug运行,可以在console上看到输出的日志
将输出的日志放到文本编辑器上(我是用的是Notepad++)
根据输出的日志查看分析大概了解到核心类是,Logger,LogManager(与logger同包,所以可以new Logger,Logger的构造器是protected),Level,Appender(设置输出位置),Layout(格式化输出)。
三、根据得到的核心类,画一个UML类图,时序图。
3.1 类图
3.2 时序图
四、核心代码逐个分析,代码逻辑
4.1 Logger分析
Logger继承Category,Category实现AppenderAttachable,Category和Logger的构造器都是protected,所以构造Logger的实例是用LogManage的方法实现的。
Logger与Appender的关联关系 是通过类AppenderAttachableImpl里面的成员变量Vector容器存放Appender实现的
public class AppenderAttachableImpl implements AppenderAttachable {
/** Array of appenders. */
protected Vector appenderList;
......
public
void addAppender(Appender newAppender) {
// Null values for newAppender parameter are strictly forbidden.
if(newAppender == null)
return;
if(appenderList == null) {
appenderList = new Vector(1);
}
if(!appenderList.contains(newAppender))
appenderList.addElement(newAppender);
}
Logger的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) {
writes += c.aai.appendLoopOnAppenders(event);
}
if(!c.additive) {
break;
}
}
}
if(writes == 0) {
repository.emitNoAppenderWarning(this);
}
}
LoggingEvent携带了日志的所有信息,取出aai中的Appender,将event给到appender让其处理
如果当前类aai为空,就会去找其父节点的Category,直到找到RootLogger。如果没有处理event信息,就会记录没有appender的错误。
/**
Log a localized and parameterized message. First, the user
supplied <code>key</code> is searched in the resource
bundle. Next, the resulting pattern is formatted using
{@link java.text.MessageFormat#format(String,Object[])} method with the
user supplied object array <code>params</code>.
@since 0.8.4
*/
public
void l7dlog(Priority priority, String key, Object[] params, Throwable t) {
if(repository.isDisabled(priority.level)) {
return;
}
if(priority.isGreaterOrEqual(this.getEffectiveLevel())) {
String pattern = getResourceBundleString(key);
String msg;
if(pattern == null)
msg = key;
else
msg = java.text.MessageFormat.format(pattern, params);
forcedLog(FQCN, priority, msg, t);
}
}
l7dlog方法,记录国际化日志,getResourceBundleString(String key)是根据key获取国际化内容的一个方法。
Logger类里面包含了对于Logger的关联,RootLogger继承Logger,也是Logger的根节点。
public final class RootLogger extends Logger {
public RootLogger(Level level) {
super("root");
setLevel(level);
}
public final Level getChainedLevel() {
return level;
}
public final void setLevel(Level level) {
if (level == null) {
LogLog.error(
"You have tried to set a null level to root.", new Throwable());
} else {
this.level = level;
}
}
}
RootLogger是final类不可被继承,并且Level一定要有值,避免再向上找父类,而且name和Level都有值。
LogManager在静态代码块将文件加载进来,并且将key=value,存放在Hierarchy中。经OptionConverter.selectAndConfigure将文件解析,并且将配置放到LogManager
中的静态私有属性repositorySelector中。
static {
// By default we use a DefaultRepositorySelector which always returns 'h'.
Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
repositorySelector = new DefaultRepositorySelector(h);
/** 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)) {
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) {
url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
if(url == null) {
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 {
OptionConverter.selectAndConfigure(url, configuratorClassName,
LogManager.getLoggerRepository());
} 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.");
}
}
Level分析
Level继承Priority,两者的构造器都是protected,Level这几个属性分别代表,级别,名字,对应的系统Level值(我并没有看出来,根据名字直译过来的)。另外,由于对相同级别的Level实例来说,它必须是单例的,因而Log4J对序列化和反序列化做了一些处理。即它的三个成员都是transient,真正序列化和反序列化的代码自己写,并且加入readResolve()方法的支持,以保证反序列化出来的相同级别的Level实例是相同的实例。【摘自 http://www.blogjava.net/DLevin/archive/2012/06/28/381667.html】
transient int level;
transient String levelStr;
transient int syslogEquivalent;
final static public Level OFF = new Level(OFF_INT, "OFF", 0);
final static public Level FATAL = new Level(FATAL_INT, "FATAL", 0);
final static public Level ERROR = new Level(ERROR_INT, "ERROR", 3);
final static public Level WARN = new Level(WARN_INT, "WARN", 4);
final static public Level INFO = new Level(INFO_INT, "INFO", 6);
final static public Level DEBUG = new Level(DEBUG_INT, "DEBUG", 7);
public static final Level TRACE = new Level(TRACE_INT, "TRACE", 7);
final static public Level ALL = new Level(ALL_INT, "ALL", 7);
/**
* Resolved deserialized level to one of the stock instances.
* May be overriden in classes derived from Level.
* @return resolved object.
* @throws ObjectStreamException if exception during resolution.
*/
private Object readResolve() throws ObjectStreamException {
//
// if the deserizalized object is exactly an instance of Level
//
if (getClass() == Level.class) {
return toLevel(level);
}
//
// extension of Level can't substitute stock item
//
return this;
}
级别从高到低,OFF是不记录,ALL,表示都记录
Appender,可以添加过滤器,错误处理类,输出类,并且对其处理
public interface Appender {
void addFilter(Filter newFilter);
public
Filter getFilter();
public
void clearFilters();
public
void close();
public
String getName();
public
void setErrorHandler(ErrorHandler errorHandler);
public
ErrorHandler getErrorHandler();
public
void setLayout(Layout layout);
public
Layout getLayout();
public
void setName(String name);
public
boolean requiresLayout();
对其子类AppenderSkeleton进行分析;
/** The layout variable does not need to be set if the appender
implementation has its own layout. */
protected Layout layout;
/** Appenders are named. */
protected String name;
/**
There is no level threshold filtering by default. */
protected Priority threshold;
/**
It is assumed and enforced that errorHandler is never null.
*/
protected ErrorHandler errorHandler = new OnlyOnceErrorHandler();
/** The first filter in the filter chain. Set to <code>null</code>
initially. */
protected Filter headFilter;
/** The last filter in the filter chain. */
protected Filter tailFilter;
属性有,输出类,最低级别,错误处理类,过滤职责链的开始和结束。
Appender的作用,将一个LoggingEvent处理一下,然后丢给Layout
AppenderSkeleton
AppenderSkeleton实现了Appender接口,写日志是通过doAppend(LoggingEvent e)实现的
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) {
switch(f.decide(event)) {
case Filter.DENY: return;
case Filter.ACCEPT: break FILTER_LOOP;
case Filter.NEUTRAL: f = f.getNext();
}
}
this.append(event);
}
先判断log是不是关闭状态,如果不是,判断日志界别是否大于阈值,然后再得到过滤器,判断过滤器是否为空,如果不为空,通过Filter的decide(event)方法判断event时候符合要求,如果decide方法返回值为DENY直接返回,如果为ACCEPT,直接跳出循环,记录日志,如果为NEUTRAL ,则得到下一个职责,进行再一次判断循环。append方法是抽象方法,调用子类。WriterAppender
public
void append(LoggingEvent event) {
// Reminder: the nesting of calls is:
//
// doAppend()
// - check threshold
// - filter
// - append();
// - checkEntryConditions();
// - subAppend();
if(!checkEntryConditions()) {
return;
}
subAppend(event);
}
判断Appender是否为关闭,输出流是否为空,输出类是否为空。protected
boolean checkEntryConditions() {
if(this.closed) {
LogLog.warn("Not allowed to write to a closed appender.");
return false;
}
if(this.qw == null) {
errorHandler.error("No output stream or file set for the appender named ["+
name+"].");
return false;
}
if(this.layout == null) {
errorHandler.error("No layout set for the appender named ["+ name+"].");
return false;
}
return true;
}
将event格式化,输出到日志,判断是否记录异常信息,如有异常信息就记录,最后刷新流,将流输出到日志文件protected
void subAppend(LoggingEvent event) {
this.qw.write(this.layout.format(event));
if(layout.ignoresThrowable()) {
String[] s = event.getThrowableStrRep();
if (s != null) {
int len = s.length;
for(int i = 0; i < len; i++) {
this.qw.write(s[i]);
this.qw.write(Layout.LINE_SEP);
}
}
}
if(shouldFlush(event)) {
this.qw.flush();
}
}
Layout
public abstract class Layout implements OptionHandler {
// Note that the line.separator property can be looked up even by
// applets.
public final static String LINE_SEP = System.getProperty("line.separator");
public final static int LINE_SEP_LEN = LINE_SEP.length();
/**
Implement this method to create your own layout format.
*/
abstract
public
String format(LoggingEvent event);
/**
Returns the content type output by this layout. The base class
returns "text/plain".
*/
public
String getContentType() {
return "text/plain";
}
/**
Returns the header for the layout format. The base class returns
<code>null</code>. */
public
String getHeader() {
return null;
}
/**
Returns the footer for the layout format. The base class returns
<code>null</code>. */
public
String getFooter() {
return null;
}
/**
If the layout handles the throwable object contained within
{@link LoggingEvent}, then the layout should return
<code>false</code>. Otherwise, if the layout ignores throwable
object, then the layout should return <code>true</code>.
If ignoresThrowable is true, the appender is responsible for
rendering the throwable.
<p>The {@link SimpleLayout}, {@link TTCCLayout}, {@link
PatternLayout} all return <code>true</code>. The {@link
org.apache.log4j.xml.XMLLayout} returns <code>false</code>.
@since 0.8.4 */
abstract
public
boolean ignoresThrowable();
}
子类PatternLayout
/**
Produces a formatted string as specified by the conversion pattern.
*/
public String format(LoggingEvent event) {
// Reset working stringbuffer
if(sbuf.capacity() > MAX_CAPACITY) {
sbuf = new StringBuffer(BUF_SIZE);
} else {
sbuf.setLength(0);
}
PatternConverter c = head;
while(c != null) {
c.format(sbuf, event);
c = c.next;
}
return sbuf.toString();
}
判断stringBuffer 容量是不是大于最大容量,如果是,新建一个StringBuffer,反之将清空StringBuffer。PatternConverter 是一个职责链,将日志信息格户到sbuf,返回格式化的字符串。
下一篇: 0基础学习python有什么好的建议?