前言前段时间,Log4j爆发了相关的安全问题,各大公司都在紧急修复这个问题,我所在的公司也不例外。但是在排查相关问题的时候,发现大家对Java日志框架的整体认知还是比较混乱的,所以搜索了各大文章了解一下,记录一下,以备后续查阅或者使用。Java日志历史最开始,Java应用程序使用System.out/err.println()来跟踪自己的程序运行情况;1996年,E.U.SEMPER(欧洲安全电子市场)项目编写了自己的跟踪API,API演化为Log4j,主要作者为:CekiGülcü,后来该项目加入了Apache基金会项目;2002年2月,Java1.4发布,Sun推出了自己的日志库:JUL(JavaUtilLogging);2002年8月,Apache推出日志接口JCL(JakartaCommonsLogging)(日志抽象层);由于某些原因,CekiGülcü离开了Apache,并于2005年推出了一套新的日志接口Slf4j(SimpleLoggingFacadeforJava);由于使用了Slf4j,之前的日志产品都不是Slf4j的正统实现。2006年,CekiGülcü推出了另一款日志产品LogBack,完美实现了Slf4j;Slf4j+LogBack模型冲击了之前的JCL+Log4jForm,Apache在2012年推出了自己的新项目Log4j2,与Lg4j1.x完全不兼容。适配方式前面我们提到,Slf4j的出现晚于JUL、JCL、log4j等日志框架,所以这些日志框架不可能牺牲版本兼容性,改造接口来符合Slf4j接口规范。Slf4j也提前考虑到了这个问题,所以它不仅提供了统一的接口定义,还提供了不同日志框架的适配器(slf4j-log4j12/slf4j-jdk14);′但实际上很多Java应用应该依赖JCL,所以日志产品适配包不够用,于是就有了日志标准适配包(slf4j-jcl)。定义适配模式顾名思义就是进行适配,将不兼容的接口转化为兼容的接口,使得因接口不兼容而无法协同工作的类可以协同工作。实现方法ITarget表示要转换成的接口定义。Adaptee是一组与ITarget接口定义不兼容的接口。Adapter将Adaptee转化为一组符合ITarget接口定义的接口。ClassAdapter//类适配器:基于继承publicinterfaceITarget{voidf1();无效f2();voidfc();}publicclassAdaptee{publicvoidfa(){//...}publicvoidfb(){//...}publicvoidfc(){//...}}publicclassAdapter扩展Adaptee实现ITarget{publicvoidf1(){super.FA();}publicvoidf2(){//。..Reimplementf2()...}//这里fc()不用实现,直接继承自Adaptee,这是与对象适配器最大的区别}对象适配器//对象适配器:基于组合public接口ITarget{voidf1();无效f2();voidfc();}publicclassAdaptee{publicvoidfa(){//...}publicvoidfb(){//...}publicvoidfc(){//...}}publicclassAdapter实现ITarget{privateAdapteeadaptee;publicAdapter(Adapteeadaptee){this.adaptee=adaptee;}publicvoidf1(){adaptee.fa();//委托给Adaptee}publicvoidf2(){//...重新实现f2()...}publicvoidfc(){adaptee.fc();}}判断标准Adaptee接口数量的多少,Adaptee和ITarget的契合度,如果接口数量少,两种实现都可以接受;否则,判断适配的两者之间的大部分接口定义是否相同,如果相同,则可以使用类适配器(基于继承),这样可以用代码重复,减少代码的冗余;如果几个部分相同,可以使用对象适配器(基于组合)使代码更灵活Tips:代理模式为原类定义一个代理类,而不改变原类的接口,主要目的是控制访问,而不是增强功能,这是它与装饰者模式最大的区别。Slf4j中示例实现方案//slf4j统一接口定义包org.slf4j;publicinterfaceLogger{publicbooleanisTraceEnabled();公共无效跟踪(字符串消息);publicvoidtrace(字符串格式,对象参数);publicvoidtrace(字符串格式,对象arg1,对象arg2);publicvoidtrace(字符串格式,对象[]argArray);publicvoidtrace(Stringmsg,Throwablet);publicbooleanisDebugEnabled();publicvoiddebug(Stringmsg);无效调试(字符串格式,对象参数);publicvoiddebug(Stringformat,Objectarg1,Objectarg2)publicvoiddebug(Stringformat,Object[]argArray)publicvoiddebug(Stringmsg,Throwablet);//...省略了一堆info,warn,error等接口}//log4j日志框架的适配器//Log4jLoggerAdapter实现了LocationAwareLogger接口,//其中LocationAwareLogger继承自Logger接口,//是等价的到实现Logger接口的Log4jLoggerAdapter。packageorg.slf4j.impl;publicfinalclassLog4jLoggerAdapterextendsMarkerIgnoringBaseimplementsLocationAwareLogger,Serializable{finaltransientorg.apache.log4j.Logger记录器;//log4jpublicbooleanisDebugEnabled(){returnlogger.isDebugEnabled();}publicvoiddebug(Stringmsg){logger.log(FQCN,Level.DEBUG,msg,null);}publicvoiddebug(Stringformat,Objectarg){if(logger.isDebugEnabled()){FormattingTupleft=MessageFormatter.format(format,arg);logger.log(FQCN,Level.DEBUG,ft.getMessage(),ft.getThrowable());}}publicvoiddebug(Stringformat,Objectarg1,Objectarg2){if(logger.isDebugEnabled()){FormattingTupleft=MessageFormatter.format(format,arg1,arg2);logger.log(FQCN,Level.DEBUG,ft.getMessage(),ft.getThrowable());}}publicvoiddebug(Stringformat,Object[]argArray){if(logger.isDebugEnabled()){FormattingTupleft=MessageFormatter.ar射线格式(格式,argArray);logger.log(FQCN,Level.DEBUG,ft.getMessage(),ft.getThrowable());}}publicvoiddebug(Stringmsg,Throwablet){logger.log(FQCN,Level.DEBUG,msg,t);}//...省略一堆接口的实现...}桥接模式但是还有一种情况,比如引用第三方框架,使用JCL,最后使用JUL打印日志,但是你的系统使用Slf4j,最后使用Log4j打印,那么会有两种输出,两种日志的输出环境不统一,但是读取日志很麻烦,然后Slf4j的开发者为了为了让大家统一使用Slf4j,定义了相应的桥接包(jul-to-slf4j/log4j-over-slf4j/jcl-over-slf4j),将抽象和实现解耦,可以独立更改。定义中的“抽象”不是指“抽象类”或“接口”,而是一组抽象出来的“类库”,里面只包含骨架代码,真正的业务逻辑需要委托给“实现””定义中的“待完成”。定义中的“实现”不是一个“接口实现类”,而是一组独立的“类库”。“抽象”和“实现”是独立开发,通过对象之间的组合关系。最佳实践日志级别variableparameters]一般用于细粒度,对调试应用很有帮助,主要用于开发过程中打印一些运行信息INFO[应用运行过程,避免过多]INFO消息突出显示运行过程粗粒度级别的应用程序。打印一些您感兴趣或重要的信息。这可以用来在生产环境中输出程序运行的一些重要信息,但不能滥用,以免打印过多的日志。WARN[Potentialerrorsorpotentialsintheexpectation]WARN表示可能会出现错误。有些信息不是错误信息,但应该给程序员一些提示。这个级别表示程序会自动调整到正常状态。没有传入类似的参数,使用默认参数,还是符合程序员预期的情况。ERROR【错误,仍在运行】ERROR表示虽然发生错误事件,但仍不影响系统的继续运行。打印错误和异常信息,如果不想输出过多的log,可以使用这个级别。一般在WARN之后的级别打印错误时,应该同时打印错误码。FATEL【错误,无法运行】FATAL指出每一个严重的错误事件都会导致应用程序退出。这个水平比较高。如果出现重大错误,则程序无法恢复,必须通过重启程序解决。在Log4j中,日志级别之间的关系如下:ALL
