引言:我们需要在已有的微服务代码中添加日志功能,用于输出需要关注的内容,这是最平常的技术需求了。由于我们的微服务代码是基于SpringBoot开发的,那么问题就转换为如何在Spring Boot应用程序中输出相应的日志。
在传统Java应用程序中,我们一般会使用类似Log4j这样的日志框架来输出日志,而不是直接在代码中通过System.out.println()来输出日志。为什么要这么做呢?原因有两点。其一,我们希望日志能输出到文件中,而不是输出到应用程序的控制台中,这样更加容易收集和分析。其二,我们可以通过异步多线程的方式,将日志输出到文件中,这样既不会影响主线程,可以提高应用程序的吞吐率,也是一种节省性能开销的方法。直接将内容打印到控制台中,这种做法比较粗暴,不是业界所推荐的做法。
这样一来,我们最终要解决的问题就非常清楚了,那就是如何在Spring Boot中添加日志框架。幸运的是,Spring Boot自带了一款名为Spring Boot Logging的插件(在Spring Boot中,称插件为Starter),它已经为我们提供了日志功能。
1 使用Spring Boot Logging插件
Spring Boot使用Apache开源项目Commons Logging作为内部的日志框架,它是一个日志接口,在实际应用中,我们需要为该接口指定相应的日志实现。Spring Boot默认的日志实现是Java Util Logging,它是JDK自带的日志包,一般场景下很少会用到。此外,Spring Boot也提供了Log4J、Logback这类流行的日志实现,我们只需要添加简单的配置,就能开启对这些日志实现的支持。
为了便于描述,我们将以上提到的“日志实现”统称为“日志框架”。
大家可以通过以下网站,进一步学习这类日志框架。
- Commons Logging官网:https://commons.apache.org/proper/commons-logging/。
- Log4J官网:https://logging.apache.org/log4j/2.x/。
- Logback官网:https://logback.qos.ch/。
在Java应用程序中,日志一般分为以下5个级别。
- ERROR:错误信息;
- WARN:警告信息;
- INFO:一般信息;
- DEBUG:调试信息;
- TRACE:跟踪信息。
以上日志级别按照严重程度,从高往低排序,一般常用的三种日志级别是ERROR、INFO、DEBUG。Spring Boot Logging插件默认输出到INFO级别,也就是说,只包含ERROR、WARN、INFO,不包含DEBUG、TRACE。如果我们希望日志可以输出到DEBUG级别,则需在Spring Boot的application.properties文件中添加如下配置:
logging.level.root=DEBUG
重新运行应用程序,我们就可在代码中看到DEBUG级别的日志了。
以下是Spring Boot的应用程序代码片段,我们使用SLF4J类库输出日志,而不要使用具体的日志实现类库,比如Log4J。
package demo.msa;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
@RestController
@SpringBootApplication
public class HelloApplication {
private static Logger logger =LoggerFactory.getLogger(HelloApplication.class);
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
@GetMapping("/hello")
public String hello() {
logger.debug("log..."); // 输出DEBUG级别的日志
return "hello";
}
}
运行以上Spring Boot应用程序,会发现控制台中输出了大量INFO级别的日志,这些日志是由Spring Boot框架输出的。因为我们调整日志输出到DEBUG级别,而INFO级别在DRBUG级别之上,所以INFO级别的日志也会输出,但TRACE级别的日志不会输出。
当我们打开浏览器,发送http://localhost:8080/hello请求时,可在控制台中看到我们想要输出的DEBUG级别日志。
如果我们不想关注Spring Boot框架的日志,则可将日志级别统一设置为ERROR,此时只会输出ERROR级别的日志。随后,再将Spring Boot应用程序指定的包(应用程序所对应的包)设置为DEBUG级别的日志,此时我们看到的就只是指定包中的日志了。
logging.level.root=ERROR
logging.level.demo.msa=DEBUG
上面的logging.level.root表示所有包,logging.level.demo.msa表示应用程序的指定包(demo.msa是包名)。以上配置可以理解为,整个应用程序的日志输出到ERROR级别,除了demo.msa包中的日志输出到DEBUG级别。这是一种“先禁止所有,再允许个别”的配置方法,这种配置方法在很多技术中都应用过。
默认情况下日志框架会将日志输出到控制台中,我们需要在application.properties文件中添加如下配置,才能将日志输出到文件中:
logging.file=${user.home}/logs/hello.log
其中,${user.home}表示当前用户目录(该变量由Spring Boot框架在运行时传入),后面的/logs/hello.log是相对于该目录的路径。大家可根据实际情况,设置所需的日志文件路径,以上仅为示例。
重新运行应用程序,就能看到日志输出到指定路径下的文件中了。
目前我们虽然可以将日志输出到文件中,但控制台中仍然会输出同样的日志,这不是我们最终想要的效果。我们希望的是日志全部输出到文件中,控制台中不输出任何日志。也就是说,我们需要关闭控制台中的输出。通过以上的尝试,我们不难发现,仅通过修改Spring Boot的配置,貌似是无法做到的。
下面我们不妨考虑集成经典的Log4J日志框架,看看能否实现我们的需求。
2 集成Log4J日志框架
Spring Boot Logging默认集成了Logback,我们只需提供Logback的配置文件就能开启Logback日志功能,但我们现在想要尝试的是自己熟知的Log4J,而不是比较新潮的Logback。毫不犹豫,现在我们就来开启对Log4J的支持。通过学习Spring Boot的官方文档与示例代码,我们了解到,只需在pom.xml文件中添加如下Maven配置,就能在Spring Boot中集成Log4J。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
在第一段dependency配置中,我们排除掉spring-boot-starter-logging依赖是因为要去掉默认集成的Logback日志功能。在第二段dependency配置中,我们自行添加了spring-boot-starter- log4j2依赖,它是Spring Boot所提供的Log4J插件,此时使用的是Log4J的2.x版本。
当完成了Maven依赖配置以后,我们接下来需要在源码中的resources目录下添加log4j2.xml文件,其内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appenders>
<File name="file" fileName="${sys:user.home}/logs/hello.log">
<PatternLayout pattern="%d{HH:mm:ss,SSS} %p %c (%L) - %m%n"/>
</File>
</appenders>
<loggers>
<root level="ERROR">
<appender-ref ref="file"/>
</root>
<logger name="demo.msa" level="DEBUG"/>
</loggers>
</configuration>
log4j2.xml配置文件分为两大部分,即appenders与loggers。在appenders中,我们添加了一个File类型的appenders,表示日志以文件的方式进行输出,该文件路径基于根目录${sys:user.home},即当前用户目录(该变量由Log4J框架在运行时传入)。此外,还需指定PatternLayout为日志输出格式。在loggers中,我们先后添加了两段配置,第一段的root表示将所有包中的日志输出到ERROR级别,第二段的logger表示将指定包demo.msa中的日志输出到DEBUG级别。很明显,这段配置与之前在Spring Boot中配置的意义相同。
通过以上配置,可将Log4J集成到Spring Boot应用中。
重新运行应用程序,日志不再输出到控制台中,而是全部输出到指定路径下的文件中了。
大家如果想了解更为详尽的Spring Boot日志特性,可参考它的官方技术文档。
- Spring Boot日志:
http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-logging.html。
目前,虽然日志已经成功输出到文件中,但是我们的微服务是以Docker容器的方式来运行的,此时输出的日志文件仍然和应用程序在一个Docker容器中,我们得想办法将日志文件输出到Docker容器外。也就是说,需要将数据与程序相分离,以便后续更加方便地获取并分析日志内容。
3 将日志输出到Docker容器外
最容易想到的办法就是,通过Docker数据卷的方式,将文件路径挂载到Docker容器上,这样日志文件就自然与Docker文件分离了,就像下面这样启动Docker容器。
docker run -v ~/logs:~/logs hello
这样一来,我们可随时在宿主机上查看Docker容器内部的日志了。但是回过头想想,却不难发现,其实完全不需要将日志输出到文件中,因为即便将日志输出到控制台中,我们也能随时通过docker logs的方式来获取日志内容,将日志输出到文件似乎有些多余了,还占用了磁盘空间。
那我们现在做的事情是否有意义呢?感兴趣的读者不妨到《架构探险:轻量级微服务架构(下册)》一书中探索详情~
本文选自《架构探险:轻量级微服务架构(下册)》,点此链接可在博文视点官网查看此书。
想及时获得更多精彩文章,可在微信中搜索“博文视点”或者扫描下方二维码并关注。