前言
java日志工具有很多种,开发人员几乎每天都在使用,但可能是因为用起来太简单了吧,很多人往往含糊了例如slf4j/log4j/logback这些工具的概念和区别,以至于log4j2漏洞爆发时都不知道对自己有没有影响
确实我觉得java的日志技术错综复杂,乱起八遭,借此文好好梳理一下这些常用日志工具,对他们的概念和使用统一记录一下
发展历程
发展历程本文根据发展历程逐一讲解,可以切实体会每个技术的产生时代背景及其定位
Log4j
最原始日志怎么打,就只能借助System.out.print
,性能极差不说,使用也很不方便,于是第三方Apache开源了一个易用日志工具:Log4j
log4j即Log for java
,它的功能比较强大,可以配置日志输出位置(控制台/文件等),可以配置日志格式、级别等很多功能,所有这些配置只需要写个配置文件即可
由于是第三方工具,所以肯定要引入jar包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
然后就是写配置文件,在resources目录下新增log4j.properties
文件
### 设置###
log4j.rootLogger = INFO,stdout
### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
我只做了最小化简单配置,可配置的功能还有很多,可以看官网,使用也很方便:
import org.apache.log4j.Logger;
public class Log4jApplication {
public static Logger logger = Logger.getLogger(Log4jApplication.class);
public static void main(String[] args) {
logger.info("各位客官里边请");
}
}
据说Apache基金会还曾经建议Sun引入Log4j到java的标准库中,但是Sun拒绝了
JUL
日志这种比较基础的功能却要第三方公司来实现,怎么也说不过去,于是Sun公司自jdk1.4开始加入自身的日志工具:JUL
他的特点就是不需要引入外部依赖即可使用
import java.util.logging.Logger;
public class JdkApplication {
public static Logger logger = Logger.getLogger("JdkApplication");
public static void main(String[] args) {
logger.info("各位客官里边请");
}
}
虽然不需要外部依赖,但对比于log4j,性能和易用都比较差,所以是比较鸡肋的存在
JCL(Commons Logging)
此时有两个比较常用的日志:log4j和jul,两种工具的代码写法完全不一样,由于没有统一的规范,应用程序和日志工具是一种强耦合的状态
强耦合当我想替换成日志工具时,是无法直接替换的,只能大刀扩福的修改应用程序
更换日志可以想一下,日志几乎每个类都在使用,真要修改是多大的工程
于是2002年8月Apache又推出了日志接口Jakarta Commons Logging,即JCL
,它整合常用的日志框架(主要就是Log4j和Jul),只要你使用JCL
,就可以随时切换日志框架而不用改代码,相当于一个日志门户
JCL只整合了当时四种日志工具:Log4j,Jul,SimpleLog(Jcl内置的日志实现),
Jdk13LumberjackLogger(老版本jdk日志),主要通过加载这些工具包的类来实现的,优先级是Log4j>Jul>Jdk13LumberjackLogger>SimpleLog
使用Jcl首先要引入依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
此时java代码
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class JclApplication {
public static Log logger = LogFactory.getLog(JclApplication.class);
public static void main(String[] args) {
logger.info("各位客官里边请");
}
}
此时由于没有引入log4j依赖,Jcl可以加载到Jul的类,所以内部使用的Jul进行日志输出
Jul输出如果想切换log4j,加入依赖:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
同时编写log4j配置文件,就可以使用log4j输出(优先级高于Jul),代码完全不用改
log4jJcl的出发点很好,初步实现了日志的统一门户,日志和应用程序从此解耦,但问题很多,首先支持的日志实现有限(就那么四个),而且后续又陆续除了很多问题:性能低/引发混乱/内存泄漏
Slf4j
过了一段时间,Log4j的作者Ceki Gülcü从Apache离职,也由于Jcl实在不给力,这位大佬就写了一个新的日志门户:Slf4j
它的设计更为合理,提供了统一的日志规范接口slf4j-api
,并且对于不同的日志工具使用桥接方式来实现接口,使得Slf4j
可以支持更多的日志实现并且可以通过桥接来使用未来的日志技术,比如:
-
slf4j-jdk
(桥接JUL日志实现) -
slf4j-log4j
(桥接log4j日志实现)
并且Slf4j
还优化了日志的使用:比如变量占位符的替换功能
由于Slf4j只是日志门面,所以还是要先选好具体的日志框架,比如我们选择log4j,则引入依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
这个依赖引入了三个jar包
slf4j-log4j12
-
slf4j-api
(slf4j作为门面的统一api,相当于门面接口) -
slf4j-Logj12
(slf4j接口基于Log4j的实现) -
log4j
(log4j日志)
java代码使用,由于使用了Log4j,所以log4j.properties配置文件也要有
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jApplication {
// slf4j-api 统一的写法
public static Logger logger = LoggerFactory.getLogger(Slf4jApplication.class);
public static void main(String[] args) {
String name = "pq";
logger.info("你好:{}", name);
}
}
这时如果我们不想使用log4j了,想使用JUL,只需要修改依赖变为
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.30</version>
</dependency>
代码完全不用改,就可以轻松切换日志框架,如果我们自己写了一个日志实现,也可以通过桥接的方式使其符合Slf4j
logback
logback与log4j和jul一样,都是实打实的日志输出技术,它出现时slf4j已经被广泛使用了,它也是Log4j及Slf4j的作者Ceki Gülcü写的,主要是为了弥补Log4j的不足之处,同时为Slf4j提供一个标准的实现
log4j、jul日志框架早于slf4j,都是通过桥接方式来实现slf4j,而logback设计之初就是Slf4j的一个是实现
使用logback引入如下依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.11</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
可以看到须要引入slf4j-api
,使用方式还是slf4j那一套,完全没有变化:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackApplication {
public static Logger logger = LoggerFactory.getLogger(LogbackApplication.class);
public static void main(String[] args) {
String name = "pq";
logger.info("你好:{}", name);
}
}
logback性能肯定要比log4j更好,否则同一个作者也没必要瞎折腾,具体区别自行百度吧
log4j2
Ceki Gülcü离开Apache之后把日志行业搞得天翻地覆,Slf4j和logback都风生水起
老东家Apache也不甘示弱,推出了新一代的log4j即log4j2
,说是log4j2,但和log4j是没什么关系的,是一个完全的新项目,几乎涵盖Logback所有的特性,并且也搞起了分离设计,分成log4j-api和log4j-core,这个log4j-api也是日志门面,log4j-core才是日志的实现
log4j-api对标slf4j-api
log4j-core对标logback
Apache当然也希望log4j-api取代slf4j成为新的行业规范,于是也是通过一些列的桥接器去桥接各种其他日志实现
log4j2也可以像slf4j一样占位符替换变量,当时引起行业暴动的远程调用漏洞就是这个
使用log4j2引入如下依赖
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
对应代码如下
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2Application {
public static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {
String name = "${java:vm}";
logger.error("请爱的: {}", name);
}
}
这个代码展示了Log4j2的使用,也展示了Log4j2的漏洞,打印结果如下
Log4j2漏洞如果name是一个客户端的传递参数,那么此时相当于在我们的服务器执行了别人的代码
总结
从java日志的发展历程来看,之所以这么复杂多样,个人感受就是这些公司或个人之见互相斗法,谁也不服谁,谁都想当领头者
其实有了门户用什么实现就不重要了,因为可以随时换吗,关键门户还这么多(主要slf4j和log4j2),真的头大
当前来看,好像大家还是一般使用slf4j当做日志门户,具体的日志实现使用log4j2或者logback
如果你使用springboot,默认是logback做实现,springboot同时引入了slf4j-api和log4j2-api,想用哪个做门户你自己选,两大门户都支持,实现只有一个即logback
(看来springboot也被这复杂的门户之争搞得难以取舍)
import org.apache.logging.log4j.LogManager;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// 使用slf4j门户,内部调用logback实现
org.slf4j.Logger slf4jLog = LoggerFactory.getLogger(Application.class);
slf4jLog.info("哈哈哈");
// 使用log4j2门户,内部调用slf4j门户,再调用内部logback实现
org.apache.logging.log4j.Logger log4j2Log = LogManager.getLogger();
log4j2Log.info("哈哈哈");
}
}
网友评论