倒霉的java日志技术

前言

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

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),代码完全不用改

log4j

Jcl的出发点很好,初步实现了日志的统一门户,日志和应用程序从此解耦,但问题很多,首先支持的日志实现有限(就那么四个),而且后续又陆续除了很多问题:性能低/引发混乱/内存泄漏

Slf4j

过了一段时间,Log4j的作者Ceki Gülcü从Apache离职,也由于Jcl实在不给力,这位大佬就写了一个新的日志门户:Slf4j

它的设计更为合理,提供了统一的日志规范接口slf4j-api,并且对于不同的日志工具使用桥接方式来实现接口,使得Slf4j可以支持更多的日志实现并且可以通过桥接来使用未来的日志技术,比如:

  • slf4j-jdk (桥接JUL日志实现)
  • slf4j-log4j(桥接log4j日志实现)
Slf4j

并且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("哈哈哈");
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容