读logback源码系列文章(五)——Appender --转载

时间:2021-09-25 14:16:45

原文地址:http://kyfxbl.iteye.com/blog/1173788

明天要带老婆出国旅游几天,所以这段时间暂时都更新不了博客了,临走前再最后发一贴

上一篇我们说到Logger类的info()方法通过层层调用,最后委托Appender来记录日志,这篇博客我们就接着说一下,Appender组件是怎么记录日志的

实际上Appender可能是logback框架中最重要的组件之一,虽然Logger是记录日志的接口,但是如果一个Logger没有关联到任何Appender的话,那么这个Logger就无法记录任何信息。此外虽然logback提供了很多扩展点,但是在应用中,我们可能很少会扩展filter,很少扩展layout和encoder,但是我们扩展Appender的机会却是很多的

老规矩,首先上图,看一下Appender的大图景,这里要说明的是,实现Appender接口有2个base类,一个是AppenderBase,另一个是UnsynchronizedAppenderBase,这2个类非常接近,80%以上的代码都是相同的。如果我们自己要自定义Appender的话,只要写一个类继承自这2个base类就好

读logback源码系列文章(五)——Appender --转载

首先是有一个Appender接口,然后如上文所说,UnsynchronizedAppenderBase类实现了这个接口,但是它本身是一个抽象类,需要继承它才能得到真正的实现类。Appender接口继承了FilterAttachable接口,而UnsynchronizedAppenderBase类持有一个FilterAttachableImpl类,委托这个类来实现FilterAttachable接口里定义的方法

然后OutputStreamAppender是继承自UnsynchronizedAppenderBase的Appender实现类,虽然它已经不是抽象类了,但是实际也是不能直接使用的,它的实现类就是最常见的ConsoleAppender和FileAppender

只要自己使用过logback的朋友都知道,appender元素下面还需要配置encoder元素,这里的Encoder接口就是对应这个encoder元素的,因为其实Appender组件还不是最终实际记录日志信息的组件,它要委托encoder组件来完成LoggingEvent的格式化和记录

介绍完了大体的结构,我们接下来就看看,从Appender接口的doAppend()方法,是怎么一步步地最终记录日志的

首先是UnsynchronizedAppenderBase里面的doAppend()方法,它主要是记录了Status状态,然后检查Appender上的Filter是否满足过滤条件,最后再调用实现子类的appender()方法。很眼熟是吗,这里用到了一个设计模式——模板方法

  1. public void doAppend(E eventObject) {
  2. // WARNING: The guard check MUST be the first statement in the
  3. // doAppend() method.
  4. // prevent re-entry.
  5. if (Boolean.TRUE.equals(guard.get())) {
  6. return;
  7. }
  8. try {
  9. guard.set(Boolean.TRUE);
  10. if (!this.started) {
  11. if (statusRepeatCount++ < ALLOWED_REPEATS) {
  12. addStatus(new WarnStatus(
  13. "Attempted to append to non started appender [" + name + "].",
  14. this));
  15. }
  16. return;
  17. }
  18. if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
  19. return;
  20. }
  21. // ok, we now invoke derived class' implementation of append
  22. this.append(eventObject);
  23. } catch (Exception e) {
  24. if (exceptionCount++ < ALLOWED_REPEATS) {
  25. addError("Appender [" + name + "] failed to append.", e);
  26. }
  27. } finally {
  28. guard.set(Boolean.FALSE);
  29. }
  30. }
  31. abstract protected void append(E eventObject);

上面的代码非常简单,就不用说了,我们就直接看看实现类的append()方法是怎么实现的,这里我们选择OutputStreamAppender实现类

  1. @Override
  2. protected void append(E eventObject) {
  3. if (!isStarted()) {
  4. return;
  5. }
  6. subAppend(eventObject);
  7. }

首先检查一下这个Appender是否已经启动,如果没启动就直接返回,如果已经启动,则又进入一个subAppend()方法

  1. /**
  2. * Actual writing occurs here.
  3. * <p>
  4. * Most subclasses of <code>WriterAppender</code> will need to override this
  5. * method.
  6. *
  7. * @since 0.9.0
  8. */
  9. protected void subAppend(E event) {
  10. if (!isStarted()) {
  11. return;
  12. }
  13. try {
  14. // this step avoids LBCLASSIC-139
  15. if (event instanceof DeferredProcessingAware) {
  16. ((DeferredProcessingAware) event).prepareForDeferredProcessing();
  17. }
  18. // the synchronization prevents the OutputStream from being closed while we
  19. // are writing. It also prevents multiple thread from entering the same
  20. // converter. Converters assume that they are in a synchronized block.
  21. synchronized (lock) {
  22. writeOut(event);
  23. }
  24. } catch (IOException ioe) {
  25. // as soon as an exception occurs, move to non-started state
  26. // and add a single ErrorStatus to the SM.
  27. this.started = false;
  28. addStatus(new ErrorStatus("IO failure in appender", this, ioe));
  29. }
  30. }

这个方法居然什么事也不干。。做了一些检查以后,又进入writeOut()方法。。。

  1. protected void writeOut(E event) throws IOException {
  2. this.encoder.doEncode(event);
  3. }

writeOut()方法委托配置给它的Encoder组件来记录

  1. public void doEncode(E event) throws IOException {
  2. String txt = layout.doLayout(event);
  3. outputStream.write(convertToBytes(txt));
  4. outputStream.flush();
  5. }

到这里,终于完了。Encoder组件又委托其Layout组件来将LoggingEvent进行格式化,返回一个String,然后通过OutputStream.write()方法,把格式化之后的日志信息写到目的地

耐心看到这里的朋友,可能已经有点被绕晕了,怎么Appender需要这么麻烦吗?其实我们这里说的只是ConsoleAppender的doAppend()全流程,并不是所有Appender都这么复杂的,当然也有一些更复杂的。。

下面看一个简单的Appender,就是我自己写的MyAppender

  1. public class MyAppender extends AppenderBase<LoggingEvent> {
  2. @Override
  3. protected void append(LoggingEvent eventObject) {
  4. System.out.println(eventObject.getMessage());
  5. }
  6. }

好吧,非常简单是不是,如果把这个Appender配置到logback.xml中,那么当Logger.info()调用的时候,就会先走进AppenderBase类的doAppend()方法里,进行Filter校验等等,然后进入MyAppender的append()方法,不做其他的操作,直接把message给打印到Console上。当然,由于这个类是极度简化的,没有Encoder和Layout,也就没办法控制输出日志的时间,也没有办法对%thread等标记进行解析处理了。但是这个类可能可以很清晰地表达出,Appender组件是怎么工作的:由AppenderBase类来调用Filter链,然后由Appender实现类来委托Encoder解析LoggingEvent,再输出到目的地

这篇博客到这里就结束了。到目前为止,5篇博客是这样的: 
1、首先介绍logback怎么和slf4j对接 
2、然后介绍logback的LoggerFactory,也就是LoggerContext是怎么创建的 
3、接下来介绍LoggerFactory怎么创建Logger 
4、然后是Logger怎么记录日志,这其中涉及了级联调用Appender,和调用TurboFilter来过滤的问题 
5、本篇博客又以最常见的ConsoleAppender为例子,介绍了Appender组件怎么把日志信息输出到目的地

下一篇博客的主题有多种分支,可以再讲讲DBAppender和FileAppender是怎么记录日志的,作为这篇博客的补充,加深理解;也可以继续深入下去,说说Encoder和Layout组件;或者回头介绍一下logback是怎么初始化的

等我陪老婆旅游回来,再继续更新本系列

读logback源码系列文章(五)——Appender --转载的更多相关文章

  1. 读 Zepto 源码系列

    虽然最近工作中没有怎么用 zepto ,但是据说 zepto 的源码比较简单,而且网上的资料也比较多,所以我就挑了 zepto 下手,希望能为以后阅读其他框架的源码打下基础吧. 源码版本 本文阅读的源 ...

  2. 读FCL源码系列之List&lt&semi;T&gt&semi;---让你知其所以然---内含疑问求大神指点

    序言 在.NET开发中,List<T>是我们经常用到的类型.前段时间看到其他部门小伙伴讨论“两个List(10W个元素)集合求并集,list1.Where(p=>list2.Cont ...

  3. Spark源码系列(五)分布式缓存

    这一章想讲一下Spark的缓存是如何实现的.这个persist方法是在RDD里面的,所以我们直接打开RDD这个类. def persist(newLevel: StorageLevel): this. ...

  4. hbase源码系列(五)Trie单词查找树

    在上一章中提到了编码压缩,讲了一个简单的DataBlockEncoding.PREFIX算法,它用的是前序编码压缩的算法,它搜索到时候,是全扫描的方式搜索的,如此一来,搜索效率实在是不敢恭维,所以在h ...

  5. 读 Zepto 源码之神奇的 &dollar;

    经过前面三章的铺垫,这篇终于写到了戏肉.在用 zepto 时,肯定离不开这个神奇的 $ 符号,这篇文章将会看看 zepto 是如何实现 $ 的. 读Zepto源码系列文章已经放到了github上,欢迎 ...

  6. 读Zepto源码之集合操作

    接下来几个篇章,都会解读 zepto 中的跟 dom 相关的方法,也即源码 $.fn 对象中的方法. 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码 ...

  7. 读 Zepto 源码之集合元素查找

    这篇依然是跟 dom 相关的方法,侧重点是跟集合元素查找相关的方法. 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 zept ...

  8. 读Zepto源码之操作DOM

    这篇依然是跟 dom 相关的方法,侧重点是操作 dom 的方法. 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 zepto1 ...

  9. 读Zepto源码之样式操作

    这篇依然是跟 dom 相关的方法,侧重点是操作样式的方法. 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 zepto1.2. ...

随机推荐

  1. 安装Windows SDK7&period;1时发生的一个错误&lpar;附解决办法&rpar;

    A problem occurred while installing selected Windows SDK components. Installation of the "Micro ...

  2. 浅谈云计算之SAN扩展系统设计

    设计背景:不管是公有云还是私有云,为了提供服务的持续性(Business Continuity,BC)和数据的灾难恢复(Disaster Recovery,DR)都不可能只有一个数据中心(Data C ...

  3. Android---App Widget(一)

    本文译自:http://developer.android.com/guide/topics/appwidgets/index.html App Widgets是一些较小的应用程序窗口,它们能够被嵌入 ...

  4. C&num; 加载并显示菜单

    1,支持cui和cuix. 2,菜单组重复加载或显示,C#下都会崩溃.所以要判断. 3,菜单加到最后. public static AcadMenuGroup LoadMenu(AcadMenuGro ...

  5. 剑指offer(14)链表中倒数第K个节点

    题目描述 输入一个链表,输出该链表中倒数第k个节点. 题目分析 用两个指针来跑,两个指针中间相距k-1个节点,第一个指针先跑,跑到了第k个节点时,第二个指针则是第一个节点. 这时候两个一起跑.当第一个 ...

  6. January 17th&comma; 2018 Week 03rd Wednesday

    Don't let go too soon, but don't hold on too long. 不要太快放手,也别紧握太久. It is inevitalbe to encounter with ...

  7. sea&period;js与require&period;js的区别

    随着ES6标准的module出台渐渐会退出历史舞台 首先原理上的区别 sea.js遵循CMD规范.书写方式类似node.js的书写模板代码.依赖的自动加载,配置的简洁清晰.说白了就是懒加载. requ ...

  8. 使用Windows Server 2003搭建一个asp&plus;access网站

    鼠标右键->新建->网站->下一步->描述(随便给一个,这里我以test为例) ->下一步->下一步->输入主目录的路径,默认路径下是C:\Inetpub\w ...

  9. Mvnw 简介

    Mvnw 简介  8月 17, 2016 |  Nix.Huang 背景 maven是一款非常流行的java项目构建软件,它集项目的依赖管理.测试用例运行.打包.构件管理于一身,是我们工作的好帮手,m ...

  10. uboot下如何查看内存里的数据

    答:使用md工具 md.b $address $count (从地址$address处显示$count个字节的数据,b=byte,8位) md.w $address $count (从地址$addre ...