最近稍微大致地阅读了log4j的源码,毕竟这是Java开源日志框架里几乎最常用的一个实现。在大多数实际应用中,还需要诸如common-logging、slf4j等Facade模式日志框架对log4j进行封装,实际日志记录、输出等级、格式化等工作都是由log4j完成。本文把注意力集中在log4j身上,探讨一下这个开源日志框架的内部。
特性
关于log4j的具体配置以及例子这里就不多说了,主要集中说一下其配置特性。作为一个开源日志框架,为了满足大部分日志需求,log4j的配置较为灵活,并且也较为容易理解,实际配置起来也并不复杂。
主要有以下可配置特性:
//DailyRollingFileAppender
protected void subAppend(LoggingEvent event) {
long n = System.currentTimeMillis();
if (n >= nextCheck) {
now.setTime(n);
nextCheck = rc.getNextCheckMillis(now);
try {
rollOver();
}
catch(IOException ioe) {
if (ioe instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("rollOver() failed.", ioe);
}
}
super.subAppend(event);
}
}//RollingFileAppender
protected
void subAppend(LoggingEvent event) {
super.subAppend(event);
if(fileName != null && qw != null) {
long size = ((CountingQuietWriter) qw).getCount();
if (size >= maxFileSize && size >= nextRollover) {
rollOver();
}
}
}
}
//CountingQuietWriter
public
void write(String string) {
try {
out.write(string);
count += string.length();
}
catch(IOException e) {
errorHandler.error("Write failure.", e, ErrorCode.WRITE_FAILURE);
}
}
public
long getCount() {
return count;
}
static String[] SPACES = {" ", " ", " ", " ", //1,2,4,8 spaces
" ", // 16 spaces
" " }; // 32 spaces
/**
Fast space padding method.
*/
public
void spacePad(StringBuffer sbuf, int length) {
while(length >= 32) {
sbuf.append(SPACES[5]);
length -= 32;
}
for(int i = 4; i >= 0; i--) {
if((length & (1<<i)) != 0) {
sbuf.append(SPACES[i]);
}
}
}
}
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);
logger.setHierarchy(this);
ht.put(key, logger);
updateParents(logger);
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.
}
}
} 如果子包的类先调用getLogger,则此时获得的Logger为NULL,这样在updateParents的时候,会不断根据x.y、x这样的顺序创建父包的ProvisionNode作为占位,如果仍然没有找到父包,则会把root的Logger作为默认的父亲,这样就保证一定有默认属性。如果是父包的类先调用getLogger,则会获得ProvisionNode,这样就需要把ProvisionNode记录的子Logger更新parent引用。这样就保证在getLogger的时候保证顺序正常,同时也保证了通过parent获取继承属性也比较快。updateChildren和updateParents方法如下: final
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;
}
final
private
void updateChildren(ProvisionNode pn, Logger logger) {
//System.out.println("updateChildren called for " + logger.name);
final int last = pn.size();
for(int i = 0; i < last; i++) {
Logger l = (Logger) pn.elementAt(i);
//System.out.println("Updating child " +p.name);
// Unless this child already points to a correct (lower) parent,
// make cat.parent point to l.parent and l.parent to cat.
if(!l.parent.name.startsWith(logger.name)) {
logger.parent = l.parent;
l.parent = logger;
}
}
}之前本人曾经为Android写过一个日志实现。由于Android本身对于流畅运行要求极高,为了实现最佳的日志性能,实现了一个Buffer+异步日志记录功能的模块。
主要是利用OutputStream的传递性。大致如下:
OutputStream writer = new BufferedOutputStream(new AsyncOutputStream(new FileOutputStream));这样在Buffer写满的时候,便会自动转为异步写文件,这种记录日志的方式应该能最大化减轻记录日志所带来的IO负担,以及避免过量异步带来的开销。
原文地址:http://blog.csdn.net/pun_c/article/details/45622083