读logback源码系列文章(八)——记录日志的实际工作类Encoder
本系列的博客从logback怎么对接slf4j开始,逐步介绍了LoggerContext、Logger、ContextInitializer、Appender、Action等核心组件。跟读logback的源码到这个程度,虽然不能说精通,不过至少日常的配置,和简单的自定义扩展都不会有问题了。
这一篇是本系列博客的最后一节,介绍一下实际记录日志的类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;}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; }} 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()); }}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(); } } }public void doEncode(E event) throws IOException { String txt = layout.doLayout(event); outputStream.write(convertToBytes(txt)); outputStream.flush(); }