log4j 版本_002-slf4j+log4j 使用及源码分析

log4j、log4j2、logback日志关系

1、Apache Log4j 是一个老的日志框架,并且是多年来最受欢迎的日志框架。2015 年 8 月 5 日,该项目管理委员会宣布 Log4j 1.x 已达到使用寿命。 建议用户使用 Log4j 1 升级到 Apache Log4j2。

2、logback 是由 log4j 创始人设计的又一个开源日志组件,作为流行的 log4j 项目的后续版本,从而替代 log4j。原生实现了SLF4J。

3、Apache Log4j 2是对 Log4j 的升级,它比其前身 Log4j 1.x 提供了重大改进,并提供了 Logback 中可用的许多改进,同时修复了 Logback 架构中的一些固有问题。

log4j2包含基于lmax disruptor库的下一代异步记录器(Asynchronous Loggers)。在多线程场景中,异步日志记录器的吞吐量是log4j1.x和Logback的18倍,延迟也要低几个数量级。log4j2的性能明显优于log4j1.x、Logback和java.util.logging,尤其是在多线程应用程序中。

4,队列的选择对于峰值吞吐量非常重要。log4j2的异步记录器使用无锁数据结构(disruptor),而Logback、log4j1.2和log4j2的异步附加器使用ArrayBlockingQueue。对于阻塞队列,多线程应用程序在尝试将日志事件排队时经常遇到锁争用

性能比较:http://logging.apache.org/log4j/2.x/performance.html

1,异步日志吞吐量

1c49a075c54bcfdd46034858494bc654.png

Y轴:单位msg/sec , 每秒消息数。

X轴:线程数。

说明:随着线程数的增加,log4j2 使用全部异步loggers ,性能达到每秒处理1800万消息。

2,异步日志记录响应时间

8fcbb6a11ba63bfb34f8d93a327a8979.png

Y轴:单位:延迟毫秒数

X轴:响应时间百分比。

下面以springboot项目来介绍日志用法

新建一个项目,只引入spring-boot-starter-web stater.

1,Log4j 用法

说明:虽然新项目一般都不会用log4j了,但是一些老项目还在使用。学习也是必要的。

根据前面介绍:SpringBoot选用 SLF4j和logback默认实现;

(1)使用slf4j+log4j的方式,需要排除默认logback日志依赖,引入slf4j+log4j依赖.

jul-to-slf4j,jcl-over-slf4j 包是替换其他日志框架,使使用jul,jcl日志框架的类也通过slf4jAPI 输出日志,由log4j 来实现。

448a50aa8c1b3276ea58b0907c9a1f34.png
    org.springframework.boot    spring-boot-starter-web                        spring-boot-starter-logging            org.springframework.boot                org.slf4j    slf4j-log4j12    1.7.12    

(2)在resources目录下创建log4j.properties文件

### 设置###log4j.rootLogger = debug,console,File1,File2### 输出信息到控制抬 ###log4j.appender.console = org.apache.log4j.ConsoleAppenderlog4j.appender.console.Target = System.outlog4j.appender.console.layout = org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern = [%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] [%t]- %l - %m%n### 输出DEBUG 级别以上的日志到D:/logs/debug.log #### 日志记录到多个文件log4j.appender.File1 = org.apache.log4j.RollingFileAppender# 日志文件的名称log4j.appender.File1.File = D:/logs/debug.log# 默认设置为true,这意味着记录的信息被附加到同一文件的末尾log4j.appender.File1.Append = true# 标志的默认设置为true,这意味着输出流的文件被刷新,在每个追加操作log4j.appender.File1.ImmediateFlush=truelog4j.appender.File1.Threshold = DEBUG# 文件的回滚临界尺寸。默认值是10MB# 此示例配置说明每个日志文件的最大允许大小为5MB。当超过最大尺寸,新的日志文件将被创建# 因为maxBackupIndex被定义为2,当第二个日志文件达到最大值,第一个日志文件将被删除,# 之后所有的日志信息将被回滚到第一个日志文件。log4j.appender.File1.MaxFileSize = 5MB# 此属性表示要创建的备份文件的数量。默认值是1log4j.appender.File1.MaxBackupIndex = 10log4j.appender.File1.Encoding = UTF-8log4j.appender.File1.layout = org.apache.log4j.PatternLayoutlog4j.appender.File1.layout.ConversionPattern = [%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] [%t]- %l - %m%n### 输出ERROR 级别以上的日志到D:/logs/error.log #### 每天生成一个日志文件log4j.appender.File2 = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.File2.File =D:/logs/errorlog4j.appender.File2.Append = truelog4j.appender.File2.DatePattern = '.'yyyy-MM-dd-HH-mm'.log'log4j.appender.File2.Threshold = ERROR log4j.appender.File2.layout = org.apache.log4j.PatternLayoutlog4j.appender.File2.layout.ConversionPattern = [%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] [%t]- %l - %m%nlog4j.logger.com.example.springboot=debug,myLoglog4j.additivity.com.example.springboot=falselog4j.appender.myLog=org.apache.log4j.ConsoleAppenderlog4j.appender.myLog.Target=System.outlog4j.appender.myLog.Threshold=debuglog4j.appender.myLog.layout=org.apache.log4j.PatternLayoutlog4j.appender.myLog.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss:SSS}] %5p %c{1}:%L - %m%n

(3)代码中使用

public class Log4JTest {    private static final Logger logger = LoggerFactory.getLogger(Log4JTest.class);    public static void main(String[] args) {        // 记录debug级别的信息        logger.debug("This is debug message.");        // 记录info级别的信息        logger.info("This is info message.");        // 记录error级别的信息        logger.error("This is error message.");    }}

Log4J框架的不同组件的虚拟图:

fb20fa7bb781dd2baff1ac4067b22041.png

Filter对象:

可以过滤日志级别,比如只想输出INFO,不想输出WARN,ERROR到文件。

一个appender对象可以有与之关联的几个Filter对象。

日志管理:

日志管理对象管理的日志框架。它负责从一个系统级的配置文件或配置类读取初始配置参数。比如从resources目录下读取log4j.properties

log4j.properties文件是一个键 - 值对保存 log4j 配置属性文件。默认情况下,日志管理在CLASSPATH 查找一个名为 log4j.properties 的文件。

Log4j由三个重要的组件构成:

  • 日志信息的优先级:log4j.rootLogger = [ level ],xxxx 配置 level = debug
  • 日志信息的输出目的地:log4j.appender.xxxx = value 配置 ,value 就是目的地,比如org.apache.log4j.ConsoleAppender 表示输出到控制台。
  • 日志信息的输出格式。

日志信息的优先级从高到低有ERROR、WARN、 INFO、DEBUG,分别用来指定这条日志信息的重要程度;日志信息的输出目的地指定了日志将打印到控制台还是文件中;而输出格式则控制了日志信息的显示内容。

1.1. 配置根Logger,其语法为:

log4j.rootLogger = [ level ] , appenderName, appenderName, …

其中,level 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定义的级别。

Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。

比如:指定level = INFO , INFO级别以下日志不输出,输出自身及以上日志。appenderName就是指把日志信息输出到哪个地方,比如上面定义了输出到控制台(console),文件1(File1),文件2(File2)

定义非根Logger(很有用)

log4j.logger.loggerName1 = [ level ], appendName1,…appendNameN

比如我们只想输出程序中com.example.springboot包下的INFO日志,可以配置

log4j.logger.com.example.springboot=info  该级别日志优先级大于根level (如果根level=debug 或者 level = error, 以info为准) 

当然也可以自定义输出appender为myLog

log4j.logger.com.example.springboot=info,myLoglog4j.additivity.com.example.springboot=falselog4j.appender.myLog=org.apache.log4j.ConsoleAppenderlog4j.appender.myLog.Target=System.outlog4j.appender.myLog.Threshold=INFOlog4j.appender.myLog.layout=org.apache.log4j.PatternLayoutlog4j.appender.myLog.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss:SSS}] %5p %c{1}:%L - %m%n#    默认情况下子Logger会继承父Logger的appender,也就是说子Logger会在父Logger的appender里输出。#若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。

1.2. 配置文件的输出目的地Appender,一般,配置代码的格式如下

log4j.appender.appenderName = fully.qualified.name.of.appender.class  log4j.appender.appenderName.[option] = [value] 
log = /usr/home/log4jlog4j.appender.FILE.File=${log}/log.out需要注意的是log4j支持UNIX风格的变量替换,如 ${variableName}.

其中,Log4j提供的appender有以下几种:

  • org.apache.log4j.ConsoleAppender(控制台),
  • org.apache.log4j.FileAppender(文件),
  • org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),
  • org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件),
  • org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

Appender默认配置:

cb3f7da89ec7fbc64c073cfa79af3891.png

1.3.配置日志信息的格式(布局),其语法为

log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class  log4j.appender.appenderName.layout.option1 = value1  log4j.appender.appenderName.layout.[option] = [value]

其中,Log4j提供的layout有以下几种:

  • org.apache.log4j.HTMLLayout(以HTML表格形式布局),
  • org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
  • org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
  • org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
# 上面log4j.properties的console 的布局就是PatternLayoutlog4j.appender.console.layout = org.apache.log4j.PatternLayout# layout输出格式log4j.appender.console.layout.ConversionPattern = [%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] [%t]- %l - %m%n

Log4J的printf函数的打印格式格式化日志信息,打印的参数含义如下:

  • %m 输出代码中指定的消息
  • %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
  • %r 输出自应用启动到输出该log信息耗费的毫秒数
  • %c 输出所属的类目,通常就是所在类的全名
  • %t 输出产生该日志事件的线程名
  • %n 输出一个回车换行符
  • %d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyyy-MM-dd HH:mm:ss,SSS},输出类似:2020-10-11 12:25:12,345
  • %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] [%t]- %l - %m%n输出类似:[2020-12-11 22:40:02:803] [INFO] [main]- com.example.springboot.Log4JTest.main(Log4JTest.java:18) - This is info message.

1.4.日志记录到多个文件

log4j.appender.File1 = org.apache.log4j.RollingFileAppender# 日志文件的名称log4j.appender.File1.File = D:/logs/debug.log# 默认设置为true,这意味着记录的信息被附加到同一文件的末尾log4j.appender.File1.Append = true# 标志的默认设置为true,这意味着输出流的文件被刷新,在每个追加操作log4j.appender.File1.ImmediateFlush=truelog4j.appender.File1.Threshold = DEBUG# 文件的回滚临界尺寸。默认值是10MB# 此示例配置说明每个日志文件的最大允许大小为5MB。当超过最大尺寸,新的日志文件将被创建# 因为maxBackupIndex被定义为2,当第二个日志文件达到最大值,第一个日志文件将被删除,# 之后所有的日志信息将被回滚到第一个日志文件。log4j.appender.File1.MaxFileSize=5# 此属性表示要创建的备份文件的数量。默认值是1log4j.appender.File1.MaxBackupIndex=2log4j.appender.File1.Encoding = UTF-8log4j.appender.File1.layout = org.apache.log4j.PatternLayoutlog4j.appender.File1.layout.ConversionPattern = [%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] [%t]- %l - %m%n

1.5. 每天生成日志文件

DatePattern控制使用下列滚动的时间表方式之一:

1f0d98b540afb7ee9bc41b22c89c0a40.png
# 每天生成一个日志文件log4j.appender.File2 = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.File2.File =D:/logs/errorlog4j.appender.File2.Append = truelog4j.appender.File2.DatePattern = '.'yyyy-MM-dd'.log'log4j.appender.File2.Threshold = ERRORlog4j.appender.File2.layout = org.apache.log4j.PatternLayoutlog4j.appender.File2.layout.ConversionPattern = [%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] [%t]- %l - %m%n

学到这里,思考一下?如何配置只输出INFO 信息到指定文件,也就是该文件中不包含WARN,ERROR级别日志?

log4j提供了Appender的Threshold属性,可以设置该appender输出什么级别的日志。

log4j.appender.File1.Threshold=INFO 表示打印大于、等于该级别的日志,会输出了info、warn和error级别的日志

解决方案,这时要用到Filter

log4j.appender.File2 = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.File2.File =D:/logs/info.loglog4j.appender.File2.Threshold = INFOlog4j.appender.File2.layout = org.apache.log4j.PatternLayoutlog4j.appender.File2.layout.ConversionPattern = [%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] [%t]- %l - %m%n# 通过Filter 过滤设置最大,最小日志级别log4j.appender.File2.filter.infoFilter = org.apache.log4j.varia.LevelRangeFilterlog4j.appender.File2.filter.infoFilter.LevelMin = INFOlog4j.appender.File2.filter.infoFilter.LevelMax = INFO

另一种解决方法:重写RollingFileAppender类中的isAsSevereAsThreshold方法:

// 默认实现 ,如果没有设置threshold属性则全部打印,否则打印大于等于threshold属性的日志。

public boolean isAsSevereAsThreshold(Priority priority) {    return this.threshold == null || priority.isGreaterOrEqual(this.threshold);}public class MyInfoLog4jAppender extends RollingFileAppender {@Override    public boolean isAsSevereAsThreshold(Priority priority) {        return priority != null && this.threshold == null && priority.isGreaterOrEqual(this.threshold) &&                this.getThreshold().isGreaterOrEqual(priority);    }}

在log4j.properties文件中修改log4j.appender.File1=com.xxx.MyInfoLog4jAppender即可。

rootcategory与rootlogger区别?

在配置文件有时候会看到这样写法:

log4j.rootLogger = debug,console,File1,File2log4j.rootcategory= debug,console,File1,File2

通过官网查证:rootCategory 是废弃API , 建议使用rootLogger。为了保持兼容:Logger is a subclass of Category。所以请使用log4j.rootLogger。

到这里log4j的基本用法和常见问题就讲完了。如果遇到新的问题,可自行google.

接下来讲讲源码:

    org.slf4j    slf4j-log4j12    1.7.12以1.7.12版本讲解。心得:学习源码的方式,就是通过一个小demo,debug一下。public class Slf4jTest {   private static Logger logger = LoggerFactory.getLogger(Slf4jTest.class);   public static void main(String[] args){      if(logger.isDebugEnabled()){         logger.debug("slf4j-log4j debug message");      }      if(logger.isInfoEnabled()){         logger.debug("slf4j-log4j info message");      }      if(logger.isTraceEnabled()){         logger.debug("slf4j-log4j trace message");      }   }}

1,LoggerFactory.getLogger(Slf4jTest.class); 得到Logger的对象。

1.1 方法链:getILoggerFactory->performInitialization->bind->findPossibleStaticLoggerBinderPathSet()

findPossibleStaticLoggerBinderPathSet:从类路径中寻找org/slf4j/impl/StaticLoggerBinder.class类,可能有多个,比如你有log4j 和 logback 实现包,都包含StaticLoggerBinder.class。

reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); 打印绑定情况如下

SLF4J: Class path contains multiple SLF4J bindings.SLF4J: Found binding in [slf4j-log4j12-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J: Found binding in [logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]

说明:slf4j与其他实际的日志框架的集成jar包中,都会含有这样的一个org/slf4j/impl/StaticLoggerBinder.class类文件,并且提供一个ILoggerFactory的实现。

// the next line does the binding 随机选取绑定 StaticLoggerBinder

StaticLoggerBinder.getSingleton();

// 这里只有log4j ,所有返回Log4jLoggerFactory

return StaticLoggerBinder.getSingleton().getLoggerFactory();

1.2 iLoggerFactory.getLogger(name); 得到logger对象

->log4jLogger = LogManager.getLogger(name);

->logger = factory.makeNewLoggerInstance(name)->new Logger(name);

public class Log4jLoggerFactory implements ILoggerFactory {    ConcurrentMap loggerMap = new ConcurrentHashMap();    ...    public Logger getLogger(String name) {        org.apache.log4j.Logger log4jLogger;        if (name.equalsIgnoreCase("ROOT")) {            log4jLogger = LogManager.getRootLogger();        } else {            // 得到log4jLogger对象            // LogManager.java静态代码块会初始化Log4j,读取配置log4j.properties文件信息            log4jLogger = LogManager.getLogger(name);        }        // 适配器模式转换        Logger newInstance = new Log4jLoggerAdapter(log4jLogger);        // 缓存Logger对象        Logger oldInstance = (Logger)this.loggerMap.putIfAbsent(name, newInstance);        return (Logger)(oldInstance == null ? newInstance : oldInstance);    }}

2 logger.debug("slf4j-log4j debug message"); 如何实现输出的呢?

查看调用链路

743d7b2596a599969a0917aef46e6f07.png

可以找到下面代码:

this.qw.write(this.layout.format(event));--> Writer out; out.write(string);

使用layout格式化传入的message, 用输出流写入。

this.qw.flush();

qw=QuietWriter(osw,handler)

OutputStreamWriter osw = new OutputStreamWriter(System.out);

到此logger.debug方法输出,主线源码已分析完。

更多参考:https://my.oschina.net/xianggao/blog/519199

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值