警告!前同事被移出公司技术群,原因竟然是……
老实交代,是不是要要多一点套路你才会点开看。《病变》哼起来:是不是要我多一些套路,才会让你点开看?好了,今天皮一下,大家周末快乐鸭🦆
今天我们讲的主题是门面日志,虽然有套路,既然点看来就看看吧!
起因
以前对日志的认知,只停留在能用就行,能够满足需求完美输出就行,但最近发现自己对日志的认知还是太过浅显,所以今天我要分享的就是如何更合理地使用日志,也是今天这篇博客的缘由。
最近项目在做组内培训,我想借着机会给大家推一下有关代码规范方面,主体内容是以阿里巴巴地《java开发手册(嵩山版)》进行的,在其中有关于日志的描述有这样的强制要求:
【强制】应用中不可直接使用日志系统(
Log4j
、Logback
) 中的 API ,而应依赖使用日志框架
(SLF4J、JCL--Jakarta Commons Logging) 中的 API ,使用门面模式的日志框架,有利于维护和
各个类的日志处理方式统一
说明:日志框架(SLF4J、JCL--Jakarta Commons Logging)的使用方式(推荐使用 SLF4J)
//使用 SLF4J:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.class);
//使用 JCL:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
private static final Log log = LogFactory.getLog(Test.class);
然后,我根据自己的以往的经验,查了一些资料,就明白了为什么有这样的强制规范要求,但这里我先卖个关子,最后再来说原因,接下来我们做一个demo实验。
实验走起
创建项目
这里为了更好地演示效果,创建的是普通地maven项目,项目结构如下:
log4J
我们先用log4j展开,具体步骤如下:
引入依赖
我们先引入log4j的依赖,版本无所谓:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
日志配置及示例
log4j的日志等级分为TRACE
,DEBUG
,INFO
,WARN
,ERROR
和FATAL
,其中debug、info、warn、error我们经常用到,至于trace、fatal实话实说,我也没用过,很少接触。我个人理解是,如果你有比debug更详细的日志打印需求,那就采用trace,也就是要追踪更详细的问题,用trace,同样你有比error更粗略的日志记录需求,那可以采用fatal。
经过我的实验,log4j.properties文件只需要如下简单配置,就可以保证日志输出:
log4j.rootLogger=trace, out
log4j.appender.out = org.apache.log4j.ConsoleAppender
log4j.appender.out.layout = org.apache.log4j.PatternLayout
示例
但是上面这些配置只能保证日志输出,而且显示效果不友好,看看我的示例:
java测试代码
public void log4jTest() {
log4j.trace("log4j trace……");
log4j.debug("log4j debug……");
log4j.info("log4j info");
log4j.warn("log4j warn……");
log4j.error("log4j error……");
log4j.fatal("log4j fatal……");
}
输出示例
log4j trace……
log4j debug……
log4j info
log4j warn……
log4j error……
log4j fatal……
是不是极其不友好,现在我们加一个格式化配置:
log4j.appender.out.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
#log4j.appender.out.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %5p (%F:%L) - %m%n
# 底下的配置当日志等级为trace的时候有问题,乱码
再运行,日志显示变得更友好了:
0 [main] TRACE io.github.syske.logger.LoggerTest - log4j trace……
2 [main] DEBUG io.github.syske.logger.LoggerTest - log4j debug……
2 [main] INFO io.github.syske.logger.LoggerTest - log4j info
2 [main] WARN io.github.syske.logger.LoggerTest - log4j warn……
2 [main] ERROR io.github.syske.logger.LoggerTest - log4j error……
2 [main] FATAL io.github.syske.logger.LoggerTest - log4j fatal……
因为今天重点不是将如何使用日志,所以log4j就讲到这里,我们继续看其他日志工具。
logback
引入依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
日志配置及示例
java代码
public void logbackTest() {
logback.trace("logback trace……");
logback.debug("logback debug……");
logback.info("logback info");
logback.warn("logback warn……");
logback.error("logback error……");
}
刚开始我的logback只这样初始化的:
ch.qos.logback.classic.Logger logback = new LoggerContext().getLogger(this.getClass());
刚开始想着通过logback单独输出日志,但发发现不行,压根走不通,不报错,但是日志无法输出,后来化成这样,日志就可以输出了:
// org.slf4j.Logger logback2 = new LoggerContext().getLogger(this.getClass());
// 上面这种写法也不行,不能输出,所以我就想不通为啥不让用logback直接输出日志(他不是强制使用日志门面吗,还是说就是我实验的这样,直接使用无法输出日志)
org.slf4j.Logger logback= LoggerFactory.getLogger(this.getClass());
配置
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
<configuration>
反正我就想不通为啥不让用logback直接输出日志(他不是强制使用日志门面吗,还是说就是我实验的这样,直接使用无法输出日志。有知道具体原因的小伙伴欢迎留言讨论。
门面日志
为什么推荐使用门面日志
大家考虑这样的场景:如果你们的系统现阶段采用的是log4j进行日志管理,但后续因为业务变更,通过log4j无法满足新的业务需求,这时候要更换日志系统,你要怎么做?修改每个类吗?
就是基于这样的问题考量,门面日志才发挥了它的强大好处,能更好的满足我们的需求,保证我们程序再日志这块实现解耦。
那么什么是门面日志呢?
门面日志是什么
百度百科给的解释是:
简单日志门面,对应的英文为Simple Logging Facade,是存取日志的标准接口,包括slf4j、log4j、jdk logging api和apache common-log等具体实现。
我个人的理解是,门面日志就类似于一个日志接口,她不提供具体实现,但是会提供丰富的日志接口,让你可以自由选择自己的日志系统,甚至可以自己去实现相关接口。
目前常用的门面日志有slf4j和commons-logging,像log4j和logback就是完美地实现了这些门面日志的方法。
如何使用门面日志
门面日志的使用很简单,主要分为两步:
引入门面依赖
比如slf4j,只需要引入它的依赖包:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
<dependency>
如果是commons-logging,一样引入它的依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
初始化日志对象
既然采用了门面日志,那么我们在初始化日志对象的时候就一定要采用门面日志,这样也方便日志管理,也就是我们前面我们提到的规范:
//使用 SLF4J:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.class);
//使用 JCL:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
private static final Log log = LogFactory.getLog(Test.class);
引入具体的日志系统
完成上面的工作,你的项目日志就ok了,但是日志是不会输出的,这时候还需要你引入具体的日志实现,比如log4j
,或者logback
,如果是maven
项目的话,logback
会自动引入slf4j-api
的依赖:
logback
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
或者log4j
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
配置日志
这里很重要,你如果引入的是log4j
作为日志实现的话,你就在配置文件中加入log4j
的配置log4j.properties
log4j.rootLogger=trace, out
log4j.appender.out = org.apache.log4j.ConsoleAppender
log4j.appender.out.layout = org.apache.log4j.PatternLayout
如果你引入的是logback
,那你就在配置文件中加入logback
的配置logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
<configuration>
如何切换日志系统
采用门面日志以后,日志的切换就变得很容易了,只需要替换具体的实现依赖,然后再变更相应的日志配置即可,至于代码,压根就不需要动。这也是我们为什么要采用门面日志的原因——降低日志系统的耦合性。
推荐使用slf4j
为什么推荐slf4j,其实是个小原因,就是她支持占位符输出,而commons-logging不支持。比如说我们需要输出这样的错误信息:
115 [main] ERROR io.github.syske.logger.LoggerTest - 错误信息:系统查询异常,错误原因:网络异常,错误类型:致命错误
用slf4j
logback.error("错误信息:{},错误原因:{},错误类型:{}","系统查询异常","网络异常","致命错误");
用commons-logging
log.error("错误信息:" + "系统查询异常" +",错误原因:" + "网络异常" + ",错误类型:" + "致命错误");
你觉得哪个更友好呢?让我来选,毫不犹豫会选slf4j.
总结
今天其实没有讲太多的东西,核心说了一个问题,就是日常开发推荐使用门面日志。至于为什么推荐使用门面日志,就是为了更好地实现日志解耦。
最后,用我给我们项目组宣讲的时候的一段话结束今天的内容:
雪崩时,没有一片雪花是无辜的。同样的,系统宕机时,没有一行代码是无辜的,稳健的系统,优美的代码,离不开每一个人的努力,为了让整个项目更好,为了让每一个人都能成长受益,让我们一起努力……
当然这里要小改一下,我希望我们每个IT人写出的代码像诗一样优美,因为我一直觉得写程序本身也是创作,和写小说、写诗没有区别,唯一的区别是我们采用的语言不一样,而且我们创作的作品反而更有现实意义😂