slf4j和log4j源代码解析以及详解
备注:下面所有代码以log4j为例
包结构
- slf4j-api.jar对外提供api
- slf4j.log4j12.jar提供适配器
- log4j.jar是log4j的jar
slf4j初始化
- 获取
ILoggerFactory
实例- 如果初始化状态为
UNINITIALIZED
,把状态改为ONGOING_INITIALIZATION
,查找org/slf4j/impl/StaticLoggerBinder.class
类,这个类在适配器包中(slf4j.log4j12.jar),校验查找结果- 如果正好能查找到一个实例,使用
StaticLoggerBinder.getSingleton().getLoggerFactory()
; - 如果查找0个或者多个,报异常
- 如果正好能查找到一个实例,使用
- 如果初始化状态为
ONGOING_INITIALIZATION
,初始化一个临时NON_Logger
放到SubstituteLoggerFactory TEMP_FACTORY
中
- 如果初始化状态为
- 获取到
ILoggerFactory
,这里的实例是Log4jLoggerFactory
,调用getLogger(String name)
根据名称获取Logger
并且修复/填充SubstituteLoggerFactory TEMP_FACTORY
暂存的Logger
,每一个name对应一个Logger实例。 - 初始化
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本身不支持。 - slf4j
logger.info("hello {}", Throwable)
,想打印异常栈信息,只能使用两个参数的方法,并且是后一个参数为Throwable
,如果参数为一个或者多余两个,都不能正常打印出异常栈。 - slf4j可能会限制第三方日志的功能,比如不支持log4j的renderer(该功能可以让info方法打印对象,并且使用特定的类解析这个对象)。
log4j初始化以及使用
- 如果
LogManager
没有初始化,使用静态区初始化(statc{}
)- 新建对象
org.apache.log4j.Hierarchy
,这个对象用来维护、新建所有的Logger
- 使用
OptionConverter.selectAndConfigure
解析配置,并初始化好Hierarchy
,真正解析配置的类是Configurator
,因为程序中只维护了一个Hierarchy
,并且解析方法,以这个对象为参数,所以可以在程序运行时,调用解析方法,动态更改配置。 - 初始化过程中,默认创建一个
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
。- 创建
logger
时,会维护好每一个Logger
的父Logger
,父子关系是用类似java里的父子包来关联的,比如x.y
是x.y.z
的父,如果父Logger
不存在,就继续向上找爷,一直到rootLogger
。 - 如果
Logger
配置了appender
,则解析出appender
并初始化好参数(使用反射,所以配置的字段都是根据实际类的字段来的),创建一个appender
实例,设置到Logger
的AppenderAttachable aai
字段。
- 创建
- 新建对象
log4j
使用org.apache.log4j.LogManager.getLogger(name)/LogManager.getRootLogger()
获取Logger
实例,实际是调用Hierarchy.getLogger()
方法- 创建的
Logger
还是以 “x.y
是x.y.z
的父”这种关系维护好所有的Logger
,代码里面创建的Logger
和配置里面配置的log4j.logger.rembau.test=info, M
区别是,配置文件里面可以配置appender
维护在Logger
的AppenderAttachable aai
字段里,而代码里面如果getLogger(name)
name没有在配置文件里配置过,aai
字段为null。 - 打印日志时,调用
logger.info()
,先判断当前logger
的最低日志级别level
是否小于info
,如果是进行下一步。如果当前logger
的aai
不为null,再调用appenderAttachable.appendLoopOnAppenders(LoggingEvent)
,迭代调用每一个appender
的doAppend
方法。如果当前Logger
的additive
为 false,则结束,否则调用父logger.info()
同样逻辑一直到rootLogger
。
- 创建的
log4j维护logger父子关系,详细算法
- 获取x.y.z logger时,存储x.y.z为有效节点,存储x、x.y为临时节点,并记录x-z、x.y-x.y.z的关系;记录父节点为root。
- 获取x.y时,把存储的x.y临时节点转换为有效节点,找到x临时节点,然后记录x-x.y关系;更新x.y-x.y.z关系,x.y.z的父节点为x.y;记录x.y的父节点为root
- 获取x时,把存储的x临时节点转换为有效节点;更新x-x.y,x.y的父节点为x,记录x的父节点为root;更新x-x.y.z,父节点是x的子节点,跳过,不处理
- 获取x时,把存储的x临时节点转换为有效节点;更新x-x.y.z,x.y.z的父节点为x,记录x的父节点为root
- 获取x.y时,把存储的x.y转换为有效节点,找到x节点,设置为父节点;更新x.y-x.y.z,x.y.z的父节点不为x.y的子节点,更新x.y.z父节点为x.y
- 获取x.y时,把存储的x.y临时节点转换为有效节点,找到x临时节点,然后记录x-x.y关系;更新x.y-x.y.z关系,x.y.z的父节点为x.y;记录x.y的父节点为root
概述:获取logger时,从底向上查找所有的祖宗节点,如果存在记录父节点为改祖宗,并返回;否则记录所有祖宗节点,并记录祖宗与该节点的关系;一个祖宗可以和多个子节点产生关系;如果该logger被列为祖宗,则更新所有相关的子节点,如果子节点的父节点不是该logger的子节点,更新子节点的父节点为当前logger。
获取类名,行号的方法
- 在每一个LogEvent中,记录应用调用日志组件的类为FQCN,比如org.slf4j.impl.Log4jLoggerAdapter、org.apache.log4j.Logger
- 需要解析成日志文字时,在任意地方new Throwable(),使用new Throwable().getStackTrace(),返回StackTraceElement[]
- 分析每一个StackTraceElement,如果getClassName与logEvent中的FQCN一致,则认为上一个StackTraceElement是我们调用日志的起点,然后使用StackTraceElement的getFileName、getLineNumber、getClassName、getMethodName获取信息。
log4j配置样例
log4j.rootCategory=info, stdout, R
log4j.logger.dubboMonitor= debug, dubbo
log4j.additivity.dubboMonitor= false
log4j.logger.com.alibaba.dubbo=WARN
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=<%d{HH:mm:ss,SSS}> %5p (%F:%L) [%t] (%c) - %m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=./logs/day.log
log4j.appender.R.bufferedIO=true
log4j.appender.R.MaxFileSize=10240KB
log4j.appender.R.Threshold = INFO
log4j.appender.R.MaxBackupIndex=100
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=<%d> %p (%F:%L) [%t] %c - %m%n
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
里,因为它的父logger
是rootLogger
log4j.additivity.dubboMonitor= false
,配置不往父logger
里输出日志log4j.logger.com.alibaba.dubbo=WARN
,这个与第二行的配置区别是,只配置了日志级别,没有配置appender
,所以只会限制日志级别,日志数据最终还是在父logger
的appender
输出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
日志时,父logger
的appender
也会输出日志。因为校验日志级别是在info
方法里的,但是向上往父logger
输出日志时直接调的logger.aai.appendLoopOnAppenders(event)
方法 logger
往appender
里输出日志时,需要给当前logger
加锁,具体代码可以看category.callAppenders
,这里我还不理解为什么要加锁,可能是考虑到有的appender
不支持同步,大量日志时,这里会阻塞。- 配置里
log4j.rootCategory
和log4j.rootLogger
是等价的;log4j.logger.dubboMonitor
和log4j.category.dubboMonitor
是等价的,等等。、 - 应用中可以使用
PropertyConfigurator.configure()
静态方法,动态更新配置。如果想重置logger
在新的配置里面加上log4j.reset=true
,这个配置可以清除掉,旧配置有新配置没有的的配置。
slf4j和log4j源代码解析以及详解的更多相关文章
- slf4j、log4j、 logback关系详解和相关用法
slf4j log4j logback关系详解和相关用法 写java也有一段时间了,一直都有用slf4j log4j输出日志的习惯.但是始终都是抱着“拿来主义”的态度,复制粘贴下配置文件就开始编码了, ...
- java 日志体系(三)log4j从入门到详解
java 日志体系(三)log4j从入门到详解 一.Log4j 简介 在应用程序中添加日志记录总的来说基于三个目的: 监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析工作: 跟踪代 ...
- HiveSQL解析过程详解 | 学步园
HiveSQL解析过程详解 | 学步园 http://www.xuebuyuan.com/2210261.html
- log4j.properties 的使用详解
一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR 为严重错误 主要是程序的错误WARN 为一般警告,比如session丢失IN ...
- 深入解析ThreadLocal 详解、实现原理、使用场景方法以及内存泄漏防范 多线程中篇(十七)
简介 从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本 ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个 ...
- Solr系列五:solr搜索详解(solr搜索流程介绍、查询语法及解析器详解)
一.solr搜索流程介绍 1. 前面我们已经学习过Lucene搜索的流程,让我们再来回顾一下 流程说明: 首先获取用户输入的查询串,使用查询解析器QueryParser解析查询串生成查询对象Query ...
- (转)DNS解析过程详解
DNS解析过程详解 原文:http://blog.csdn.net/crazw/article/details/8986504 先说一下DNS的几个基本概念: 一. 根域 就是所谓的“.”,其实我们的 ...
- DNS解析过程详解(转载)
DNS解析过程详解(转载) DNS Domain Name System 域名系统,它就是根据域名查出IP地址. 先说一下DNS的几个基本概念: 一. 根域 就是所谓的“.”,其实我们的网址ww ...
- JAVA中的四种JSON解析方式详解
JAVA中的四种JSON解析方式详解 我们在日常开发中少不了和JSON数据打交道,那么我们来看看JAVA中常用的JSON解析方式. 1.JSON官方 脱离框架使用 2.GSON 3.FastJSON ...
随机推荐
- 用layui遇到过的问题
1.报错“layui.form is not a function”问题 把代码中这一串修改一下:form = layui.form(); 括号去掉就行: form = layui.form; 如果你 ...
- apicloud 基础
时间成本 人力成本 很多人想开发app 又碍于时间和金钱成本 . 本色对app 要求不高的话. 混合app 开发是一种很好的方式. apicloud 就是一种很好的方式. apicloud ...
- maven 服务器搭建 -- nexus
参考文档 http://blog.sina.com.cn/s/blog_5745d6cb0100hasa.html 首先下载nexus webapp,可以使用wget来下载: Java代码 wget ...
- innodb 源码分析 --锁
innodb引擎中的锁分两种 1)针对数据结构, 如链表 互斥锁 读写锁 http://mysqllover.com/?p=425 http://www.cnblogs.com/justfortast ...
- 【xsy1147】 异或(xor) 可持久化trie
我的脑回路可能比较奇怪. 我们对这些询问离线,将所得序列${a}$的后缀和建$n$棵可持久化$trie$. 对于一组询问$(l,r,x)$,我们在主席树上询问第$l$棵树$-$第r$+1$棵树中与$s ...
- Maven国内阿里镜像(Maven下载慢的解决方法)
Maven是当前流行的项目管理工具,但官方的库在国外经常连不上,连上也下载速度很慢.国内oschina的maven服务器很早之前就关了.今天发现阿里云的一个中央仓库,亲测可用. <mirror& ...
- easyui datagrid 清除缓存方法
easyui datagrid 清除缓存方法 今天在项目中做了一个添加合同编号页面,添加合同编号了,在datagrid列表上没有显示刚才添加的那个合同编号. 这个问题在IE上特别明显. 原因是添加编号 ...
- Python3之hashlib
简介: 用于加密相关的操作,代替了md5模块和sha模块,主要提供SHA1,SHA224,SHA256,SHA384,SHA512,MD5算法. 在python3中已经废弃了md5和sha模块,简单说 ...
- 【Java并发编程】:死锁
当线程需要同时持有多个锁时,有可能产生死锁.考虑如下情形: 线程A当前持有互斥所锁lock1,线程B当前持有互斥锁lock2.接下来,当线程A仍然持有lock1时,它试图获取lock2,因为线程B正持 ...
- python中使用eval() 和 ast.literal_eval()的区别 分类: Python 2015-05-11 15:21 1216人阅读 评论(0) 收藏
eval函数在python中做数据类型的转换还是很有用的.它的作用就是把数据还原成它本身或者是能够转化成的数据类型. 那么eval和ast.literal_val()的区别是什么呢? eval在做计算 ...