美文网首页
倒霉的java日志技术

倒霉的java日志技术

作者: pq217 | 来源:发表于2022-12-04 09:04 被阅读0次

    前言

    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("哈哈哈");
        }
    }
    

    相关文章

      网友评论

          本文标题:倒霉的java日志技术

          本文链接:https://www.haomeiwen.com/subject/zvoefdtx.html