Log4J学习【二十七】常用Appender使用例子

时间:2021-10-22 21:48:39
看完WriterAppender的代码之后,我们就随便挑选两个稍微简单的ConsoleAppender和FilterAppender来看看,我们真正平时在使用的Appender是怎么实现的。首先是ConsoleAppender:
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();
  }

    在这段代码中,我们可以看到另一个我们之前并没有介绍的属性follow,这个属性的作用非常简单,如果follow为true,那么就根据我们设置的target属性(还记得ConsoleAppender的target属性么?)来选择重新创建一个SystemErrStream或者SystemOutStream,如果follow为false,就是使用当前的System.out或System.err来输出。在这个createWriter方法中,我们就只看一句代码就够了:
retval = new OutputStreamWriter(os, enc);
    非常简单,就是把传进来的System.out或者System.err,和encoding(还记得WriterAppender上面的encoding属性么?)包装成一个OutputStreamWriter。
    另外,这个类也没有再去实现append方法或者subAppend方法,而直接是使用的WriterAppender去执行的。同时,看完代码之后,我们在使用ConsoleAppender设置的那些属性到底是怎么起作用的,也就非常明显了。

    再来个例子,看看FileAppender,同理,还是先找acitivateOption再找append或者subAppend:
public void activateOptions() {
    if (fileName != null) {
        try {
            setFile(fileName, fileAppend, bufferedIO, bufferSize);
        } catch (java.io.IOException e) {
            errorHandler.error("setFile(" + fileName + "," + fileAppend + ") call failed.", e,
            ErrorCode.FILE_OPEN_FAILURE);
        }
    } else {
        LogLog.warn("File option not set for appender [" + name + "].");
        LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");
    }
}

很简单,首先检查必须设置文件名,这里的fileName即是我们设置的file属性得到的;如果没有问题,则使用setFile方法创建文件和Writer,我们来看看setFile的代码:
public synchronized void setFile(String fileName, boolean append, boolean bufferedIO,
int bufferSize) throws IOException {
    LogLog.debug("setFile called: " + fileName + ", " + append);
    reset();
    FileOutputStream ostream = null;
    try {
        ostream = new FileOutputStream(fileName, append);
    } catch (FileNotFoundException ex) {
        String parentName = new File(fileName).getParent();
        if (parentName != null) {
            File parentDir = new File(parentName);
            if (!parentDir.exists() && parentDir.mkdirs()) {
                ostream = new FileOutputStream(fileName, append);
            } else {
                throw ex;
            }
        } else {
            throw ex;
        }
    }
    Writer fw = createWriter(ostream);
    if (bufferedIO) {
        fw = new BufferedWriter(fw, bufferSize);
    }
}

    首先注意调用方法传入的参数值,分别是fileName,append,bufferedIO和bufferSize。怎么样,这4个属性名字都很熟悉吧。首先,根据fileName和append创建一个FileOutputStream;如果文件不存在则创建这个文件,再把这个文件包装成FileOutputStream;紧接着调用WriterAppender的createWriter方法,把FileOutputStream包装成一个Writer(这个Writer就设置好了encoding了);接着,如果需要缓存,则再把Writer用BufferedWriter包装一次,即可使用。
    同样,这个类也没有再去实现append方法或者subAppend方法,而直接是使用的WriterAppender去执行的。其实ConsoleAppender和FileAppender都只是对用什么东西来写做了规定,其他的比如怎么写,就都是WriterAppender来规范的。

    代码先看到这里,我们来思考一个问题,我们已经看了FileAppender的实现方式,那么,我们自己来猜想一下RollingFileAppender的实现呢?那下面我们就来简单猜想一下,请大家注意一下,可能我们下面的思路不一定就是RollingFileAppender的实现方式,但是我们先去猜,再看代码,自己的提升会很大。
    首先我们考虑,RollingFileAppender应该不需要去覆写activateOption方法,因为RollingFileAppender的初始化动作和FileAppender是相同的,都是根据情况创建好日志文件而已。但是,在RollingFileAppender中,就必须要去修改subAppend方法了。因为在日志记录的过程中,涉及到文件备份/新开操作和当文件索引号达到规定最大号码的时候,删除备份文件的动作。这些都只有在subAppend方法中实现。首先,当记录完成一条日志后,就需要去判断当前日志文件的大小,如果文件大小满足了最大文件上限,就需要执行文件处理动作。首先把当前日志文件之前的所有日志文件备份名+1,即log.log.N变成log.log.N+1,然后再帮当前日志文件重命名为log.log.1,如果当前的备份日志文件名称数量已经超过了maxBackupIndex大小,则直接删除log.log.maxBackupIndex+1这个文件即可,所以,在RollingFileAppender中,又应该保持一个属性来记录当前已经备份了多少文件号,处理完备份文件之后,再重新创建log.log,并把FileOutputStream重新定向到这个新的日志文件即可。
    那么真正的代码是否如我们所设想呢?这个大家就下去自己看看RollingFileAppender的实现,保证能学到别人更好的设计思想和规范的编码方式。