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,异步日志吞吐量
Y轴:单位msg/sec , 每秒消息数。
X轴:线程数。
说明:随着线程数的增加,log4j2 使用全部异步loggers ,性能达到每秒处理1800万消息。
2,异步日志记录响应时间
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 来实现。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
<!--包含了slf4j log4j包-->
</dependency>
(2)在resources目录下创建log4j.properties文件
### 设置###
log4j.rootLogger = debug,console,File1,File2
### 输出信息到控制抬 ###
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.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=true
log4j.appender.File1.Threshold = DEBUG
# 文件的回滚临界尺寸。默认值是10MB
# 此示例配置说明每个日志文件的最大允许大小为5MB。当超过最大尺寸,新的日志文件将被创建
# 因为maxBackupIndex被定义为2,当第二个日志文件达到最大值,第一个日志文件将被删除,
# 之后所有的日志信息将被回滚到第一个日志文件。
log4j.appender.File1.MaxFileSize = 5MB
# 此属性表示要创建的备份文件的数量。默认值是1
log4j.appender.File1.MaxBackupIndex = 10
log4j.appender.File1.Encoding = UTF-8
log4j.appender.File1.layout = org.apache.log4j.PatternLayout
log4j.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.DailyRollingFileAppender
log4j.appender.File2.File =D:/logs/error
log4j.appender.File2.Append = true
log4j.appender.File2.DatePattern = '.'yyyy-MM-dd-HH-mm'.log'
log4j.appender.File2.Threshold = ERROR
log4j.appender.File2.layout = org.apache.log4j.PatternLayout
log4j.appender.File2.layout.ConversionPattern = [%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] [%t]- %l - %m%n
log4j.logger.com.example.springboot=debug,myLog
log4j.additivity.com.example.springboot=false
log4j.appender.myLog=org.apache.log4j.ConsoleAppender
log4j.appender.myLog.Target=System.out
log4j.appender.myLog.Threshold=debug
log4j.appender.myLog.layout=org.apache.log4j.PatternLayout
log4j.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框架的不同组件的虚拟图:
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,myLog
log4j.additivity.com.example.springboot=false
log4j.appender.myLog=org.apache.log4j.ConsoleAppender
log4j.appender.myLog.Target=System.out
log4j.appender.myLog.Threshold=INFO
log4j.appender.myLog.layout=org.apache.log4j.PatternLayout
log4j.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/log4j
log4j.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默认配置:
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 的布局就是PatternLayout
log4j.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=true
log4j.appender.File1.Threshold = DEBUG
# 文件的回滚临界尺寸。默认值是10MB
# 此示例配置说明每个日志文件的最大允许大小为5MB。当超过最大尺寸,新的日志文件将被创建
# 因为maxBackupIndex被定义为2,当第二个日志文件达到最大值,第一个日志文件将被删除,
# 之后所有的日志信息将被回滚到第一个日志文件。
log4j.appender.File1.MaxFileSize=5
# 此属性表示要创建的备份文件的数量。默认值是1
log4j.appender.File1.MaxBackupIndex=2
log4j.appender.File1.Encoding = UTF-8
log4j.appender.File1.layout = org.apache.log4j.PatternLayout
log4j.appender.File1.layout.ConversionPattern = [%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] [%t]- %l - %m%n
1.5. 每天生成日志文件
DatePattern控制使用下列滚动的时间表方式之一:
# 每天生成一个日志文件
log4j.appender.File2 = org.apache.log4j.DailyRollingFileAppender
log4j.appender.File2.File =D:/logs/error
log4j.appender.File2.Append = true
log4j.appender.File2.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.File2.Threshold = ERROR
log4j.appender.File2.layout = org.apache.log4j.PatternLayout
log4j.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.DailyRollingFileAppender
log4j.appender.File2.File =D:/logs/info.log
log4j.appender.File2.Threshold = INFO
log4j.appender.File2.layout = org.apache.log4j.PatternLayout
log4j.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.LevelRangeFilter
log4j.appender.File2.filter.infoFilter.LevelMin = INFO
log4j.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,File2
log4j.rootcategory= debug,console,File1,File2
通过官网查证:rootCategory 是废弃API , 建议使用rootLogger。为了保持兼容:Logger is a subclass of Category。所以请使用log4j.rootLogger。
到这里log4j的基本用法和常见问题就讲完了。如果遇到新的问题,可自行google.
接下来讲讲源码:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
以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<String, Logger> 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"); 如何实现输出的呢?
查看调用链路
可以找到下面代码:
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
,