一、slf4j和log4j的关系:
也就是说slf4j仅仅是一个为Java程序提供日志输出的统一接口,并不是一个具体的日志实现方案,就比如JDBC一样,只是一种规则而已。必须搭配具体的log实现方案比如说log4j jdklogging等等,中间需要适配层做桥接,例如slf4j与log4j的桥接包:slf4j-log4j12-1.6.1.jar。
二、log4j加载过程:
代码中的用法一般如下:
Logger myLog = LoggerFactory.getLogger(XXXX.class); myLog.info(“this is info log text”); myLog.error(“this is error log text”); |
1、获取Logger对象步骤:
(1) 获取StaticLoggerBinder的单例对象;
(2) StaticLoggerBinder对象里有一个Log4jLoggerFactory对象,Log4jLoggerFactory对象里面有一个存储了Logger对象的hash表:
loggerMap = new ConcurrentHashMap<String, Logger>(); |
(3) getLogger(XXXX.class)时首先查这个hash表,如果查询到则直接返回,否则创建Log4jLoggerAdapter对象并添加到这个hash表中;
2、日志记录:
(1) Log4jLoggerAdapter对象作为Logger对象的代理对象,所有记录日志的info, error等方法都是传递到Logger对象去执行处理的;
三、Log4j配置文件解析过程:
Log4jLoggerFactory在构造函数中会调用LogManager的getRootLogger方法,LogManager的静态初始方法块中会检查配置文件并加载,可以是指定的Class类,也可以是xml格式配置文件,也可以是properties格式的配置文件;对于properties格式的配置文件,使用PropertyConfigurator类来读取和解析配置信息;
Properties格式的log4j的配置文件说明:
#Log4J配置文件实现了输出到控制台、文件、回滚文件、自定义标签,数据库等功能。仅供参考。 log4j.rootLogger=DEBUG,CONSOLE,FILE,DLOGFILE,ROLLING_FILE,MYSQL_LOG log4j.addivity.org.apache=true
#应用于控制台 log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Threshold=DEBUG log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyyMMdd-HH:mm:ss} %t %c %m%n
#应用于文件 log4j.appender.FILE=org.apache.log4j.FileAppender log4j.appender.FILE.File=d:\file.log log4j.appender.FILE.Append=false log4j.appender.FILE.layout=org.apache.log4j.PatternLayout log4j.appender.FILE.layout.ConversionPattern=%d{yyyyMMdd-HH:mm:ss} %t %c %m%n
#应用于按日期生成文件 log4j.appender.DLOGFILE=org.apache.log4j.DailyRollingFileAppender log4j.appender.DLOGFILE.File=d:\test.log log4j.appender.DLOGFILE.Threshold=INFO log4j.appender.DLOGFILE.DatePattern='.'yyyy-MM-dd log4j.appender.DLOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.DLOGFILE.layout.ConversionPattern=%d{yyyyMMdd-HH:mm:ss} %t %c %m%n
#应用于文件回滚 log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender log4j.appender.ROLLING_FILE.Threshold=INFO log4j.appender.ROLLING_FILE.File=d:\rolling.log log4j.appender.ROLLING_FILE.Append=true log4j.appender.ROLLING_FILE.MaxFileSize=1KB log4j.appender.ROLLING_FILE.MaxBackupIndex=1 log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout log4j.appender.ROLLING_FILE.layout.ConversionPattern=%d{yyyyMMdd-HH:mm:ss} %t %c %m%n
# 数据库输出 log4j.appender.MYSQL_LOG=org.apache.log4j.jdbc.JDBCAppender log4j.appender.MYSQL_LOG.driver=com.mysql.jdbc.Driver log4j.appender.MYSQL_LOG.URL=jdbc:mysql://127.0.0.1:3306/txl log4j.appender.MYSQL_LOG.Threshold=ERROR log4j.appender.MYSQL_LOG.user=root log4j.appender.MYSQL_LOG.password= log4j.appender.MYSQL_LOG.sql=insert into log_monitor(level,category,thread,time,location,note) values('%p','%c','%t','%d{yyyy-MM-dd HH:mm:ss:SSS}','%l','%m') log4j.appender.MYSQL_LOG.layout=org.apache.log4j.PatternLayout #虽然以上布局 没啥效果,但是可以减少告警提示
#自定义Appender ,输出到任意地方 |
四、Log4j组件:
1.Logger:
继承Category(一种日志类),提供不同级别的日志接口(例:logger.info,logger.error等);当调用方调用了info, error等方法记录日志时,调用的是Log4jLoggerAdapter的方法,然后Log4jLoggerAdapter又将调用转给Logger对象去执行;
2.Appender:
appender就是日志输出地;日志的记录输出抽象,大致有控制台输出,文件输出等;
Logger对象在记录日志时,会遍历Logger对象上注册的所有Appender以及父类上注册的Appender,然后依次调用所有Appender的appendLoopOnAppenders方法记录日志;
主要实现类有WriteAppender,ConsoleAppender,FileAppender;WriterAppender将日志写入Java IO中,它继承自SkeletonAppender类。它引入了三个字段:immediateFlush,指定每写完一条日志后,即将日志内容刷新到设备中,因而一般推荐将该值设置为true,即默认值;econding用于定义日志文本的编码方式;qw定义写日志的writer,它可以是文件或是控制台等Java IO支持的流;
FileAppender的子类主要有DailyRollingFileAppender和RollingFileAppender:DailyRollingFileAppender会在每隔一段时间可以生成一个新的日志文件,不过这个时间间隔是可以设置的,不仅仅只是每隔一天。时间间隔通过setDatePattern()方法设置,datePattern必须遵循SimpleDateFormat中的格式;RollingFileAppender则是基于文件大小作为阀值。当日志文件超过指定大小,日志文件会被重命名成”日志文件名.1”,若此文件已经存在,则将此文件重命名成”日志文件名.2”,一次类推。若文件数已经超过设置的可备份日志文件最大个数,则将最旧的日志文件删除。如果要设置不删除任何日志文件,可以将maxBackupIndex设置成Integer最大值。
3.Layout:
实现了OptionHandler的抽象类;主要是对日志行的格式进行限定,常用有PatternLayout和HTMLLayout;
4.LoggerRepository:
常见的Hirearchy为其实现类,封装了框架的默认配置,还有Logger工厂,事件源,封装了一些列事件。
5.LoggingEvent:
封装了消息内容、级别、记录器类名的全名称等信息;当记录日志时,会将日志内容封装成LoggingEvent对象并调用Logger对象进行记录;
五、流程图:
1、调用方调用LoggerFactory.getLogger(XXXX.class)时,首先获取具体日志实现框架的LoggerFactory类Log4jLoggerFactory,在Log4jLoggerFactory.getLogger(XXXX.class)时,首先从hashMap里面去获取,如果获取不到则创建,创建时首先获取getLoggerRepository(也就是Hierarchy),然后在Hierarchy的hashMap里面去获取,如果找到则直接返回,否则创建新的Logger对象并添加到hashMap里面后返回;
2、记录日志时调用的info, debug, error等方法都是调用的适配器对象Log4jLoggerAdapter里面的方法,Log4jLoggerAdapter会将调用转到Logger对象上;接着判断Logger实例对应的日志记录级别(每个Logger实例都有自己的Level)是否要比请求的级别低→若是则调用forcedLog记录日志;
3、接着创建LoggingEvent实例→将LoggingEvent实例传递给appender,Appender调用Layout实例格式化日志消息;最后Appender将格式化后的日志信息写入改Appender对应的日志输出中。
六、相关问题:
1.如何实现log4j和slf4j的解耦:也即如何实现将log4j的实现绑定到slf4j的接口定义上?
LoggerFactory的绑定:
LoggerFactory是一个定义在slf4j-api中的类,其getLogger通过StaticLoggerBinder的单实例对象获取到ILoggerFactory对象,StaticLoggerBinder是在slf4j-log4j插件里面定义的;最后获取Logger对象是在LogManager中创建Logger对象并返回的,LogManager和Logger对象都是在实现jar包log4j里面实现的;
2.Appender是如何注册到Logger上的?
PropertyConfigurator类的parseCategory方法解析配置文件log4j.properties的代码:
void parseCategory(Properties props, Logger logger, String optionKey, String loggerName, String value) { ................... // Begin by removing all existing appenders. logger.removeAllAppenders(); Appender appender; String appenderName; while(st.hasMoreTokens()) { appenderName = st.nextToken().trim(); if(appenderName == null || appenderName.equals(",")) continue; LogLog.debug("Parsing appender named "" + appenderName +""."); appender = parseAppender(props, appenderName); if(appender != null) { logger.addAppender(appender); } } } |
LogManager的静态代码中检测到配置文件log4j.properties的路径并加载这个配置文件,然后执行parseCategory方法解析配置文件;上面加粗部分,parseAppender会根据log4j.properties配置文件生成appender对象,然后将其添加到logger对象上;
七、参考资料:
https://my.oschina.net/xianggao/blog/518059
https://www.cnblogs.com/zeng-wei/archive/2012/08/28/2660363.html
https://blog.csdn.net/m0_37652164/article/details/80487522
https://blog.csdn.net/u011794238/article/details/50736331/
http://wiki.10101111.com/pages/viewpage.action?pageId=190240215
http://www.cnblogs.com/question-sky/p/7425069.html
http://www.cnblogs.com/question-sky/p/7429548.html
http://www.cnblogs.com/question-sky/p/7469596.html
https://www.cnblogs.com/question-sky/p/8436366.html
http://www.blogjava.net/DLevin/archive/2012/06/28/381667.html
http://www.blogjava.net/DLevin/archive/2012/11/04/390755.html