以下文章来源于鸭血粉丝
一、摘要
不管是使用何种编程语言,何种框架,日志输出几乎无处不再,也是任何商业软件中必不可少的一部分。
总结起来,日志的用途大致可以归纳成以下三种:
- 问题追踪:通过日志不仅仅包括我们程序的一些bug,也可以在安装配置时,通过日志可以发现问题。
- 状态监控:通过实时分析日志,可以监控系统的运行状态,做到早发现问题、早处理问题。
- 安全审计:审计主要体现在安全上,通过对日志进行分析,可以发现是否存在非授权的操作。
以 Java 编程语言为例,打印日志的方式有很多,例如通过System.out.print()
方法将关键信息输出到控制台,也可以通过 JDK 自带的日志Logger
类输出,虽然 JDK 从1.4开始支持日志输出,但是功能单一,无法更好的满足商业要求,于是诞生了很多第三方日志库,像我们所熟悉的主流框架log4j
、log4j2
、logback
等,提供的 API 功能都远胜 JDK 提供的Logger
。
二、Log4j
2.1、介绍
Log4j 是一种非常流行的日志框架,由Ceki Gülcü
首创,之后将其开源贡献给 Apache 软件基金会。
Log4j 有三个主要的组件:Loggers
(记录器),Appenders
(输出源)和Layouts
(布局)。这里可简单理解为日志类别、日志要输出的地方和日志以何种形式输出。
综合使用这三个组件可以轻松地记录信息的类型和级别,并可以在运行时控制日志输出的样式和位置。
Log4j 的架构大致如下:
当我们使用 Log4j 输出一条日志时,Log4j 自动通过不同的Appender
(输出源)把同一条日志输出到不同的目的地。例如:
- console:输出到屏幕;
- file:输出到文件;
- socket:通过网络输出到远程计算机;
- jdbc:输出到数据库
在输出日志的过程中,通过Filter
来过滤哪些log
需要被输出,哪些log
不需要被输出。
在Loggers
(记录器)组件中,级别分五种:DEBUG
、INFO
、WARN
、ERROR
和FATAL
。
这五个级别是有顺序的,DEBUG
< INFO
< WARN
< ERROR
< FATAL
,分别用来指定这条日志信息的重要程度,明白这一点很重要,Log4j
有一个规则:只输出级别不低于设定级别的日志信息。
假设Loggers
级别设定为INFO
,则INFO
、WARN
、ERROR
和FATAL
级别的日志信息都会输出,而级别比INFO
低的DEBUG
则不会输出。
最后,通过Layout
来格式化日志信息,例如,自动添加日期、时间、方法名称等信息。
具体输出样式配置,可以参考如下内容Log4j2 - Layouts布局介绍
2.2、项目应用
以 Java 项目为例,在 Maven 的pom.xml
中添加如下依赖!
2.2.1、添加 maven 依赖
org.slf4j
slf4j-api
1.6.6
org.slf4j
slf4j-log4j12
1.6.6
log4j
log4j
1.2.17
2.2.2、创建log4j配置
在实际应用中,要使Log4j
在系统中运行须事先设定配置文件。
配置文件实际上也就是对Logger
、Appender
及Layout
进行相应设定。
Log4j
支持两种配置文件格式,一种是XML
格式的文件,一种是properties
属性文件,二选一。
创建一个log4j.xml或者log4j.properties,将其放入项目根目录下。
1、XML格式
2、properties格式
log4j.rootLogger=INFO,M,C,E
log4j.additivity.monitorLogger=false
# INFO级别文件输出配置
log4j.appender.M=org.apache.log4j.DailyRollingFileAppender
log4j.appender.M.File=/logs/info.log
log4j.appender.M.ImmediateFlush=false
log4j.appender.M.BufferedIO=true
log4j.appender.M.BufferSize=16384
log4j.appender.M.Append=true
log4j.appender.M.Threshold=INFO
log4j.appender.M.DatePattern='.'yyyy-MM-dd
log4j.appender.M.layout=org.apache.log4j.PatternLayout
log4j.appender.M.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} %p %l %m %n
# ERROR级别文件输出配置
log4j.appender.E=org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File=/logs/error.log
log4j.appender.E.ImmediateFlush=true
log4j.appender.E.Append=true
log4j.appender.E.Threshold=ERROR
log4j.appender.E.DatePattern='.'yyyy-MM-dd
log4j.appender.E.layout=org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} %p %l %m %n
# 控制台输出配置
log4j.appender.C=org.apache.log4j.ConsoleAppender
log4j.appender.C.Threshold=INFO
log4j.appender.C.layout=org.apache.log4j.PatternLayout
log4j.appender.C.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %l %m %n
2.2.3、log4j使用
在需要打印日志的类中,引入Logger
类,在需要的地方打印即可!
package org.example.log4j.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogPrintUtil {
/**log静态常量*/
private static final Logger logger = LoggerFactory.getLogger(LogPrintUtil.class);
public static void main(String[] args){
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");
}
}
当然你还可以这样写
if(logger.isInfoEnabled()) {
logger.info("info信息");
}
if(logger.isWarnEnabled()) {
logger.warn("warn信息");
}
2.2.4、isInfoEnabled()有何作用呢?
简单来说,在某些场景下,用isInfoEnabled()
方法判断下是能提升性能的!
例如我们打印这段内容logger.info("User:" + userId + appId)
,程序在打印这行代码时,先对内容("User:" + userId + appId)
进行字符串拼接,然后再输出。
如果当前配置文件中日志输出级别是info
,是直接输出的,当日志输出级别是error
时,logger.info()
的内容时不输出的,但是我们却进行了字符串拼接,如果加上if(logger.isInfoEnabled())
进行一次判定,logger.info()
就不会执行,从而更好的提升性能,这个尤其是在高并发和复杂log
打印情况下提升非常显著。
另外,ERROR
及其以上级别的log信息是一定会被输出的,所以只有logger.isDebugEnabled
、logger.isInfoEnabled
和logger.isWarnEnabled()
方法,而没有logger.isErrorEnabled
方法
三、Log4j2
3.1、介绍
log4j2 是 log4j 1.x 的升级版,参考了 logback 的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要特点有:
- 异常处理:在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
- 性能提升, log4j2相较于log4j 1和logback都具有很明显的性能提升,后面会有官方测试的数据。
- 自动重载配置:参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用——那对监控来说,是非常敏感的。
- 无垃圾机制:log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc。
3.2、项目应用
3.2.1、添加 maven 依赖
org.slf4j
slf4j-api
1.7.13
org.slf4j
jcl-over-slf4j
1.7.13
runtime
org.apache.logging.log4j
log4j-api
2.4.1
org.apache.logging.log4j
log4j-core
2.4.1
org.apache.logging.log4j
log4j-slf4j-impl
2.4.1
com.lmax
disruptor
3.2.0
3.2.2、创建log4j2配置
在项目的根目录下创建一个log4j2.xml
的文件,与log4j
相比,log4j2
的异步输出日志性能非常强劲,配置如下:
1、同步输出日志
/logs/log4j2
/logs/log4j2/history
2、异步输出日志
/logs/log4j2
/logs/log4j2/history
详细 API 可以参考官方网站!
3.2.3、log4j2使用
与 log4j 类似,直接在需要位置打印日志即可
package org.example.log4j.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogPrintUtil {
/**log静态常量*/
private static final Logger logger = LoggerFactory.getLogger(LogPrintUtil.class);
public static void main(String[] args){
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");
}
}
四、Logback
4.1、介绍
Logback 也是用 java 编写一款非常热门的日志开源框架,由 log4j 创始人写的,性能比 log4j 要好!
logback 主要分为3个模块:
- logback-core:核心代码模块
- logback-classic:log4j的一个改良版本,同时实现了slf4j的接口,这样你如果之后要切换其他日志组件也是一件很容易的事
- logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能
4.2、项目应用
4.2.1、添加 maven 依赖
ch.qos.logback
logback-classic
1.2.3
org.codehaus.janino
janino
2.7.8
4.2.2、创建logback配置文件
1、配置说明
logback在启动的时候,会按照下面的顺序加载配置文件:
- 如果java程序启动时指定了
logback.configurationFile
属性,就用该属性指定的配置文件。如java -Dlogback.configurationFile=/path/to/mylogback.xml Test
,这样执行Test
类的时候就会加载/path/to/mylogback.xml
配置 - 在
classpath
中查找logback.groovy
文件 - 在
classpath
中查找logback-test.xml
文件 - 在
classpath
中查找logback.xml
文件 - 如果是
jdk6+
,那么会调用ServiceLoader
查找com.qos.logback.classic.spi.Configurator
接口的第一个实现类 - 自动使用
ch.qos.logback.classic.BasicConfigurator
,在控制台输出日志
上面的顺序表示优先级,使用java -D
配置的优先级最高,只要获取到配置后就不会再执行下面的流程。相关代码可以看ContextInitializer#autoConfig()
方法。
2、同步输出日志
${CONTEXT_NAME}
${CUSTOM_LOG_PATTERN}
UTF-8
log/testC.%d{yyyy-MM-dd}.%i.log
30
100MB
WARN
ACCEPT
DENY
${CUSTOM_LOG_PATTERN}
UTF-8
${logs.dir}/logback-test.log
${logs.dir}/logback-test.%i.log
1
3
INFO
ACCEPT
DENY
30MB
${CUSTOM_LOG_PATTERN}
UTF-8
注意:logback
如果配置要输出行号,性能会明显降低,如果不是必须,建议不要配置!
4.2.3、logback使用
package org.example.logback.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogPrintUtil {
/**log静态常量*/
private static final Logger logger = LoggerFactory.getLogger(LogPrintUtil.class);
public static void main(String[] args){
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");
}
}
五、SLF4J桥接
细心的你,会发现上面代码使用时,都使用的是private static final Logger logger = LoggerFactory.getLogger(LogPrintUtil.class)
这个,其中都来自org.slf4j
包,SLF4J
是啥?有什么作用呢?
SLF4J
本身并不输出日志,最大的特色是:它可以通过适配的方式挂接不同的日志系统,属于一个日志接口。
如果项目适配到log4j
就使用log4j
日志库进行输出;如果适配到logback
就使用logback
日志库进行输出;如果适配到log4j2
就使用log4j2
日志库进行输出。
这样最大的好处,就是当你想将项目从log4j
换成log4j2
的时候,只需要在项目pom.xml
中进行桥接适配即可,不用修改具体需要打印日志的代码!
六、三大主流日志框架性能比较
介绍了这么多,但是我们还不知道三个日志框架的日志输出性能如何,本文以10000条数据进行打印,比较log4j
、log4j2
、logback
日志的输出时间。
本次测试采用的是本地电脑(win7),每个电脑的配置不一样,测试的结果也不一样,结果是真实的。
- 同步输出
- 异步输出
从测试结果上可以看出:
- 不建议生产环境进行控制台输出;
- 在纯文件输出的环境下,
logback
的输出优于log4j2
,而log4j2
要优于log4j
,如果要进行生产环境的部署,建议采用logback
,如果是使用log4j2
,建议使用异步方式进行输出,输出结果基本是实时输出;
最后需要注意的地方是:log
有风险,输出需谨慎!
由于输出log
过程需要进行磁盘操作,且log4j
为了保证log
输出过程的线程安全性而使用同步锁,就使得输出log
成为很耗时的操作,所以log
信息一定要言简意赅,不要输出一些无用的log
。
七、总结
本文主要围绕项目中使用到的日志框架进行应用介绍,限于笔者的才疏学浅,对本文内容可能还有理解不到位的地方,如有阐述不合理之处还望留言一起探讨。