简单的说,日志就是记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。
本篇文章分为三部分讲解:

  • 常用日志框架
  • SpringBoot 配置Logback
  • 阿里日志规约

常用日志框架

  • Logging
    这是 Java 自带的日志工具类,在 JDK 1.5 开始就已经有了,在 java.util.logging 包下。
  • Log4j
    Log4j 是 Apache 的一个开源日志框架,也是市场占有率最多的一个框架。大多数没用过 Java Logging, 但没人敢说没用过 Log4j 吧,反正从我接触 Java 开始就是这种情况,做 Java 项目必有 Log4j 日志框架。
  • commons-logging
    commons-logging 就是日志的门面接口,它也是 apache 最早提供的日志门面接口,用户可以根据喜好选择不同的日志实现框架,而不必改动日志定义,这就是日志门面的好处,符合面对接口抽象编程。
  • Slf4j
    全称:Simple Logging Facade for Java,即简单日志门面接口,和 Apache 的 commons-logging 是一样的概念,它们都不是具体的日志框架,你可以指定其他主流的日志实现框架。
  • Logback
    Logback 是 Slf4j 的原生实现框架,同样也是出自 Log4j 一个人之手,但拥有比 log4j 更多的优点、特性和更做强的性能,现在基本都用来代替 log4j 成为主流。

为什么 Logback 会成为主流?
无论从设计上还是实现上,Logback相对log4j而言有了相对多的改进。
更快的执行速度
基于我们先前在log4j上的工作,logback 重写了内部的实现,在某些特定的场景上面,甚至可以比之前的速度快上10倍。在保证logback的组件更加快速的同时,同时所需的内存更加少。

日志框架总结

  1. commons-loggin、slf4j 只是一种日志抽象门面,不是具体的日志框架。
  2. log4j、logback 是具体的日志实现框架。
  3. 一般首选强烈推荐使用 slf4j + logback。当然也可以使用slf4j + log4j、commons-logging + log4j 这两种日志组合框架。

SpringBoot logback

Spring Boot会用Logback来记录日志,并用INFO级别输出到控制台。在运行应用程序和其他例子时,你应该已经看到很多INFO级别的日志了。

日志依赖

依赖 spring-boot-starter-web 默认包含spring-boot-starter-logging
那么,我们的Spring Boot应用将自动使用logback作为应用日志框架,Spring Boot启动的时候,由org.springframework.boot.logging.Logging-Application-Listener根据情况初始化并使用。

默认配置属性支持

Spring Boot为我们提供了很多默认的日志配置,所以,只要将spring-boot-starter-logging作为依赖加入到当前应用的classpath,则“开箱即用”。 下面介绍几种在application.properties就可以配置的日志相关属性。

控制台输出

日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出。 Spring Boot中默认配置ERROR、WARN和INFO级别的日志输出到控制台。您还可以通过启动您的应用程序–debug标志来启用“调试”模式(开发的时候推荐开启),以下两种方式皆可:

  • 在运行命令后加入–debug标志,如:$ java -jar springTest.jar --debug。
  • 在application.properties中配置debug=true,该属性置为true的时候,核心Logger(包含嵌入式容器、hibernate、spring)会输出更多内容,但是你自己应用的日志并不会输出为DEBUG级别。

文件输出

默认情况下,Spring Boot将日志输出到控制台,不会写到日志文件。如果要编写除控制台输出之外的日志文件,则需在application.properties中设置logging.file或logging.path属性。

  • logging.file,设置文件,可以是绝对路径,也可以是相对路径。如:logging.file=my.log。
  • logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=/var/log。

如果只配置 logging.file,会在项目的当前路径下生成一个 xxx.log 日志文件。
如果只配置 logging.path,在 /var/log文件夹生成一个日志文件为 spring.log。

级别控制

所有支持的日志记录系统都可以在Spring环境中设置记录级别(例如在application.properties中) 格式为:’logging.level.* = LEVEL’

  • logging.level:日志级别控制前缀,*为包名或Logger名
  • LEVEL:选项TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF

举例:

  • logging.level.com.mrbird=DEBUG:com.mrbird包下所有class以DEBUG级别输出。
  • logging.level.root=WARN:root日志以WARN级别输出。

自定义日志配置

由于日志服务一般都在ApplicationContext创建前就初始化了,它并不是必须通过Spring的配置文件控制。因此通过系统属性和传统的Spring Boot外部配置文件依然可以很好的支持日志控制和管理。

根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:

  • Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
  • Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
  • Log4j2:log4j2-spring.xml, log4j2.xml
  • JDK (Java Util Logging):logging.properties

Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项(下面会提到)。

上面是默认的命名规则,并且放在src/main/resources下面即可。

如果你即想完全掌控日志配置,但又不想用logback.xml作为Logback配置的名字,可以在application.properties配置文件里面通过logging.config属性指定自定义的名字:

logging.config=classpath:logging-config.xml

虽然一般并不需要改变配置文件的名字,但是如果你想针对不同运行时Profile使用不同的日 志配置,这个功能会很有用。

下面我们来看看一个普通的logback-spring.xml例子:

<?xml version="1.0" encoding="UTF-8"?>

<!--    scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。

    scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。

    debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。-->
<configuration  scan="true" scanPeriod="60 seconds" debug="false">

<!--    每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用设置成其他名字,用于区分不同应用程序的记录。
    一旦设置,不能修改,可以通过%contextName来打印日志上下文名称。-->
    <contextName>logback</contextName>

<!--    设置变量<property> 用来定义变量值的标签,有两个属性,name和value;其中name的值是变量的名称,value的值时变量定义的值。
    通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。-->
    <property name="log.path" value="log" />

<!--  appender用来格式化日志输出节点,有俩个属性name和class,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。-->

<!--    <encoder>表示对日志进行编码:

    %d{HH: mm:ss.SSS}——日志输出时间。

    %thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用。

    %-5level——日志级别,并且使用5个字符靠左对齐。

    %logger{36}——日志输出者的名字。

    %msg——日志消息。

    %n——平台的换行符。

    ThresholdFilter为系统定义的拦截器,例如我们用ThresholdFilter来过滤掉ERROR级别以下的日志不输出到文件中。如果不用记得注释掉,不然你控制台会发现没日志~-->
    <!--输出到控制台-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
             <level>ERROR</level>
         </filter>-->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

<!--    <fileNamePattern>${log.path}/logback.%d{yyyy-MM-dd}.log</fileNamePattern>定义了日志的切分方式——把每一天的日志归档到一个文件中;

    <maxHistory>30</maxHistory>表示只保留最近30天的日志,以防止日志填满整个磁盘空间。同理,可以使用%d{yyyy-MM-dd_HH-mm}来定义精确到分的日志切分方式;

    <totalSizeCap>1GB</totalSizeCap>用来指定日志文件的上限大小,例如设置为1GB的话,那么到了这个值,就会删除旧的日志。-->
    <!--输出到文件-->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/logback.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

<!--    root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性,
    用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。-->
    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>

<!--    <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。-->
    <!-- logback为java中的包 -->
    <logger name="top.lconcise.controller.HelloController"/>

<!--    name:用来指定受此logger约束的某一个包或者具体的某一个类。

    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,
    代表强制执行上级的级别。如果未设置此属性,那么当前logger将会继承上级的级别。

    addtivity:是否向上级logger传递打印信息。默认是true。-->
    <!--logback.LogbackDemo:类的全路径 -->
    <logger name="top.lconcise.controller.HelloController2" level="WARN" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>

阿里日志规约

1.【强制】应用中不可直接使用日志系统(Log4j、 Logback) 中的 API,而应依赖使用日志框架
SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(A.class);

2.【强制】日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。

3.【强制】应用中的扩展日志(如打点、临时监控、访问日志等) 命名方式:
appName_logType_logName.log。 logType:日志类型,推荐分类有
stats/desc/monitor/visit 等; logName:日志描述。这种命名的好处:通过文件名就可知
道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
正例: mppserver 应用中单独监控时区转换异常,如:
mppserver_monitor_timeZoneConvert.log
说明: 推荐对日志进行分类, 如将错误日志和业务日志分开存放,便于开发人员查看,也便于
通过日志对系统进行及时监控。

4.【强制】对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
说明:

logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);

如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
正例: (条件)

if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}

正例: (占位符)

logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);

5.【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
正例: <logger name="com.taobao.dubbo.config" additivity="false">
6.【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过
关键字 throws 往上抛出。
正例: logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);

7.【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志; 有选择地输出 info 日志; 如果使
用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘
撑爆,并记得及时删除这些观察日志。
说明: 大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。 记录日志时请
思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?

8.【参考】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适
从。注意日志输出的级别, error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必
要,请不要在此场景打出 error 级别。