23.1 为什么要使用日志工具
日志就是Logging,使用它的目的是为了取代System.out.println()来观察程序中的变量值和逻辑步骤,更是为了记录软件中的各种运行横扫。使用日志工具,而不是用System.out.println(),有以下几个好处:
- 可以设置输出样式
- 可以设置输出级别,禁止或允许某些级别输出。例如,可以输出错误日志、禁止输出调试日志(这样不用删除那些代码)
- 可以被重定向到文件,这样可以在程序运行结束后查看日志
- 可以按包名控制日志级别,只输出某些包打的日志;
231.2 Java 标准库的日志工具
Java标准库内置了日志包 java.util.logging ,可以直接使用: ```java import java.util.logging.Level; import java.util.logging.Logger;
public class Hello { public static void main(String[] args) { Logger logger = Logger.getGlobal(); logger.info(“start process…”); logger.warning(“memory is running out…”); logger.fine(“ignored.”); logger.severe(“process will be terminated…”); } }
运行上述代码,得到类似如下的输出:
```bash
Mar 02, 2019 6:32:13 PM Hello main
INFO: start process...
Mar 02, 2019 6:32:13 PM Hello main
WARNING: memory is running out...
Mar 02, 2019 6:32:13 PM Hello main
SEVERE: process will be terminated...
对比可见,使用 Java 标准日志比 Sytem.outprintln() 最大的好处是它自动打印了时间、调用类、调用方法等很多有用的信息。再仔细观察发现,4条日志只打印了3条,logger.fine()没有打印。这是因为,日志的输出可以设定级别。JDK的Logging定义了7个日志级别,从严重到普通:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
因为默认级别是INFO,因此,INFO级别以下的日志,不会被打印出来。使用日志级别的好处在于,调整级别,就可以屏蔽掉很多调试相关的日志输出。
使用Java标准库内置的Logging有以下局限:
- Logging系统在JVM启动时读取配置文件并完成初始化,一旦开始运行main()方法,就无法修改配置;
- 配置不方便,需要在JVM启动时传递参数-Djava.util.logging.config.file=
。
因此,Java标准库内置的Logging使用并不是非常广泛。
23.3 Commons Logging 工具
和Java标准库提供的日志不同,Commons Logging是一个是由Apache创建的第三方日志工具。它不直接执行输出日志信息的工作,而是可以挂接不同的日志系统并通过配置文件指定挂接的日志系统。默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是一个流行的日志系统),如果没有找到Log4j则使用Java Logging。使用Commons Logging只需要和两个类打交道,并且只有两步:
第一步,通过LogFactory获取Log类的实例; 第二步,使用Log实例的方法打日志。
示例代码如下:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Main {
public static void main(String[] args) {
Log log = LogFactory.getLog(Main.class);
log.info("start...");
log.warn("end.");
}
}
Commons Logging定义了6个日志级别(默认级别是INFO):
- FATAL
- ERROR
- WARNING
- INFO
- DEBUG
- TRACE
23.4 使用SLF4J和Logback
23.4.1 SLF4J 概述及强制使用
SLF4J是Simple Logging Facade for Java (即简单日志门面)的缩写,和Commons Logging一样,它也是一个日志接口不是具体的日志解决方案,通常它和Logbackj这个日志系统结对工作。后者不仅类似Log4j,并没是同一个作者设计的。
在《阿里巴巴Java开发手册(正式版)》中,日志规约一项第一条就强制要求使用 SLF4J:
1.【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
“强制”两个字体现出了 SLF4J 的优势,所以建议在实际项目中,使用 SLF4J 作为自己的日志框架。
23.4.2 基本用法及特点
使用 SLF4J 记录日志不需要专门引入依赖, spring-boot-starter-web 会自动引入它。而使用的时候也非常简单,和Commons Logging类似直接使用 LoggerFactory 创建即可。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class Main {
final Logger logger = LoggerFactory.getLogger(Main.class);
}
对比一下Commons Logging和SLF4J的接口:
Commons Logging | SLF4J |
---|---|
org.apache.commons.logging.Log | org.slf4j.Logger |
org.apache.commons.logging.LogFactory | org.slf4j.LoggerFactory |
不同之处就是Log变成了Logger,LogFactory变成了LoggerFactory。
我们先来看看SLF4J对Commons Logging的接口有何改进。在Commons Logging中,我们要打印日志,有时候得这么写:
int score = 99;
p.setScore(score);
log.info("Set score " + score + " for Person " + p.getName() + " ok.");
拼字符串是一个非常麻烦的事情,所以SLF4J的日志接口改进成这样了:
int score = 99;
p.setScore(score);
logger.info("Set score {} for Person {} ok.", score, p.getName());
这里 SLF4J 的日志接口传入的是一个带占位符的字符串(相当于模板),用后面的变量自动替换占位符,所以看起来更加自然。
从目前的趋势来看,越来越多的开源项目从Commons Logging加Log4j转向了SLF4J加Logback。
23.4.3 关于 @Slf4j 注解
既然己写日志的时候一定要需要写一行固定格式的代码,那有没有简单的办法呢?答案是有的。可以在类上使用 Lombok 提供的 @Slf4j 注解,它的作用就是帮你省略了这句必须的定义。
package com.longser.union.cloud.extended;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class LoggerTest {
@Test
public void test(){
log.debug("debug");
log.info("info");
log.error("error");
log.warn("warn");
}
}
下面是运行的结果
15:42:02.751 [main] DEBUG com.longser.union.cloud.extended.LoggerTest - debug
15:42:02.754 [main] INFO com.longser.union.cloud.extended.LoggerTest - info
15:42:02.754 [main] ERROR com.longser.union.cloud.extended.LoggerTest - error
15:42:02.754 [main] WARN com.longser.union.cloud.extended.LoggerTest - warn
如果你是在一个已经使用了 lombok 的项目中的编写该项目的专有代码,那么你可以放心地去使用这个 @Slf4j 注解。但如果你写的是一个具有一定通用性的类库或者工具类代码,那么最好不要使用这个注解,否则你会给其他的使用者附加了使用 lombok 的强制要求。
23.4.4. 静态和动态日志对象
如果在静态方法中引用Log,通常直接定义一个静态类型变量:
// 在静态方法中引用Log:
public class Main {
static final Logger logger = LoggerFactory.getLogger(Main.class);
static void foo() {
logger.info("foo");
}
}
如果是在实例方法中引用Log,通常定义一个实例变量:
// 在实例方法中引用Log:
public class Person {
protected final Logger logger = LoggerFactory.getLogger(getClass());
void foo() {
logger.info("foo");
}
}
注意到实例变量 logger 的获取方式是LoggerFactory.getLogger(getClass()),虽然也可以用LoggerFactory.getLogger(Person.class),但是前一种方式有个非常大的好处,就是子类可以直接使用该logger实例。例如:
// 在子类中使用父类实例化的log:
public class Student extends Person {
void bar() {
logger.info("bar");
}
}
由于Java类的动态特性,子类无需改动代码而从父类继承即相当于 LoggerFactory.getLogger(Student.class)。
23.5 SLF4J 的配置方法
SLF4J 的提供了丰富的配置选项,本节讨论一些常见的配置。
23.5.1 设置日志级别
日志级别从小到大为 trace < debug < info < warn < error < fatal,默认日志级别为 INFO。
我们可以在 applicaition.yml 文件中修改日志级别。比如下面将全局日志级别都改成 trace,因此系统所有的日志都能看到:
logging:
level:
root: trace
我们也可以只设置某个包的日志级别,这样能够更方便准确地定位问题。比如下面配置,只对所有 com.longser.union.cloud.data包下面产生的日志级别改成 trace:
logging:
config: logback.xml
level:
com.longser.union.cloud.data: trace
23.5.2 输出到日志文件
默认情况下,Spring Boot 仅将日志记录到控制台,不写入日志文件。如果要在控制台输出之外写入日志文件,则需要设置 logging.file.name 或者 logging.file.path。
下表显示了 logging.* 属性可以一起使用的规则
logging.file.name | logging.file.path | 示例 | 描述 |
---|---|---|---|
(none) | (none) | 仅控制台日志记录。 | |
Specific file | (none) | my.log | 写入指定的日志文件。名称可以是确切位置或相对于当前目录。 |
(none) | Specific directory | /var/log | 写 spring.log 到指定目录。名称可以是确切位置或相对于当前目录。 |
如我们可以可以在 application.yml 中做如下的配置
logging:
file:
name: logs/cloud-app.log
重新启动应用,会在当前项目目录创建 logs 目录和 cloud-app.log 文件,该文件中记录了启动过程的信息(前文测试程序输出的信息不会被记录在日志文件中)。
无论何种设置,Spring Boot 都会自动按天分割日志文件,也就是说每天都会自动生成一个新的 log 文件,而之前的会自动打成 GZ 压缩包。我们还可以设置日志文件的保留时间,以及单个文件的大小:
logging:
file:
max-size: 10MB # 日志文件大小
max-history: 10 # 保留的日志时间
下面是相关的属性
Name | Description |
---|---|
logging.logback.rollingpolicy.file-name-pattern | 用于创建日志存档的文件名模式。 |
logging.logback.rollingpolicy.clean-history-on-start | 是否在应用程序启动时进行日志存档清理。 |
logging.logback.rollingpolicy.max-file-size | 存档前日志文件的最大大小(默认为10MB)。 |
logging.logback.rollingpolicy.total-size-cap | 日志存档在删除之前可以占用的最大大小。 |
logging.logback.rollingpolicy.max-history | 保留日志存档的天数 (默认为7) |
日志记录系统在应用程序生命周期的早期被初始化。因此,在通过 @PropertySource 注解加载的属性文件中找不到日志记录属性。
日志记录属性独立于实际的日志记录基础结构。因此,特定的配置 (例如 Logback 的logback.ConfigurationFile ) 不受 Spring Boot管理。
23.5.3 配置输出格式
我们可以分别修改在控制台输出的日志格式,以及文件中日志输出的格式。下面是用来定义格式的符号的说明:
- %d{HH:mm:ss.SSS} 日志输出时间
- %-5level 日志级别,并且使用 5 个字符靠左对齐
- %thread 输出日志的进程名字,这在 Web 应用以及异步任务处理中很有用
- %logger 日志输出者的名字
- %msg 日志消息
- %n 平台的换行符
下面是两个范例
logging:
pattern:
# 修改在控制台输出的日志格式
console: %d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger : %msg%n
# 修改输出到文件的日志格式
file: d%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger : %msg%n
使用默认格式进行日志输出时,控制台不同参数会输出为不同的颜色。而一旦我们修改了日志格式,彩色也随之消失。如果想让不同类型的数据具有不同的高亮效果,可以改用如下配置:
logging:
pattern:
# 修改在控制台输出的日志格式
console: %d{yyyy-MM-dd HH:mm:ss} %clr(%5p) [%thread] %clr(%logger){cyan} : %msg%n
# 修改输出到文件的日志格式
file: %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger : %msg%n
如果觉得类名太长(%logger),我们还可以将其设置个长度限制。比如下面限制长度为 45 个字符,超过得话包名部分会变成缩写:
logging:
pattern:
# 修改在控制台输出的日志格式
console: %d{yyyy-MM-dd HH:mm:ss} %clr(%5p) [%thread] %clr(%logger{45}){cyan} : %msg%n
# 修改输出到文件的日志格式
file: %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger : %msg%n
23.5.4 指定独立的配置文件
通过如下的配置可以指定一个独立的 SLF4J 配置文件
logging:
config: logback.xml
下面是典型的 SLF4J 配置文件结构及其说明
<configuration>
<!-- %m输出的信息,%p日志级别,%t线程名,%d日期,%c类的全名,%i索引【从数字0开始递增】,,, -->
<!-- appender是configuration的子节点,是负责写日志的组件。 -->
<!-- ConsoleAppender:把日志输出到控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %p (%file:%line\)- %m%n</pattern>
<!-- 控制台也要使用UTF-8,不要使用GBK,否则会中文乱码 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是sys.log -->
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过1KB时,对当前日志进行分割 重命名-->
<appender name="syslog"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>log/sys.log</File>
<!-- rollingPolicy:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 -->
<!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
<!-- 文件名:log/sys.2017-12-05.0.log -->
<fileNamePattern>log/sys.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天 -->
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
<maxFileSize>1KB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<!-- pattern节点,用来设置日志的输入格式 -->
<pattern>
%d %p (%file:%line\)- %m%n
</pattern>
<!-- 记录日志的编码 -->
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
</appender>
<!-- 控制台输出日志级别 -->
<root level="info">
<appender-ref ref="STDOUT" />
</root>
<!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
<!-- com.appley为根包,也就是只要是发生在这个根包下面的所有日志操作行为的权限都是DEBUG -->
<!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
<logger name="com.appleyk" level="DEBUG">
<appender-ref ref="syslog" />
</logger>
</configuration>
23.6 参考文档
下面是官方文档的链接
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.logging
版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。