浏览 1880 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-10-17
这一篇是本系列博客的最后一节,介绍一下实际记录日志的类Encoder。其实继续深入下去,logback还是有很多值得研究的地方,比如Layout、Listener等。不过我个人感觉对logback框架已经比较熟悉了,所以就告一段落,有兴趣的朋友可以自己再深入下去。下一步我准备读一读struts2的源码,因为个人感觉spring、tomcat、hibernate可能难了一点,最近的工作比较忙,不会有太多的时间钻研技术,所以就挑选比较简单的struts2来读一读。 前面的文章我们已经介绍过,logback记录日志的入口是Logger类,然后Logger类又是委托Appender来记录日志,但是实际上,Appender还不是实际工作的类,Appender往往还要委托Encoder来记录日志。可能会觉得有点绕,但是一直跟下来的朋友可能会跟我有一样的感觉,就是logback框架的这种设计风格,虽然复杂了一点,但是在可扩展性上确实比较好 好了,老规矩,首先上图: 从图中我们可以看到,Encoder是一个接口,定义了3个方法,其中最重要的方法是doEncode(ILoggingEvent event)方法 然后EncoderBase抽象类部分实现了Encoder接口,其实就是持有了一个OutputStream的引用,这是为了后面调用outputStream.write(byte[] b)方法,毕竟最终,日志信息是要写到输出流里才可以的 EncoderBase又有若干个具体的实现类,但是实际上真正有意义的就是LayoutWrappingEncoder,这个实现类持有一个Layout字段,委托Layout的doLayout(ILoggingEvent event)方法,来把一个日志事件转换成String。由于基本上只会使用PatternLayoutEncoder,所以在logback.xml里这是无需配置的,如果使用了<encoder>标签,不指定实现类,那么就会默认使用PatternLayoutEncoder 上面大致介绍了一下结构,下面就来看具体的代码 public interface Encoder<E> extends ContextAware, LifeCycle { /** * This method is called when the owning appender starts or whenever output * needs to be directed to a new OutputStream, for instance as a result of a * rollover. Implementing encoders should at the very least remember the * OutputStream passed as argument and use it in future operations. * * @param os * @throws IOException */ void init(OutputStream os) throws IOException; /** * Encode and write an event to the appropriate {@link OutputStream}. * Implementations are free to differ writing out of the encoded event and * instead write in batches. * * @param event * @throws IOException */ void doEncode(E event) throws IOException; /** * This method is called prior to the closing of the underling * {@link OutputStream}. Implementations MUST not close the underlying * {@link OutputStream} which is the responsibility of the owning appender. * * @throws IOException */ void close() throws IOException; } 这是Encoder的接口定义,大致上,只要关注doEncode()方法就可以了 abstract public class EncoderBase<E> extends ContextAwareBase implements Encoder<E> { protected boolean started; protected OutputStream outputStream; public void init(OutputStream os) throws IOException { this.outputStream = os; } public boolean isStarted() { return started; } public void start() { started = true; } public void stop() { started = false; } } 如前所述,EncoderBase主要是为了方便用户的自定义扩展(在使用logback框架的时候,基本上只要是允许扩展的组件,都提供了一个Base抽象类,所以要自定义扩展,最简单的办法就是写一个类继承自XXXBase),这里持有了一个OutputStream对象,目的是为了稍后调用outputStream.write()方法 public class EchoEncoder<E> extends EncoderBase<E> { String fileHeader; String fileFooter; public EchoEncoder() { } public void doEncode(E event) throws IOException { String val = event + CoreConstants.LINE_SEPARATOR; outputStream.write(val.getBytes()); } public void close() throws IOException { if (fileFooter == null) { return; } outputStream.write(fileFooter.getBytes()); } public void init(OutputStream os) throws IOException { super.init(os); if (fileHeader == null) { return; } outputStream.write(fileHeader.getBytes()); } } 这是EchoEncoder,基本上,这个类只是起一个示例的作用,在实际应用中,不能配置Layout的Encoder没有太大的意义。该类的doEncode()方法就是对日志事件加上一个分行符,然后通过outputStream输出,也就是说,使用这个encoder的时候,不需要配置layout。下面看一下关键性的LayoutWrappingEncoder,由于持有了Layout组件,所以这个Encoder有了自定义输出格式的能力,所以也是目前基本上唯一有实用性的Encoder组件 public void init(OutputStream os) throws IOException { super.init(os); writeHeader(); } void writeHeader() throws IOException { if (layout != null && (outputStream != null)) { StringBuilder sb = new StringBuilder(); appendIfNotNull(sb, layout.getFileHeader()); appendIfNotNull(sb, layout.getPresentationHeader()); if (sb.length() > 0) { sb.append(CoreConstants.LINE_SEPARATOR); // If at least one of file header or presentation header were not // null, then append a line separator. // This should be useful in most cases and should not hurt. outputStream.write(convertToBytes(sb.toString())); outputStream.flush(); } } } 这个类在初始化的时候,会把文件头首先输出,这主要是在HTMLLayoutBase中的,如果不准备以HTML格式输出日志,这2个方法是可以忽略的 public void doEncode(E event) throws IOException { String txt = layout.doLayout(event); outputStream.write(convertToBytes(txt)); outputStream.flush(); } 然后看一下最最关键的doEncode()方法,它委托layout组件将日志事件转换成String字符串,然后通过outputStream输出。。。 好吧,所以到这里其实还没有完,接下来就要看看Layout组件是怎么把ILoggingEvent转换成String的了。不过这已经不是本篇的主题,本人也不准备继续了,只能告诉大家,Layout组件是委托Converter组件,按照配置文件里定义的格式,得到格式化之后的字符串的,有兴趣的朋友可以继续研究研究 本系列博客就到此结束了,个人感觉从中收获最大的代码有以下几个地方: 1、logback框架用JoranConfigurator来实现配置的设计思路是非常值得借鉴的,这部分内容记录在ContextInitializer那篇博客里 2、logback通过Logger-->Appender-->Encoder层层委托,来实现日志记录的思路也很精彩,通过这种层层委托的方式,使整个框架的灵活性和扩展性都很强 3、用StaticLoggerBinder类来实现具体日志框架和slf4j门面的对接,这部分的设计思路和代码也相当不错 4、ContextSelectorStaticBinder类,解决了在JNDI等环境下,不同应用使用不同的LoggerContext的问题,解决问题的思路也很值得学习 5、用XXXBase类来提供自定义组件扩展点的方法,在设计系统时也可以借鉴 总的来说,logback是一个很优秀的开源日志框架,阅读其源码除了可以加深对其理解,更好地使用它之外,还可以通过其中优秀的设计思想和代码实现,提高自己的编程水平。我认为如果想阅读源码的话,logback框架是一个不错的开始 本系列博客就到这里了,后面我准备读一读struts2框架 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |