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…”); } }

  1. 运行上述代码,得到类似如下的输出:
  2. ```bash
  3. Mar 02, 2019 6:32:13 PM Hello main
  4. INFO: start process...
  5. Mar 02, 2019 6:32:13 PM Hello main
  6. WARNING: memory is running out...
  7. Mar 02, 2019 6:32:13 PM Hello main
  8. 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实例的方法打日志。
示例代码如下:

  1. import org.apache.commons.logging.Log;
  2. import org.apache.commons.logging.LogFactory;
  3. public class Main {
  4. public static void main(String[] args) {
  5. Log log = LogFactory.getLog(Main.class);
  6. log.info("start...");
  7. log.warn("end.");
  8. }
  9. }

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 创建即可。

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. class Main {
  4. final Logger logger = LoggerFactory.getLogger(Main.class);
  5. }

对比一下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 文件,该文件中记录了启动过程的信息(前文测试程序输出的信息不会被记录在日志文件中)。
image.png

无论何种设置,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

版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。