当前位置: 首页 > 科技观察

阿里巴巴开发手册强行使用SLF4J作为外观秘诀,想通了

时间:2023-03-23 01:53:38 科技观察

之前已经对Log4j进行了详细全面的介绍,相信小伙伴们已经完全掌握了。然后在看阿里巴巴开发手册松山版的时候(没有的话记得找我要),发现了一个“强制”的日志协议:日志系统(Log4j、Logback)不能使用直接在应用程序中。而不是使用SLF4J等日志框架中的API,使用门面模式的日志框架有利于维护和统一各个类的日志处理方法。(为什么要手写这段文字,因为我发现阿里巴巴开发手册有语言错误,看下面红色标出的部分)完了,有点牵强,手册的编辑可能是数学老师教的语言吧?)然后看到这个强制性的规定,忍不住想问一句:“阿里巴巴开发手册为什么强制使用SLF4J作为Log4J的门面呢?这背后隐藏着什么“不可告人”的秘密?(小伙伴们请配上央视12的BGM)PS:顺便给小伙伴们科普一下,阿里巴巴开发手册上出现的Jakarta其实是Apache软件基金会下的一个开源项目。其实Commons以前是属于Jakarta的,现在是Apache下的一个独立项目。阿里巴巴开发手册中的描述已经不合适了。将其替换为ApacheCommonsLogging会更合适。(忍不住又挑了一个阿里巴巴开发手册的问题,请原谅我“一丝不苟”的工作态度)01.什么是SLF4J?SLF4J是SimpleLoggingFacadeforJava(for≈4)的缩写,是简单的日志门面以外观模式(Facadepattern,一种为一组接口提供统一高层接口的设计模式)子系统,使子系统更易于使用),并支持java.util.logging、Log4J和Logback。SLF4J的作者是Log4J和Logback的作者。他的GitHub主页是这样的:秋风吹来的凉意,有没有?也许巨人不屑于维护他的GitHub主页?我的GitHub主页够惨的,没想到巨人比我还惨,终于可以吹牛了,“我沉默王二,GitHub主页比SLF4J、Log4J和Logback的作者CekiGulcu绿多了...”1996年初,欧洲安全电子市场项目决定编写自己的跟踪API,最终演变成Log4j,后者已经广受欢迎。2002年2月,Sun推出了自己的日志记录包java.util.logging(称为JUL)。据说是借鉴了Log4j的实现思路。毕竟此时的Log4j已经非常成熟了。2002年8月,Apache推出了自己的日志包,也就是阿里巴巴开发手册中提到的JCL(JakartaCommonsLogging)。JCL有远大的抱负。它提供了基于JUL和Log4j的抽象层接口,方便用户在JUL和Log4j之间切换。但是JCL好像不是很流行。有人这样抱怨:CekiGulcu也觉得JCL不好,不然他也不会在2005年推出一个新项目叫SLF4J吧?这是要付出代价的。SLF4J只有接口,没有实现。您不能强制Java和Apache实现SLF4J接口,对吗?这太难了,也不现实。但巨人之所以被称为巨人,是因为他拥有常人所无法比拟的惊人之处。他在SLF4J和JUL、Log4j、JCL之间架起了三座桥:巨人做到了,衣食无忧不是吗?起床甚至为您自己的Log4j搭建一座桥梁。面对豪门的嚣张气焰,我只想弱弱地说一句,“SLF4J是门面,你以为容易吗?”02.SLF4J解决了哪些痛点?春秋战国时期,各国都有自己的货币,用他国的货币不太合适吧?那么有交易的时候就比较麻烦了。如果币种不统一,是不能直接交易的,因为币种不一定等价。秦始皇统一六国后,实行了新的货币政策,全国统一使用一种货币,以前的问题就迎刃而解了。你看,同样的道理,还有JUL和JCL日志系统,CekiGulcu又写了两个,Log4j和Logback,各有优缺点,再加上千万用户,各有所爱,作为一个结果,不同的应用程序可能使用不同的日志记录系统。假设我们正在开发一个系统,打算使用SLF4J作为门面,Log4j作为日志系统。我们在项目中使用了A框架,而A框架的门面是JCL,日志系统是JUL,相当于维护了两套日志系统吧?这很不舒服!CekiGulcu想到了这个问题,帮我们解决了!看看SLF4J官网给出的解决方案。用jcl-over-slf4j.jar替换commons-logging.jar导入jul-to-slf4j.jar为了模拟这个过程,让我们使用JCL构建一个项目。第一步是在pom.xml文件中引入commons-logging.jar:commons-loggingcommons-logging1.2第二步新建测试类:packagecom.itwanger;importorg.apache.commons.logging.Log;importorg.apache.commons.logging.LogFactory;/***@author微信搜索《沉默之王二》",回复关键字PDF*/publicclassDemo{privatestaticLoglogger=LogFactory.getLog(Demo.class);publicstaticvoidmain(String[]args){logger.info("jcl");}}这个类会通过LogFactory获取一个Log对象,并使用info()方法打印日志行。在调试这段代码的过程中,你会发现Log有四种实现方式:如果不绑定Log4j,默认会选择Jdk14Logger——它返回的Logger对象就是java.util.logging.Logger,也就是JUL.因此在控制台可以看到如下信息:2020-10-213:13:30com.itwanger.Demomain信息:jcl如何将使用JCL的项目改造为使用SLF4J?第三步,使用jcl-over-slf4j.jar替换commons-logging.jar,并添加jul-to-slf4j.jar、slf4j-log4j12.jar(会自动导入slf4j-api.jar和log4j.jar):org.slf4jjcl-over-slf4j1.7.25org.slf4jjul-to-slf4j1.7.29org.slf4jslf4j-log4j121.7.25第四步,在resources目录下创建log4j.properties文件。内容如下:###Settings###log4j.rootLogger=debug,stdout,D###向控制台输出信息###log4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout。Target=System.outlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=[%-5p]%d{yyyy-MM-ddHH:mm:ss,SSS}方法:%l%n%m%n###将DEBUG级别以上的日志输出到=debug.log###log4j.appender.D=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.D.File=debug.loglog4j.appender.D.Append=truelog4j.appender.D.Threshold=DEBUGlog4j.appender.D.layout=org.apache.log4j.PatternLayoutlog4j.appender.D.layout.ConversionPattern=%d{yyyy-MM-ddHH:mm:ss}[%t:%r]-[%p]%m%n再次运行Demo类,你会发现在target目录下会生成一个名为debug.log的文件,内容如下:2020-10-2115:32:06[main:0]-[INFO]jcl并且可以在控制台查看到如下信息:[INFO]2020-10-2115:32:06,192method:com.itwanger.Demo.main(Demo.java:12)Jcl仔细对比一下,你会发现这次的输出格式和之前的不一样,这是Log4j和JUL的日志格式不同导致的。另外,你注意到了吗?我们没有改动测试类Demo,仍然采用JCL的方式获取Log:privatestaticLoglogger=LogFactory.getLog(Demo.class);但输出格式已切换为Log4j!SLF4J除了提供这个解决方案外,还绑定了Log4j来替代JUL和JCL;还提供了绑定Logback替换JUL、JCL、Log4j的方案:还有绑定JUL替换JCL的方案Log4j的方案:太强了,有吗?如果是,请在留言区输入666。03.SLF4J比Log4J好在哪里?除了解决上述痛点,SLF4J还帮助我们的应用程序独立于任何特定的日志系统。它还有一个非常牛逼的功能,就是SLF4J在打印日志的时候使用了占位符。character{},有点类似于String类的format()方法(用%s填充参数等),但是更方便,很大程度上提高了程序的性能。众所周知,字符串是不可变的,字符串拼接会创建很多不必要的字符串对象,极大地消耗内存空间。但是Log4J在打印带参数的日志时,只能使用字符串拼接:Stringname="沉默王二";intage=18;logger.debug(name+",age:"+age+",很不要脸的程序员");很繁琐,但是加入SLF4J后,这个问题就迎刃而解了。下面我们来看看在Log4j项目中添加SLF4J的详细步骤。第一步是用slf4j-log4j12替换log4j依赖(Maven会自动导入slf4j-api.jar和log4j.jar):org.slf4jslf4j-log4j121.7.25第二步是在resources目录下创建log4j.properties文件。内容和Log4j完全一样:###Settings###log4j.rootLogger=debug,stdout,D,E###向控制台输出信息###log4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.Target=System.outlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=[%-5p]%d{yyyy-MM-ddHH:mm:ss,SSS}方法:%l%n%m%n###输出DEBUG级别以上的日志到=debug.log###log4j.appender.D=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.D.File=debug.loglog4j.appender.D.Append=truelog4j.appender.D.Threshold=DEBUGlog4j。appender.D.layout=org.apache.log4j.PatternLayoutlog4j.appender.D.layout.ConversionPattern=%d{yyyy-MM-ddHH:mm:ss}[%t:%r]-[%p]%m%n###输出ERROR级别以上的日志到=error.log###log4j.appender.E=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.E.File=error.loglog4j.appender.E.Append=truelog4j.appender.E.Threshold=ERRORlog4j.appender.E.layout=org.apache.log4j.PatternLayoutlog4j.appender。E.layout.ConversionPattern=%d{yyyy-MM-ddHH:mm:ss}[%t:%r]-[%p]%m%n第三步新建测试类:packagecom.itwanger;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/***@作者微信搜索“沉默之王2”,回复关键字PDF*/publicclassLog4jSLF4JDemo{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(Log4jSLF4JDemo.class);publicstaticvoidmain(String[]args){logger.debug("{},是个很不要脸的程序员","沉默之王二");}}你看,使用占位符比“+”运算符方便多了,此时已经不需要isDebugEnabled()先判断,在字符串拼接之前会执行debug()方法。如果只是Log4J,会先拼接字符串,然后执行debug()方法。我们看示例代码:Stringname="沉默王二";intage=18;logger.debug(name+",age:"+age+",是个很不要脸的程序员");在调试这段代码的时候,你会发现,如下图:这意味着如果日志系统级别不是DEBUG,会进行更多的字符串拼接操作,是对性能的浪费。注意阿里巴巴开发手册中还有一个“强制”级别的规范:这是因为如果参数是基本数据类型,会先自动装箱(Integer.valueOf())。测试代码如下:logger.debug("沉默王二,{}岁",18);通过反编译工具可以看到:logger.debug("\u6C89\u9ED8\u738B\u4E8C\uFF0C{}\u5C81",Integer.valueOf(18));如果参数需要调用其他方法,则后续会调用debug()方法。也就是说如果没有isDebugEnabled(),如果不是DEBUG级别,就会进行更多的自动装箱和调用其他方法的操作——程序的性能会下降!测试类运行结果与之前Log4J的运行结果相同,小伙伴们可以点击链接跳转到Log4j对比。04.总结对本文做一个简单的总结。1)在使用日志系统时,一定要使用SLF4J作为门面。2)SLF4J可以统一日志系统。作为上层的抽象接口,不需要关注底层的日志实现。它可以是Log4j、Logback或JUL或JCL。3)SLF4J在打印日志时可以使用占位符,不仅提高了程序的性能(临时字符串少,垃圾回收的工作量小),而且代码美观统一。本文转载自微信公众号“沉默王二”,可通过以下二维码关注。转载本文请联系沉默王二公众号。