Log4j日志输出详解 : http://donald-draper.iteye.com/blog/2332395
slf4j + Log4j的使用: http://donald-draper.iteye.com/blog/2332407
上一篇我们说了log4j的初始化,Logger的实例为NOPLogger,所有Appender,委托给
rootLogger管理,今天我们来看一下,日志的打印输出。
日志输出源头为下一句
log.info("========test daily level info=========");
我们来看一下,这一句都做了些什么?
public final class NOPLogger extends Logger public class Logger extends Category
而NOPLogger,Logger,没有info方法,来看Category
//Category
public class Category implements AppenderAttachable { AppenderAttachableImpl aai; static { FQCN = (org.apache.log4j.Category.class).getName(); } //输出日志 public void info(Object message) { //查看info日志,是否开启 if(repository.isDisabled(20000)) return; //如果INFO,大于等于有效的日志级别,则输出日志 if(Level.INFO.isGreaterOrEqual(getEffectiveLevel())) forcedLog(FQCN, Level.INFO, message, null); } //根据日志级别,输出日志 protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) { //将LoggingEvent,委托给AppenderS处理, callAppenders(new LoggingEvent(fqcn, this, level, message, t)); } public void callAppenders(LoggingEvent event) { int writes; Category c; writes = 0; c = this; _L3: label0: { if(c == null) break; /* Loop/switch isn't completed */ synchronized(c) { if(c.aai != null) //Appenders处理日志输出事件 writes += c.aai.appendLoopOnAppenders(event); if(c.additive) break label0; } break; /* Loop/switch isn't completed */ } category; JVM INSTR monitorexit ; goto _L1 exception; throw exception; _L1: c = c.parent; if(true) goto _L3; else goto _L2 _L2: if(writes == 0) repository.emitNoAppenderWarning(this); return; } }
//AppenderAttachableImpl
public class AppenderAttachableImpl implements AppenderAttachable { protected Vector appenderList; //遍历rootLooger的Appenders,每一个Appenders分别处理log输出事件 public int appendLoopOnAppenders(LoggingEvent event) { int size = 0; if(appenderList != null) { size = appenderList.size(); for(int i = 0; i < size; i++) { Appender appender = (Appender)appenderList.elementAt(i); appender.doAppend(event); } } return size; } }
这里我们来看一下DailyRollingFileAppender
//DailyRollingFileAppender
public class DailyRollingFileAppender extends FileAppender { static final int TOP_OF_TROUBLE = -1; static final int TOP_OF_MINUTE = 0; static final int TOP_OF_HOUR = 1; static final int HALF_DAY = 2; static final int TOP_OF_DAY = 3; static final int TOP_OF_WEEK = 4; static final int TOP_OF_MONTH = 5; private String datePattern; private String scheduledFilename; private long nextCheck; Date now; SimpleDateFormat sdf; RollingCalendar rc; int checkPeriod; static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT"); public DailyRollingFileAppender(Layout layout, String filename, String datePattern) throws IOException { super(layout, filename, true); this.datePattern = "'.'yyyy-MM-dd"; nextCheck = System.currentTimeMillis() - 1L; now = new Date(); rc = new RollingCalendar(); checkPeriod = -1; this.datePattern = datePattern; //构造是调用激活配置方法 activateOptions(); } }
//AppenderSkeleton
public abstract class AppenderSkeleton implements Appender, OptionHandler { //处理日志事件 public synchronized void doAppend(LoggingEvent event) { append(event); } //待子类拓展 protected abstract void append(LoggingEvent loggingevent); }
//AppenderSkeleton
public class WriterAppender extends AppenderSkeleton { //处理日志事件 public void append(LoggingEvent event) { if(!checkEntryConditions()) { return; } else { subAppend(event); return; } } protected void subAppend(LoggingEvent event) { qw.write(layout.format(event)); if(layout.ignoresThrowable()) { String s[] = event.getThrowableStrRep(); if(s != null) { int len = s.length; for(int i = 0; i < len; i++) { //输出日志,关键是QuietWriter qw.write(s[i]); qw.write(Layout.LINE_SEP); } } } if(shouldFlush(event)) qw.flush(); } protected boolean immediateFlush; protected String encoding; protected QuietWriter qw; }
下面看一下QuietWriter是什么?如何来的?
看DailyRollingFileAppender的构造方法中,调用了一个方法激活配置activateOptions
public DailyRollingFileAppender(Layout layout, String filename, String datePattern) throws IOException { super(layout, filename, true); this.datePattern = "'.'yyyy-MM-dd"; nextCheck = System.currentTimeMillis() - 1L; now = new Date(); rc = new RollingCalendar(); checkPeriod = -1; this.datePattern = datePattern; //激活配置 activateOptions(); } public void activateOptions() { super.activateOptions(); if(datePattern != null && fileName != null) { now.setTime(System.currentTimeMillis()); sdf = new SimpleDateFormat(datePattern); int type = computeCheckPeriod(); printPeriodicity(type); rc.setType(type); File file = new File(fileName); scheduledFilename = fileName + sdf.format(new Date(file.lastModified())); } else { LogLog.error("Either File or DatePattern options are not set for appender [" + name + "]."); } }
查看FileAppender
public class FileAppender extends WriterAppender { public void activateOptions() { if(fileName != null) { try { setFile(fileName, fileAppend, bufferedIO, bufferSize); } } public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize) throws IOException { LogLog.debug("setFile called: " + fileName + ", " + append); if(bufferedIO) setImmediateFlush(false); reset(); FileOutputStream ostream = null; try { ostream = new FileOutputStream(fileName, append); } //根据文件流,创建Writer Writer fw = createWriter(ostream); if(bufferedIO) fw = new BufferedWriter(fw, bufferSize); //设置QuietWriter的输出流 setQWForFiles(fw); this.fileName = fileName; fileAppend = append; this.bufferedIO = bufferedIO; this.bufferSize = bufferSize; writeHeader(); LogLog.debug("setFile ended"); } //设置QuietWriter的输出流 protected OutputStreamWriter createWriter(OutputStream os) { OutputStreamWriter retval = null; String enc = getEncoding(); if(enc != null) try { retval = new OutputStreamWriter(os, enc); } if(retval == null) retval = new OutputStreamWriter(os); return retval; } //设置QuietWriter的输出流 protected void setQWForFiles(Writer writer) { qw = new QuietWriter(writer, errorHandler); } }
再看一下ConsoleAppender
//ConsoleAppender。 ublic class ConsoleAppender extends WriterAppender { private static class SystemOutStream extends OutputStream { public void close() { } public void flush() { System.out.flush(); } public void write(byte b[]) throws IOException { System.out.write(b); } public void write(byte b[], int off, int len) throws IOException { System.out.write(b, off, len); } public void write(int b) throws IOException { System.out.write(b); } public SystemOutStream() { } } public ConsoleAppender(Layout layout, String target) { this.target = "System.out"; follow = false; setLayout(layout); setTarget(target); activateOptions(); } public void activateOptions() { if(follow) { if(target.equals("System.err")) setWriter(createWriter(new SystemErrStream())); else //设置输出流 setWriter(createWriter(new SystemOutStream())); } else if(target.equals("System.err")) setWriter(createWriter(System.err)); else setWriter(createWriter(System.out)); super.activateOptions(); } }
总结:
从上面的分析我们可以看出,log日志的输出,是遍历rootLogger的Appender来处理日志输出事件,Appender,首先确定日志级别是否大于RootLogger的日志级别,大于,则处理日志,而日志的输出委托给QuietWriter,而QuietWriter来源于DailyRollingFileAppender,
ConsoleAppender。