备注:下面所有代码以log4j为例

包结构

  • slf4j-api.jar对外提供api
  • slf4j.log4j12.jar提供适配器
  • log4j.jar是log4j的jar

slf4j初始化

  1. 获取ILoggerFactory实例

    1. 如果初始化状态为UNINITIALIZED,把状态改为ONGOING_INITIALIZATION,查找org/slf4j/impl/StaticLoggerBinder.class类,这个类在适配器包中(slf4j.log4j12.jar),校验查找结果

      1. 如果正好能查找到一个实例,使用StaticLoggerBinder.getSingleton().getLoggerFactory()
      2. 如果查找0个或者多个,报异常
    2. 如果初始化状态为ONGOING_INITIALIZATION,初始化一个临时NON_Logger放到SubstituteLoggerFactory TEMP_FACTORY
  2. 获取到ILoggerFactory,这里的实例是Log4jLoggerFactory,调用getLogger(String name)根据名称获取Logger并且修复/填充SubstituteLoggerFactory TEMP_FACTORY暂存的Logger,每一个name对应一个Logger实例。
  3. 初始化log4j,使用LogManager.getLogger(name)获取org.apache.log4j.Logger实例,返回适配器org.slf4j.impl.Log4jLoggerAdapter(org.apache.log4j.Logger),该适配器实现了org.slf4j.Logger接口

slf4j的几个细节

  • slf4j已经支持logger.info("hello {}", name)这种格式,用来替代之前的logger.info("hello " + name)。当日志级别高于info时,前一种写法可以减少一些性能损耗,不需要做一些无意义的字符串拼接。同时后一种写法,更为直观一些。这种特性支持是slf4j提供的,log4j本身不支持。
  • slf4jlogger.info("hello {}", Throwable),想打印异常栈信息,只能使用两个参数的方法,并且是后一个参数为Throwable,如果参数为一个或者多余两个,都不能正常打印出异常栈。
  • slf4j可能会限制第三方日志的功能,比如不支持log4j的renderer(该功能可以让info方法打印对象,并且使用特定的类解析这个对象)。

log4j初始化以及使用

  1. 如果LogManager没有初始化,使用静态区初始化(statc{}

    1. 新建对象org.apache.log4j.Hierarchy,这个对象用来维护、新建所有的Logger
    2. 使用OptionConverter.selectAndConfigure解析配置,并初始化好Hierarchy,真正解析配置的类是Configurator,因为程序中只维护了一个Hierarchy,并且解析方法,以这个对象为参数,所以可以在程序运行时,调用解析方法,动态更改配置。
    3. 初始化过程中,默认创建一个RootLogger,然后根据配置log4j.logger.x.y、log4j.logger.x.y.z、log4j.category.a、log4j.category.a.b等等创建名为x.y、x.y.z、a、a.b的Logger
      1. 创建logger时,会维护好每一个Logger的父Logger,父子关系是用类似java里的父子包来关联的,比如x.yx.y.z的父,如果父Logger不存在,就继续向上找爷,一直到rootLogger
      2. 如果Logger配置了appender,则解析出appender并初始化好参数(使用反射,所以配置的字段都是根据实际类的字段来的),创建一个appender实例,设置到LoggerAppenderAttachable aai字段。
  2. log4j使用org.apache.log4j.LogManager.getLogger(name)/LogManager.getRootLogger()获取Logger实例,实际是调用Hierarchy.getLogger()方法
    1. 创建的Logger还是以 “x.yx.y.z的父”这种关系维护好所有的Logger,代码里面创建的Logger和配置里面配置的log4j.logger.rembau.test=info, M区别是,配置文件里面可以配置appender维护在LoggerAppenderAttachable aai字段里,而代码里面如果getLogger(name)name没有在配置文件里配置过,aai字段为null。
    2. 打印日志时,调用logger.info(),先判断当前logger的最低日志级别level是否小于info,如果是进行下一步。如果当前loggeraai不为null,再调用appenderAttachable.appendLoopOnAppenders(LoggingEvent),迭代调用每一个appenderdoAppend方法。如果当前Loggeradditive为 false,则结束,否则调用父logger.info()同样逻辑一直到rootLogger

log4j维护logger父子关系,详细算法

  1. 获取x.y.z logger时,存储x.y.z为有效节点,存储x、x.y为临时节点,并记录x-z、x.y-x.y.z的关系;记录父节点为root。

    1. 获取x.y时,把存储的x.y临时节点转换为有效节点,找到x临时节点,然后记录x-x.y关系;更新x.y-x.y.z关系,x.y.z的父节点为x.y;记录x.y的父节点为root

      1. 获取x时,把存储的x临时节点转换为有效节点;更新x-x.y,x.y的父节点为x,记录x的父节点为root;更新x-x.y.z,父节点是x的子节点,跳过,不处理
    2. 获取x时,把存储的x临时节点转换为有效节点;更新x-x.y.z,x.y.z的父节点为x,记录x的父节点为root
      1. 获取x.y时,把存储的x.y转换为有效节点,找到x节点,设置为父节点;更新x.y-x.y.z,x.y.z的父节点不为x.y的子节点,更新x.y.z父节点为x.y

概述:获取logger时,从底向上查找所有的祖宗节点,如果存在记录父节点为改祖宗,并返回;否则记录所有祖宗节点,并记录祖宗与该节点的关系;一个祖宗可以和多个子节点产生关系;如果该logger被列为祖宗,则更新所有相关的子节点,如果子节点的父节点不是该logger的子节点,更新子节点的父节点为当前logger。

获取类名,行号的方法

  1. 在每一个LogEvent中,记录应用调用日志组件的类为FQCN,比如org.slf4j.impl.Log4jLoggerAdapter、org.apache.log4j.Logger
  2. 需要解析成日志文字时,在任意地方new Throwable(),使用new Throwable().getStackTrace(),返回StackTraceElement[]
  3. 分析每一个StackTraceElement,如果getClassName与logEvent中的FQCN一致,则认为上一个StackTraceElement是我们调用日志的起点,然后使用StackTraceElement的getFileName、getLineNumber、getClassName、getMethodName获取信息。

log4j配置样例

  1. log4j.rootCategory=info, stdout, R
  2. log4j.logger.dubboMonitor= debug, dubbo
  3. log4j.additivity.dubboMonitor= false
  4. log4j.logger.com.alibaba.dubbo=WARN
  5. log4j.appender.stdout=org.apache.log4j.ConsoleAppender
  6. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
  7. log4j.appender.stdout.layout.ConversionPattern=<%d{HH:mm:ss,SSS}> %5p (%F:%L) [%t] (%c) - %m%n
  8. log4j.appender.R=org.apache.log4j.RollingFileAppender
  9. log4j.appender.R.File=./logs/day.log
  10. log4j.appender.R.bufferedIO=true
  11. log4j.appender.R.MaxFileSize=10240KB
  12. log4j.appender.R.Threshold = INFO
  13. log4j.appender.R.MaxBackupIndex=100
  14. log4j.appender.R.layout=org.apache.log4j.PatternLayout
  15. log4j.appender.R.layout.ConversionPattern=<%d> %p (%F:%L) [%t] %c - %m%n
  16. log4j.renderer.rembau.test.Hello=rembau.test.HelloRenderer
  • log4j.rootCategory=info, stdout,R配置rootLogger,日志级别以及两个appender
  • log4j.logger.dubboMonitor= debug, dubbo配置dubboMonitor logger,日志级别,以及一个appender,如果没有下一行的配置,则这个logger每次要输出到三个appender里,因为它的父loggerrootLogger
  • log4j.additivity.dubboMonitor= false,配置不往父logger里输出日志
  • log4j.logger.com.alibaba.dubbo=WARN,这个与第二行的配置区别是,只配置了日志级别,没有配置appender,所以只会限制日志级别,日志数据最终还是在父loggerappender输出
  • log4j.appender.stdout=org.apache.log4j.ConsoleAppender配置appender
  • log4j.appender.R.bufferedIO=true配置文件appender使用BufferedOutputWrite类,并且不立即flush()
  • log4j.renderer.rembau.test.Hello=rembau.test.HelloRenderer,当打印Hello对象时,使用HelloRenderer类解析Hello成字符串,然后打印这个字符串。slf4j不支持这个功能。
  • 等等

log4j几个细节

  • 如果父logger日志级别配置了warn,子logger配置了info,子logger打印info日志时,父loggerappender也会输出日志。因为校验日志级别是在info方法里的,但是向上往父logger输出日志时直接调的logger.aai.appendLoopOnAppenders(event)方法
  • loggerappender里输出日志时,需要给当前logger加锁,具体代码可以看category.callAppenders,这里我还不理解为什么要加锁,可能是考虑到有的appender不支持同步,大量日志时,这里会阻塞。
  • 配置里log4j.rootCategorylog4j.rootLogger是等价的;log4j.logger.dubboMonitorlog4j.category.dubboMonitor是等价的,等等。、
  • 应用中可以使用PropertyConfigurator.configure()静态方法,动态更新配置。如果想重置logger在新的配置里面加上log4j.reset=true,这个配置可以清除掉,旧配置有新配置没有的的配置。

slf4j和log4j源代码解析以及详解的更多相关文章

  1. slf4j、log4j、 logback关系详解和相关用法

    slf4j log4j logback关系详解和相关用法 写java也有一段时间了,一直都有用slf4j log4j输出日志的习惯.但是始终都是抱着“拿来主义”的态度,复制粘贴下配置文件就开始编码了, ...

  2. java 日志体系(三)log4j从入门到详解

    java 日志体系(三)log4j从入门到详解 一.Log4j 简介 在应用程序中添加日志记录总的来说基于三个目的: 监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析工作: 跟踪代 ...

  3. HiveSQL解析过程详解 | 学步园

    HiveSQL解析过程详解 | 学步园   http://www.xuebuyuan.com/2210261.html

  4. log4j.properties 的使用详解

    一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR 为严重错误 主要是程序的错误WARN 为一般警告,比如session丢失IN ...

  5. 深入解析ThreadLocal 详解、实现原理、使用场景方法以及内存泄漏防范 多线程中篇(十七)

    简介 从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本 ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个 ...

  6. Solr系列五:solr搜索详解(solr搜索流程介绍、查询语法及解析器详解)

    一.solr搜索流程介绍 1. 前面我们已经学习过Lucene搜索的流程,让我们再来回顾一下 流程说明: 首先获取用户输入的查询串,使用查询解析器QueryParser解析查询串生成查询对象Query ...

  7. (转)DNS解析过程详解

    DNS解析过程详解 原文:http://blog.csdn.net/crazw/article/details/8986504 先说一下DNS的几个基本概念: 一. 根域 就是所谓的“.”,其实我们的 ...

  8. DNS解析过程详解(转载)

    DNS解析过程详解(转载) DNS Domain Name System 域名系统,它就是根据域名查出IP地址.    先说一下DNS的几个基本概念: 一. 根域 就是所谓的“.”,其实我们的网址ww ...

  9. JAVA中的四种JSON解析方式详解

    JAVA中的四种JSON解析方式详解 我们在日常开发中少不了和JSON数据打交道,那么我们来看看JAVA中常用的JSON解析方式. 1.JSON官方 脱离框架使用 2.GSON 3.FastJSON ...

随机推荐

  1. error while loading shared libraries: libudev.so.0 的问题

    在linux下运行koala的时候遇到了问题: 应该是缺少文件libudev.so.0 在/lib和/lib64目录下也都没有这个文件.然后网上找解决方案,在/lib/x86_64-linux-gnu ...

  2. 201621123018《Java程序设计》第1周学习报告

    1.<Java程序设计>第1周学习报告 1.本周学习报告 关键字:Java的发展.Java语言的特点.JDK.JRE.JVE.Java开发工具.Java环境配置.Java是一种面向对象的程 ...

  3. BZOJ 5334--[Tjoi2018]数学计算(线段树)

    5334: [Tjoi2018]数学计算 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 220  Solved: 147[Submit][Status ...

  4. 算法逆向6——RSA识别

    本文原创作者:i春秋作家——icq5f7a075d 1. 算法介绍 RSA算法是一种用数论构造的.基于大合数因子分解困难性的公开密钥密码.由于RSA密码既可用于加密,又可用于数字签名,安全.易懂,因此 ...

  5. echart 遇到的点

    1,图表随着外部container变化而变化: window.onresize = myChart.resize (拿着resize在api文档中搜就看到了)

  6. Spring配置项<context:annotation-config>的解释说明

    今天在闲逛CSDN论坛时,看到一位博主写的一篇关于<Spring中IOC的Annotation的实现>的文章, 于是点击进去看了下, 发现在说明中对Spring配置文件中的有些配置节点模凌 ...

  7. 当需要给<span>标签设宽度

    span{ display: inline-block; width: 100px; } 当需要实现横排单行文字,用到span并且想设宽度的时候, 用以上css来实现.

  8. python学习笔记07-元组 字典

    元组: 元组里面的元素不可修改  创建后只可读  不可写 一个元素的时候 在后面加一个逗号 字典: 无序的 Python 中唯一的映射类型 采用键值对的形式存储数据 key必须是可哈希的  可哈希表示 ...

  9. Unity 自动生成组件索引类工具

    Unity 自动生成组件索引类工具 需求由来 我们在写UI类时 需要获取预设中的组件 joystick = transform.Find("joystick"); backgrou ...

  10. [BZOJ2758] [SCOI2012]Blinker的噩梦 扫描线+set

    题目大意:有n个圆或凸多边形,这些图形不会相交,每当走入或走出一个图形时需要异或上一个代价,有m组操作: 询问操作,每次询问从一个点走到另一个点时,需要的代价(初始代价为0) 修改操作,每次修改一个图 ...