Java打印日志注意事项和异常类型

目录

1. 日志类型 

2. 日志

2.1 什么是日志

2.2 常用日志框架

2.3日志级别

2.4 打日志的规范准则

2.5 几个错误的打日志方式

3. springboot整合logback进行日志管理

3.1 根节点configuration

3.2 子节点1:contextName

3.3 子节点2:springProperty

 3.4 子节点3:appender

3.5 子节点4:root

 3.6 子节点5:springProfile


1. 日志类型 

注意纠正上图:RuntimeException用e.getMessage();获取不到异常信息!SQlException和IOException可以获取到异常信息。

2. 日志

2.1 什么是日志

简单的说,日志就是记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。

我们 Java 程序员在开发项目时都是依赖 Eclipse/ Idea 等开发工具的 Debug 调试功能来跟踪解决 Bug,在开发环境可以这么做,但项目发布到了测试、生产环境呢?你有可能会说可以使用远程调试,但实际并不能允许让你这么做。

所以,日志的作用就是在测试、生产环境没有 Debug 调试工具时开发、测试人员定位问题的手段。日志打得好,就能根据日志的轨迹快速定位并解决线上问题,反之,日志输出不好不能定位到问题不说反而会影响系统的性能。

优秀的项目都是能根据日志定位问题的,而不是在线调试,根据日志准确定位到异常代码。

2.2 常用日志框架

log4j、Logging、commons-logging、slf4j、logback,开发的同学对这几个日志相关的技术不陌生吧,为什么有这么多日志技术,它们都是什么区别和联系呢?

Logging

这是 Java 自带的日志工具类,在 JDK 1.5 开始就已经有了,在 java.util.logging包下。

可以查看,Java Logging官方文档地址

Log4j

Log4j 是 Apache 的一个开源日志框架,也是市场占有率最多的一个框架。大多数没用过 Java Logging, 但没人敢说没用过 Log4j 吧,反正从我接触 Java 开始就是这种情况,做 Java 项目必有 Log4j 日志框架。

注意:log4j 在 2015/08/05 这一天被 Apache 宣布停止维护了,用户需要切换到 Log4j2上面去。

On August 5, 2015 the Logging Services Project Management Committee announced that Log4j 1.x had reached end of life. For complete text of the announcement please see the Apache Blog. Users of Log4j 1 are recommended to upgrade to Apache Log4j 2.

Log4j官方地址

commons-logging

上面介绍的 log4j 是一个具体的日志框架的实现,而 commons-logging 就是日志的门面接口,它也是 apache 最早提供的日志门面接口,用户可以根据喜好选择不同的日志实现框架,而不必改动日志定义,这就是日志门面的好处,符合面对接口抽象编程。

可以参考官文档

什么是日志门面?

日志门面,是门面模式的一个典型的应用。

门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。

就像前面介绍的几种日志框架一样,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性。

为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。

在软件开发领域有这样一句话:计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。而门面模式就是对于这句话的典型实践。

为什么需要日志门面?

前面提到过一个重要的原因,就是为了在应用中屏蔽掉底层日志框架的具体实现。这样的话,即使有一天要更换代码的日志框架,只需要修改jar包,最多再改改日志输出相关的配置文件就可以了。这就是解除了应用和日志框架之间的耦合。

有人或许会问了,如果我换了日志框架了,应用是不需要改了,那日志门面不还是需要改的吗?

要回答这个问题,我们先来举一个例子,再把门面模式揉碎了重新解释一遍。

日志门面就像饭店的服务员,而日志框架就像是后厨的厨师。对于顾客这个应用来说,我到饭店点菜,我只需要告诉服务员我要一盘番茄炒蛋即可,我不关心后厨的所有事情。因为虽然主厨从把这道菜称之为『番茄炒蛋』A厨师换成了把这道菜称之为『西红柿炒鸡蛋』的B厨师。但是,顾客不需要关心,他只要下达『番茄炒蛋』的命令给到服务员,由服务员再去翻译给厨师就可以了。

所以,对于一个了解了”番茄炒蛋的多种叫法”的服务员来说,无论后厨如何换厨师,他都能准确的帮用户下单。

同理,对于一个设计的全面、完善的日志门面来说,他也应该是天然就兼容了多种日志框架的。所以,底层框架的更换,日志门面几乎不需要改动。

以上,就是日志门面的一个比较重要的好处——解耦

Java简易日志门面(Simple Logging Facade for Java,缩写SLF4J),是一套包装Logging 框架的界面程式,以外观模式实现。可以在软件部署的时候决定要使用的 Logging 框架,目前主要支援的有Java Logging API、Log4j及logback等框架。以MIT 授权方式发布。

SLF4J 的作者就是 Log4j和Logback 的作者 Ceki Gülcü,他宣称 SLF4J 比 Log4j 更有效率,而且比 Apache Commons Logging (JCL) 简单、稳定。

其实,SLF4J其实只是一个门面服务而已,他并不是真正的日志框架,真正的日志的输出相关的实现还是要依赖Log4j、logback等日志框架的。

pache Commons Logging是一个基于Java的日志记录实用程序,是用于日志记录和其他工具包的编程模型。它通过其他一些工具提供API,日志实现和包装器实现。

commons-logging和SLF4J的功能是类似的,主要是用来做日志 门面的。提供更加好友的API工具。

Slf4j

全称:Simple Logging Facade for Java,即简单日志门面接口,和 Apache 的 commons-logging 是一样的概念,它们都不是具体的日志框架,你可以指定其他主流的日志实现框架。Slf4j 也是现在主流的日志门面框架,使用 Slf4j 可以很灵活的使用占位符进行参数占位,简化代码,拥有更好的可读性,这个后面会讲到。

Slf4j官方地址参考

Logback

Logback 是 Slf4j 的原生实现框架,同样也是出自 Log4j 一个人之手,但拥有比 log4j 更多的优点、特性和更做强的性能,现在基本都用来代替 log4j 成为主流。

Logback官方地址

为什么 Logback 会成为主流?

无论从设计上还是实现上,Logback相对log4j而言有了相对多的改进。不过尽管难以一一细数,这里还是列举部分理由为什么选择logback而不是log4j。牢记logback与log4j在概念上面是很相似的,它们都是有同一群开发者建立。所以如果你已经对log4j很熟悉,你也可以很快上手logback。如果你喜欢使用log4j,你也许会迷上使用logback。

更快的执行速度

基于我们先前在log4j上的工作,logback 重写了内部的实现,在某些特定的场景上面,甚至可以比之前的速度快上10倍。在保证logback的组件更加快速的同时,同时所需的内存更加少。

更多请参考《从Log4j迁移到LogBack的理由 》

日志框架总结

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

从上图可以看出 slf4j 很强大吧,不但能和各种日志框架对接,还能和日志门面 commons-logging 进行融合。

2.3日志级别

日志的输出都是分级别的,不同的设置不同的场合打印不同的日志。下面拿最普遍用的 Log4j 日志框架来做个日志级别的说明,这个也比较奇全,其他的日志框架也都大同小异。

Log4j 的级别类 org.apache.log4j.Level 里面定义了日志级别,日志输出优先级由高到底分别为以下8种。

Log4j日志级别
日志级别描述
1OFF关闭:最高级别,不输出日志。
2FATAL致命:输出非常严重的可能会导致应用程序终止的错误。
3ERROR错误:输出错误,但应用还能继续运行。
4WARN警告:输出可能潜在的危险状况。
5INFO信息:输出应用运行过程的详细信息。
6DEBUG调试:输出更细致的对调试应用有用的信息。
7TRACE跟踪:输出更细致的程序运行轨迹。
8ALL所有:输出所有级别信息。

ALL<TRACE<DEBUG<INFO<WARN<ERROR<FATAL<OFF

级别排序,从左到右,级别越来越高

只有当日志级别大于你设置的日志级别才会输出。你设置的日志级别是ALL那么比ALL日志级别高的都会输出;你设置日志的级别是INFO那么比INFO级别高的日志级别会输出到日志文件或者控制台,比INFO级别低的不会输出。

2.4 打日志的规范准则

1. 正确的定义日志

private static final Logger LOG = LoggerFactory.getLogger(this.getClass());

通常一个类只有一个 LOG 对象,如果有父类可以将 LOG 定义在父类中。

日志变量类型定义为门面接口(如 slf4j 的 Logger),实现类可以是 Log4jLogback等日志实现框架,不要把实现类定义为变量类型,否则日志切换不方便,也不符合抽象编程思想。

2、使用参数化形式{}占位,[] 进行参数隔离

LOG.debug("Save order with order no:[{}], and order amount:[{}]");

这种可读性好,这样一看就知道[]里面是输出的动态参数,{}用来占位类似绑定变量,而且只有真正准备打印的时候才会处理参数,方便定位问题。

如果日志框架不支持参数化形式,且日志输出时不支持该日志级别时会导致对象冗余创建,浪费内存,此时就需要使用 isXXEnabled 判断,如:

if(LOG.isDebugEnabled()){
    // 如果日志不支持参数化形式,debug又没开启,那字符串拼接就是无用的代码拼接,影响系统性能
    logger.debug("Save order with order no:" + orderNo + ", and order amount:" + orderAmount);
}

至少 debug 级别是需要开启判断的,线上日志级别至少应该是 info 以上的。

这里推荐大家用 SLF4J 的门面接口,可以用参数化形式输出日志,debug 级别也不必用 if 判断,简化代码。

3、输出不同级别的日志

项目中最常用有日志级别是ERRORWARNINFODEBUG四种了,这四个都有怎样的应用场景呢。

  • ERROR(错误)

一般用来记录程序中发生的任何异常错误信息(Throwable),或者是记录业务逻辑出错。

  • WARN(警告)

一般用来记录一些用户输入参数错误、

  • INFO(信息)

这个也是平时用的最低的,也是默认的日志级别,用来记录程序运行中的一些有用的信息。如程序运行开始、结束、耗时、重要参数等信息,需要注意有选择性的有意义的输出,到时候自己找问题看一堆日志却找不到关键日志就没意义了。

  • DEBUG(调试)

这个级别一般记录一些运行中的中间参数信息,只允许在开发环境开启,选择性在测试环境开启。

2.5 几个错误的打日志方式

1. 不要使用 System.out.print..

输出日志的时候只能通过日志框架来输出日志,而不能使用 System.out.print.. 来打印日志,这个只会打印到 tomcat 控制台,而不会记录到日志文件中,不方便管理日志,如果通过服务形式启动把日志丢弃了那更是找不到日志了。

2. 不要使用 e.printStackTrace()

首先来看看它的源码:

public void printStackTrace() {
    printStackTrace(System.err);
}

它其实也是利用 System.err 输出到了 tomcat 控制台。

3. 不要抛出异常后又输出日志

如捕获异常后又抛出了自定义业务异常,此时无需记录错误日志,由最终捕获方进行异常处理。不能又抛出异常,又打印错误日志,不然会造成重复输出日志。

try {
    // ...
} catch (Exception e) {
    // 错误
    LOG.error("xxx", e);
    throw new RuntimeException();
}

4. 不要使用具体的日志实现类

InterfaceImpl interface = new InterfaceImpl();

这段代码大家都看得懂吧?应该面向接口的对象编程,而不是面向实现,这也是软件设计模式的原则,正确的做法应该是。

Interface interface = new InterfaceImpl();

日志框架里面也是如此,上面也说了,日志有门面接口,有具体实现的实现框架,所以大家不要面向实现编程。

5. 没有输出全部错误信息

看以下代码,这样不会记录详细的堆栈异常信息,只会记录错误基本描述信息,不利于排查问题。

try {
    // ...
} catch (Exception e) {
    // 错误
    LOG.error('XX 发生异常', e.getMessage());
 
    // 正确
    LOG.error('XX 发生异常', e);
}

6. 不要使用错误的日志级别

曾经在线上定位一个问题,同事自信地和我说:明明输出了日志啊,为什么找不到...,后来我去看了下他的代码,是这样的:

try {
    // ...
} catch (Exception e) {
    // 错误
    LOG.info("XX 发生异常...", e);
}

大家看出了什么问题吗?用 info 记录 error 日志,日志输出到了 info 日志文件中了,同事拼命地在 error 错误日志文件里面找怎么能找到呢?

7. 不要在千层循环中打印日志

这个是什么意思,如果你的框架使用了性能不高的 Log4j 框架,那就不要在上千个 for循环中打印日志,这样可能会拖垮你的应用程序,如果你的程序响应时间变慢,那要考虑是不是日志打印的过多了。

for(int i=0; i<2000; i++){
    LOG.info("XX");
}

最好的办法是在循环中记录要点,在循环外面总结打印出来。

8. 禁止在线上环境开启 debug

这是最后一点,也是最重要的一点。

一是因为项目本身 debug 日志太多,二是各种框架中也大量使用 debug 的日志,线上开启 debug 不久就会打满磁盘,影响业务系统的正常运行。

3. springboot整合logback进行日志管理

resource下面新建一个logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds" debug="false">

    <contextName>logback</contextName>

    <!--1、logback配置在application.yml这样配置会在项目当前路径下产生日志文件LOG_PATH_IS_UNDEFINED和指定路径下生成日志文件-->
    <!--<springProperty scope="context" name="logPath" source="logging.path"/>-->

    <!--2、这样配置会在项目根路径下产生日志文件LOG_PATH_IS_UNDEFINED和指定路径下产生日志文件-->
    <!--<property name="LOG_PATH" value="${LOG_PATH:- }"/>-->

    <!--<springProperty scope="context" name="logname" source="spring.application.name"/>-->
    <!--3、bootstrap.yml配置logback所需要的参数,只会在指定路径下生成日志文件-->
    <springProperty scope="context" name="logPath"  source="logging.path"/>
    

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target>
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <!-- 设置字符集 FATAL_FILE-->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <!--<file>${logPath}/log_debug.log</file>-->
        <!--日志文件输出格式-->
        <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%logger{50}:日志输出者的名字,%msg:日志消息,%n是换行符-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 定义了日志的切分方式,把每一天的日志归档到一个文件中 -->
            <fileNamePattern>${logPath}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录debug级别的 -->
        <!--<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>-->

        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <!--<file>${logPath}/log_info.log</file>-->
        <!--日志文件输出格式-->
        <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%logger{50}:日志输出者的名字,%msg:日志消息,%n是换行符-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 定义了日志的切分方式,把每一天的日志归档到一个文件中 -->
            <fileNamePattern>${logPath}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <!--<file>${logPath}/log_warn.log</file>-->
        <!--日志文件输出格式-->
        <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%logger{50}:日志输出者的名字,%msg:日志消息,%n是换行符-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${logPath}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <!--<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>warn</level>
        </filter>-->

        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>

    </appender>

    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <!--<file>${logPath}/log_error.log</file>-->
        <!--日志文件输出格式-->
        <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%logger{50}:日志输出者的名字,%msg:日志消息,%n是换行符-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--定义了日志的切分方式,把每一天的日志归档到一个文件中-->
            <fileNamePattern>${logPath}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!--用来指定日志文件的上限大小,例如设置为100M-->
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <!--<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>-->

        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--开发环境:打印到文件
    additivity:向上传递,false不传递,true传递,默认true。如果配置了<appender-ref>日志可能重复打印,需要
    看日志文件,不是控制台,策略是保存在日志文件,当然也可以设置打印在控制台。不配置<appender-ref>会向上传递,
    只在上面打印一次。
    -->
    <springProfile name="dev">
        <logger name="com.benjamin.logback.mapper" level="debug" additivity="true">
            <appender-ref ref="DEBUG_FILE"/>
        </logger>
    </springProfile>

    <!--测试环境:打印到文件
    additivity:向上传递,false不传递,true传递,默认true。如果配置了<appender-ref>日志可能重复打印,需要
    看日志文件,不是控制台,策略是保存在日志文件,当然也可以设置打印在控制台。不配置<appender-ref>会向上传递,
    只在上面打印一次。
    -->
    <springProfile name="qa">
        <logger name="com.benjamin.logback.mapper" level="debug">
            <appender-ref ref="DEBUG_FILE"/>
        </logger>
    </springProfile>

    <!--生产环境:打印到文件
    additivity:向上传递,false不传递,true传递,默认true。如果配置了<appender-ref>日志可能重复打印,需要
    看日志文件,不是控制台,策略是保存在日志文件,当然也可以设置打印在控制台。不配置<appender-ref>会向上传递,
    只在上面打印一次。
    -->
    <springProfile name="prod">
        <logger name="com.benjamin.logback.mapper" level="debug">
            <!--<appender-ref ref="DEBUG_FILE"/>-->
        </logger>
    </springProfile>

    <root level="info">
        <!--打印在控制台-->
        <appender-ref ref="CONSOLE"/>

        <!--打印在文件里面-->
        <appender-ref ref="DEBUG_FILE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="WARN_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
        <!--<appender-ref ref="EMAIL"/>-->
    </root>

</configuration>

这样配置会生成LOG_PATH_IS_UNDEFINED日志文件夹,springboot配置文件yml或者properties进行日志配置这样配置会出现自动生成一个日志文件夹。

logging:
  path: D:/app/logs/${spring.application.name}
  config: classpath:logback-spring.xml
  #level:
  #  root: info
  #  com.gisquest.tdzz.scheduledtask.mapper: debug
  #file.max-size: 10MB
  #file.max-history: 365
  #file: D:/app/logs/${spring.application.name}.log

 原因:springboot比logback先启动

解决:通过bootstrap.yml(bootstrap.properties)配置文件配置logback的相关配置:

Spring容器在Logback初始化之后设置LOG_PATH ,所以在 logback-spring.xml 找不到 LOG_PATH。但 bootstrap.yml 由父Spring ApplicationContext加载,父ApplicationContext被加载到使用application.yml的之前 

3.1 根节点configuration

根标签有3个属性scan、scanPeriod、debug。并且有5个子标签:<contextName>、<springProperty>、<appender>、<root>、<springProfile>。

  • scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
  • scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
  • debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
<configuration scan="true" scanPeriod="10 seconds" debug="false">
    <contextName></contextName>
    <springProperty></springProperty>
    <appender></appender>
    <root></root>
    <springProfile></springProfile>
</configuration>

3.2 子节点1:contextName

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

<contextName>logback</contextName>

3.3 子节点2:springProperty

用来定义变量,可以直接用yml里面的配置信息,主要有两个属性name、source。通过${logPath}引用此值。

<springProperty scope="context" name="logPath"  source="logging.path"/>

 3.4 子节点3:appender

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

<!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <!--<file>${logPath}/log_info.log</file>-->
        <!--日志文件输出格式-->
        <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%logger{50}:日志输出者的名字,%msg:日志消息,%n是换行符-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--定义了日志的切分方式——把每一天的日志归档到一个文件中-->
            <fileNamePattern>${logPath}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

appender的class属性常用的有三个:

ch.qos.logback.core.ConsoleAppender:把日志添加到控制台
ch.qos.logback.core.rolling.RollingFileAppender:滚动记录文件
ch.qos.logback.core.FileAppender:把日志添加到文件

滚动策略rollingPolicy常用有两个:

ch.qos.logback.core.rolling.TimeBasedRollingPolicy:常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责触发滚动
ch.qos.logback.core.rolling.FixedWindowRollingPolicy:根据固定窗口算法重命名文件的滚动策略

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

%d{HH: mm:ss.SSS}——日志输出时间
%thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用
%-5level——日志级别,并且使用5个字符靠左对齐
%logger{36}——日志输出者的名字
%msg——日志消息
%n——平台的换行符

 <filter>对日志信息过滤: 

 LevelFilter: 级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。有以下子节点:
        <level>:设置过滤级别
        <onMatch>:用于配置符合过滤条件的操作
        <onMismatch>:用于配置不符合过滤条件的操作
ThresholdFilter:临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。过滤掉所有低于Ilevel级别的日志。

3.5 子节点4:root

root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。默认是DEBUG。可以包含零个或多个元素,标识这个appender将会添加到这个loger。  

 <root level="info">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="DEBUG_FILE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="WARN_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
        <!--<appender-ref ref="EMAIL"/>-->
    </root>

 3.6 子节点5:springProfile

根据spring.profile.active配置的环境来设置logback日志。有一个子标签<logger>。

<logger>:用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的additivity属性。

  • name:用来指定受此loger约束的某一个包或者具体的某一个类。
  • level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前logger将会继承上级的级别。
  • additivity:是否向上级logger传递打印信息。默认是true。false:表示只用当前logger的appender-ref。true:表示当前logger的appender-ref和rootLogger的appender-ref都有效。如果这里没有指定并且允许向上传递,会在<root>设置的打印出来,如果这里也设置了,root也设置了,并且允许传递,可能会多次打印。
 <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <logger name="com.benjamin.logback.mapper" level="debug"/>
    </springProfile>

    <!--测试环境:打印控制台-->
    <springProfile name="qa">
        <logger name="com.benjamin.logback.mapper" level="debug"/>
    </springProfile>

    <!--生产环境:打印控制台-->
    <springProfile name="prod">
        <logger name="com.benjamin.logback.mapper" level="debug"/>
    </springProfile>

 我在logger标签配置如下:

<!--开发环境:打印到文件
    additivity:向上传递,false不传递,true传递,默认true。如果配置了<appender-ref>日志可能重复打印,需要
    看日志文件,不是控制台,策略是保存在日志文件,当然也可以设置打印在控制台。不配置<appender-ref>会向上传递,
    只在上面打印一次。
    -->
    <springProfile name="dev">
        <logger name="com.benjamin.logback.mapper" level="debug" additivity="true">
            <appender-ref ref="DEBUG_FILE"/>
        </logger>
    </springProfile>




 <root level="info">
        <!--打印在控制台-->
        <appender-ref ref="CONSOLE"/>

        <!--打印在文件里面-->
        <appender-ref ref="DEBUG_FILE"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="WARN_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
        <!--<appender-ref ref="EMAIL"/>-->
    </root>

 日志文件里面同时在debug下,打印了两条sql信息。切记要不配置root标签里面的<appender-ref>和logger标签同时设置这个,如果同时可以关闭向上传递功能,属性additivity="false"即可关闭。

 root标签、springProfile标签、logger标签根据实际应用自由组合使用:

<!--TRACE < DEBUG < INFO < WARN < ERROR < FATAL-->
第一种:
<!-- 开发环境下的日志配置 -->
<springProfile name="dev">
	<root level="INFO">
		<appender-ref ref="CONSOLE" />
		<appender-ref ref="SYSTEM_FILE" />
	</root>
</springProfile>

<!-- 生产环境下的日志配置 -->
<springProfile name="prod">
	<root level="INFO">
		<appender-ref ref="SYSTEM_FILE" />
	</root>
</springProfile>


<!-- 测试环境+开发环境. 多个使用逗号隔开. -->
<springProfile name="test,dev">
    <logger name="com.dudu.controller" level="info" />
</springProfile>

<!-- 生产环境. -->
<springProfile name="prod">
    <logger name="com.dudu.controller" level="ERROR" />
</springProfile>

<logger name="com.dudu.controller" level="ERROR" />


第二种:
<springProfile name="dev,test">
	<logger name="com.dudu.controller" level="info" />
	<root level="INFO">
		<appender-ref ref="CONSOLE" />
		<appender-ref ref="SYSTEM_FILE" />
	</root>
</springProfile>

<!-- 生产环境下的日志配置 -->
<springProfile name="prod">
	<logger name="com.dudu.controller" level="ERROR" />
	<root level="INFO">
		<appender-ref ref="SYSTEM_FILE" />
	</root>
</springProfile>

  • 7
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值