Springboot中使用日志框架

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Springboot中使用日志框架

一、概述

对于一个完整的项目而言,通过日志可以随时观察系统运行情况,日志功能是必不可少的,平时开发项目的时候想知道程序运行情况一般可以使用sysout.print(),打印一些关键的代码或者通过debug查看运行状态,使用sysout.print()会出现代码多余,于是市场上了出现许多记录运行状态的框架。

二、日志框架

日志框架分为3部分:日志接口、日志适配器、日志库

  • JCL  (Jakarta Commons Logging)
  • SLF4 (SimplefLogging Facade for Java)
  • JUL (java.util.logging)
  • Log4j是Apache下的一款开源的日志框架
  • Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。
  • Log4j2是对Log4j的升级版,参考了logback一些优秀的设计

日志接口

  • JCL  (Jakarta Commons Logging)
  • SLF4 (SimplefLogging Facade for Java)

这些框架都是日志门面,类似JDBC本身自己不干活,就是一套接口规范,让调用者不需要关心日志底层具体是什么框架在干活

日志库:也就是真实干活的人,几种常见的日志库

  • JUL (java.util.logging)JDK自带的日志工具类Java自带日志工具java.util.logging.Logger-CSDN博客
  • Log4j是Apache下的一款开源的日志框架
  • Log4j2是对Log4j的升级版,参考了logback一些优秀的设计。Log4j 是 Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI 组件,甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进程等;我们也可以控 制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。 Log4j由三个重要的组成构成:日志记录器(Loggers),输出端(Appenders)和日志格式化器(Layout)。 1.Logger:控制要启用或禁用哪些日志记录语句,并对日志信息进行级别限制 2.Appenders : 指定了日志将打印到控制台还是文件中 3.Layout : 控制日志信息的显示格式 Log4j 中将要输出的 Log 信息定义了 5 种级别,依次为 DEBUG、INFO、WARN、ERROR 和 FATAL,当输出时,只有级别高过配置中规定的 级别的信息才能真正的输出,这样就 很方便的来配置不同情况下要输出的内容,而不需要更改代码。
  • Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。Logback 是一个 Java 领域的日志框架。它被认为是 Log4J 的继承人。 Logback 主要由三个模块组成:logback-core,logback-classic。logback-access logback-core 是其它模块的基础设施,其它模块基于它构建,显然,logback-core 提供了一些关键的通用机制。logback-classic 的地位和作用等同于 Log4J,它也被认为是 Log4J 的一个改进版,并且它实现了简单日志门面 SLF4J; logback-access 主要作为一个与 Servlet 容器交互的模块,比如说 tomcat 或者 jetty,提供一些与HTTP 访问相关的功能,具有以下优点
  1. 同样的代码路径,Logback 执行更快 更充分的测试
  2. 原生实现了 SLF4J API(Log4J 还需要有一个中间转换层)
  3. 内容更丰富的文档
  4. 支持 XML 或者 Groovy 方式配置
  5. 配置文件自动热加载
  6. 从 IO 错误中优雅恢复
  7. 自动删除日志归档
  8. 自动压缩日志成为归档文件
  9. 支持 Prudent 模式,使多个 JVM 进程能记录同一个日志文件
  10. 支持配置文件中加入条件判断来适应不同的环境
  11. 更强大的过滤器
  12. 支持 SiftingAppender(可筛选 Appender)
  13. 异常栈信息带有包信息

日志适配器:它是解决日志接口和日志库接口不兼容的,一般配套的都是兼容的

所以我们的目标是挑选一个日志门面,再挑选一个日志库,搭配使用即可,在Springboot项目中整合日志框架有两套方案

  • slf4j+Logback
  • slf4j+Log4j2

三、slf4j+Logback

Springboot默认抽象接口层使用Slf4j(spring框架是JCL),实现层用Logback,当sprinboot项目引入spring-boot-starter的时候,就默认帮我们引入了logbackslf4j

当创建一个boot项目,没有任何其它配置,就看到在控制台下打印日志,Logback默认打印debug级别日志,但是我们注意到debug级别的日志没有记录下来,那是因为Spring Boot为Logback提供了默认的配置文件base.xml以及两个输出端的配置文件console-appender.xml和file-appender.xml,如下是从git上找到spring-boot用的base.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
Base logback configuration provided for compatibility with Spring Boot 1.1
-->
<included>
  <include resource="org/springframework/boot/logging/logback/defaults.xml" />
  <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
  <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
  <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
  <root level="INFO">
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="FILE" />
  </root>
</included>

可以看到base.xml引用了console-appender.xml和file-appender.xml这两个配置文件。可以看到 Spring boot已通过将根记录器设置为INFO来覆盖 Logback 的默认日志记录级别, 所以没有看到debug消息的原因。

Springboot日志框架配置有以下两种方式

第一种:简单配置

小项目可以直接在application.properties中对logback简单配置,如下

 
#修改指定包下的日志输出级别
logging.level.com.javayihao=trace
#当前项目下生成mylog.log日志记录输出的日志
#logging.file=mylog.log
#在指定的路径下输出日志
#logging.file=f:/mylog.log
#当前的磁盘根路径下创建spring文件家和里面的log文件夹,以spring.log作为日志文件名字
logging.path=/spring/log
#指定控制台输出日志的格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS}+++[%thread] %-5level %logger{50} - %msg%n
#执行日志文件中输出日志的格式
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS}===[%thread] %-5level %logger{50} - %msg%n

第二种:通过logback专有的xml配置文件进行详细配置

通过application.properties文件配置Logback,对于大多数Spring Boot应用来说已经足够了,但是对于一些大型的企业应用来说似乎有一些相对复杂的日志需求。在Spring Boot中你可以在logback.xml或者在logback-spring.xml中对Logback进行配置,相对于logback.xml,logback-spring.xml更加被偏爱。如果是其他名字,只需在application.propertions

中配置logging.config=classpath:logback-boot.xml使用自己的日志配置文件即可

logback.xml结构

<configuration scan="true" scanPeriod="60 seconds" debug="false">  
    <property name="glmapper-name" value="glmapper-demo" /> 
    <contextName>${glmapper-name}</contextName> 
    
    
    <appender>
        //xxxx
    </appender>   
    
    <logger>
        //xxxx
    </logger>
    
    <root>             
       //xxxx
    </root>  
</configuration>  

属性

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

property

用来定义变量值的标签,property标签有两个属性,namevalue;其中name的值是变量的名称,value的值时变量定义的值。通过property定义的值会被插入到logger上下文中。定义变量后,可以使“${name}”来使用变量。如上面的xml所示。

contextName

每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用contextName标签设置成其他名字,用于区分不同应用程序的记录

appender负责写日志的组件

<appender name="GLMAPPER-LOGGERONE"
    class="ch.qos.logback.core.rolling.RollingFileAppender">
    <append>true</append>
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>${logging.level}</level>
    </filter>
    <file>
        ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
    </file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}</FileNamePattern>
        <MaxHistory>30</MaxHistory>
    </rollingPolicy>
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
</appender>

appender 有两个属性 nameclass;name指定appender名称,class指定appender的全限定名。上面声明的是名为GLMAPPER-LOGGERONEclassch.qos.logback.core.rolling.RollingFileAppender的一个appender

appender 的种类

  • ConsoleAppender:把日志添加到控制台
  • FileAppender:把日志添加到文件
  • RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件。它是FileAppender的子类

append 子标签

<append>true</append>

如果是 true,日志被追加到文件结尾,如果是false,清空现存文件,默认是true

filter 子标签

filter其实是appender里面的子元素。它作为过滤器存在,执行一个过滤器会有返回DENY,NEUTRAL,ACCEPT三个枚举值中的一个。appender 有多个过滤器时,按照配置顺序执行。

  • DENY:日志将立即被抛弃不再经过其他过滤器
  • NEUTRAL:有序列表里的下个过滤器过接着处理日志
  • ACCEPT:日志会被立即处理,不再经过剩余过滤器

filter的class有ThresholdFilter以及LevelFilter

ThresholdFilter

临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。

<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    <level>INFO</level>
</filter>

LevelFilter

级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath(用于配置符合过滤条件的操作) 和 onMismatch(用于配置不符合过滤条件的操作)接收或拒绝日志。

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

file 子标签

file 标签用于指定被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。

<file>
    ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
</file>

这个表示当前appender将会将日志写入到${logging.path}/glmapper-spring-boot/glmapper-loggerone.log这个目录下。

rollingPolicy 子标签

这个子标签用来描述滚动策略的。这个只有appenderclassRollingFileAppender时才需要配置。这个也会涉及文件的移动和重命名(a.log->a.log.2018.07.22)。class有TimeBasedRollingPolicy和FixedWindowRollingPolicy

TimeBasedRollingPolicy

最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。这个下面又包括了两个属性:

  • FileNamePattern
  • maxHistory
<rollingPolicy 
    class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!--日志文件输出的文件名:按天回滚 daily -->
    <FileNamePattern>
        ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}
    </FileNamePattern>
    <!--日志文件保留天数-->
    <MaxHistory>30</MaxHistory>
</rollingPolicy>
复制代码

上面的这段配置表明每天生成一个日志文件,保存30天的日志文件

FixedWindowRollingPolicy

根据固定窗口算法重命名文件的滚动策略。

encoder 子标签

对记录事件进行格式化。它干了两件事:

  • 把日志信息转换成字节数组
  • 把字节数组写入到输出流
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}
    - %msg%n</pattern>
    <charset>UTF-8</charset>
</encoder>

目前encoder只有PatternLayoutEncoder一种类型。

appender案例

定义一个只打印error级别日志的appdener

 <!-- 错误日志 appender : 按照每天生成日志文件 -->
<appender name="ERROR-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <append>true</append>
    <!-- 过滤器,只记录 error 级别的日志 -->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>error</level>
    </filter>
    <!-- 日志名称 -->
    <file>${logging.path}/glmapper-spring-boot/glmapper-error.log</file>
    <!-- 每天生成一个日志文件,保存30天的日志文件 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!--日志文件输出的文件名:按天回滚 daily -->
        <FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-error.log.%d{yyyy-MM-dd}</FileNamePattern>
        <!--日志文件保留天数-->
        <MaxHistory>30</MaxHistory>
    </rollingPolicy>
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        <!-- 编码 -->
        <charset>UTF-8</charset>
    </encoder>
</appender>

定义一个输出到控制台的appender

<!-- 默认的控制台日志输出,一般生产环境都是后台启动,这个没太大作用 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <Pattern>%d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n</Pattern>
    </encoder>
</appender>

logger

用来设置某一个包或者具体的某一个类的日志打印级别以及指定appender

root

根logger,也是一种logger,且只有一个level属性

案例logback.xml

a):统一配置

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<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把日志写到文件中-->
    <!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是sys.log -->
    <!--             2.如果日期没有发生变化,但是当前日志的文件大小超过1KB时,对当前日志进行分割 重命名-->
    <appender name="syslog"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--当前项目下生成sys.log日志记录输出的日志-->
        <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>10MB</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.javayihao为根包,也就是只要是发生在这个根包下面的所有日志操作行为的权限都是DEBUG -->
    <!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE  -->
    <logger name="com.javayihao" level="DEBUG">
        <appender-ref ref="syslog" />
    </logger>
</configuration>

b):生产和测试环境分开配置

可以在application-dev.ymlapplication-pro.yml中分别指明不同环境的logback配置文件的目录,即可分开,也开使用一个xml文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml" />
 
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
 
    <!-- 测试环境+开发环境. 多个使用逗号隔开. -->
    <springProfile name="test,dev">
        <logger name="org.springframework.web" level="INFO">
            <appender-ref ref="FILE"/>
        </logger>
        <logger name="com.example" level="INFO" />
    </springProfile>
 
    <!-- 生产环境. -->
    <springProfile name="prod">
        <logger name="org.springframework.web" level="ERROR">
            <appender-ref ref="FILE"/>
        </logger>
        <logger name="com.example" level="ERROR" />
    </springProfile>
</configuration>

c:)提示信息和异常记录分开记录

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
 
 
    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{50} - %msg%n"/>
 
    <property name="LOG_HOME" value="./log"/>
 
 
    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <!--<charset>utf8</charset>-->
        </encoder>
    </appender>
 
 
    <!--info 级别的日志-->
    <!-- 按照每天生成日志文件 -->
    <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
        <file>${LOG_HOME}/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <fileNamePattern>${LOG_HOME}/achiveLog/info-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <!--日志文件保留天数-->
            <MaxHistory>180</MaxHistory>
        </rollingPolicy>
    </appender>
 
 
    <!--WARN 级别的日志-->
    <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
        <file>${LOG_HOME}/warn.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/achiveLog/warn-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <MaxHistory>180</MaxHistory>
        </rollingPolicy>
    </appender>
 
    <!--ERROR 级别的日志-->
    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
        <file>${LOG_HOME}/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/achiveLog/error-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <MaxHistory>180</MaxHistory>
        </rollingPolicy>
    </appender>
 
    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="INFO"/>
        <appender-ref ref="WARN"/>
        <appender-ref ref="ERROR"/>
    </root>
</configuration>

首先看一下根节点root,这个level="INFO"指的是日志登记,按日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,我们这边指定了INFO,也就是说DEBUG的日志是不会被输出出来的

根节点有4个直接点,挑一个做一下解释

    <!--WARN 级别的日志-->
    <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
        <file>${LOG_HOME}/warn.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/achiveLog/warn-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <MaxHistory>180</MaxHistory>
        </rollingPolicy>
    </appender>
  • filter:用于过滤日志
  • level:匹配等级是WARN
  • onMatch:当匹配时ACCEPT
  • onMismatch:不匹配时DENY,不匹配的过滤掉

因此只过滤出levelwarn级别的日志,其他的日志信息经过这个节点会被过滤掉,也就不会进入到后续流程了。如果你不写onMatchonMismatch标签,那么error级别的因为大于warn,也会进入到后续流程

而我们说的后续流程就是下面的标签。这些标签指明过滤出来的数据应该放到控制台还是放到文件

  • file:这些过滤出来的数据需要被写入到文件中
  • fileNamePattern:文件的目录和命名格式warn-yyyy-mm-dd_1.log
  • maxFileSize:文件最大50MB
  • MaxHistory:保留18天

四、slf4j+Log4j2

1、引入pom

<!--==========war 包 web 工程==========-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!--排除 springboot 默认的 logback 依赖 -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--引入 log4j2-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>
<!--异步,使用 log4j2 的 AsyncLogger 时需要包含 disruptor-->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.2</version>
</dependency>
 
 
 
<!--==========jar 包启动工程==========-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <!--排除 springboot 默认的 logback 依赖 -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--引入 log4j2-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>
<!--异步,使用 log4j2 的 AsyncLogger 时需要包含 disruptor-->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.2</version>
</dependency>

2、application.yml

# 引入日志配置文件
logging:
  config: classpath:log4j2.xml

3、log4j2.xml 放在目录 resources 下即可

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration 后面的 status,这个用于设置 log4j2 自身内部的信息输出级别,可以不设置,当设置成 trace 时,你会看到 log4j2 内部各种详细输出-->
<!--monitorInterval:Log4j2 能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="error"  monitorInterval="30">
    <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
 
    <!--变量配置-->
    <Properties>
        <!-- 格式化输出:%date 表示日期,%thread 表示线程名,%-5level:级别从左显示 5 个字符宽度 %msg:日志消息,%n 是换行符-->
        <!-- %logger{36} 表示 Logger 名字最长 36 个字符 -->
        <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%logger{50}:%L] - %msg%n" />
        <!-- 定义日志存储的路径 -->
        <property name="FILE_PATH" value="/var/log/songo" />
        <property name="FILE_NAME" value="songo" />
    </Properties>
 
    <appenders>
 
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
        </console>
 
        <!-- 这个会打印出所有的info及以上级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}/${FILE_NAME}.log" filePattern="${FILE_PATH}/${FILE_NAME}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz" append="true">
            <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
            <Policies>
                <!-- 基于时间的触发策略。该策略主要是完成周期性的log文件封存工作。有两个参数:
               interval,integer型,指定两次封存动作之间的时间间隔。单位:以日志的命名精度来确定单位,
                   比如yyyy-MM-dd-HH 单位为小时,yyyy-MM-dd-HH-mm 单位为分钟
               modulate,boolean型,说明是否对封存时间进行调制。若modulate=true,
                   则封存时间将以0点为边界进行偏移计算。比如,modulate=true,interval=4hours,
                   那么假设上次封存日志的时间为00:00,则下次封存日志的时间为04:00,
                   之后的封存时间依次为08:00,12:00,16:00-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy 属性如不设置,则默认为最多同一文件夹下当天 7 个文件后开始覆盖-->
            <DefaultRolloverStrategy max="30">
                <!-- 删除处理策略,在配置的路径中搜索,maxDepth 表示往下搜索的最大深度 -->
                <Delete basePath="${FILE_PATH}/${FILE_NAME}/" maxDepth="2">
                    <!-- 文件名搜索匹配,支持正则 -->
                    <IfFileName glob="*.log.gz" />
                    <!--!Note: 这里的 age 必须和 filePattern 协调, 后者是精确到 dd, 这里就要写成 xd, xD 就不起作用
                    另外, 数字最好 >2, 否则可能造成删除的时候, 最近的文件还处于被占用状态,导致删除不成功!-->
                    <!--7天-->
                    <IfLastModified age="7d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </appenders>
 
    <!--Logger 节点用来单独指定日志的形式,比如要为指定包下的 class 指定不同的日志级别等。-->
    <!--然后定义 loggers,只有定义了 logger 并引入的 appender,appender 才会生效-->
    <loggers>
        <!--若是 additivity 设为 false,则子 Logger 只会在自己的 appender 里输出,而不会在父 Logger 的 appender 里输出。-->
        <!--Console、RollingFileInfo 没有配置 ThresholdFilter,默认走的是 AsyncRoot 的 level 级别,
            com.songo.mapper 为我项目 mapper 的包路径,级别设为 debug,可以打印 sql 语句-->
        <AsyncLogger name="com.songo.mapper" level="debug" additivity="false">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFileInfo"/>
        </AsyncLogger>
        <AsyncLogger name="org.springframework" level="warn" additivity="false">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFileInfo"/>
        </AsyncLogger>
        <AsyncRoot level="info" includeLocation="true">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFileInfo" />
        </AsyncRoot>
    </loggers>
</configuration>

4、声明 logger 变量

package com.songo.service;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class TestService {
    private static final Logger logger = LoggerFactory.getLogger(TestService.class);
    
    public void Test() {
        logger.info("test...");
    }
}

5、附自己使用配置文件

<?xml version="1.0" encoding="utf-8"?>
<configuration status="INFO">
    <Properties>
        <Property name="SYSTEM_LOG_FILE_PATH">${sys:LOG_PATH}/apps/manager</Property>
        <Property name="MANAGER_REPORT_LOG_FILE_PATH">${sys:LOG_PATH}/apps/manager</Property>
    </Properties>
 
    <Appenders>
        <!--应用服务器日志-->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
 
        <RollingFile name="SYSTEM_INFO_ROLLING_FILE"
                     fileName="${SYSTEM_LOG_FILE_PATH}/logs.log"
                     filePattern="${SYSTEM_LOG_FILE_PATH}/%d{yyyy-MM-dd}-info.log">
            <PatternLayout>
                <Pattern>%d [%t] %-5p [%c] - %m%n</Pattern>
            </PatternLayout>
            <!-- 文件截断的条件,具体参考文档 -->
            <Policies>
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
        </RollingFile>
 
        <!-- 定义ERROR的Appender -->
        <RollingFile name="SYSTEM_ERROR_ROLLING_FILE"
                     fileName="${SYSTEM_LOG_FILE_PATH}/error-logs.log"
                     filePattern="${SYSTEM_LOG_FILE_PATH}/%d{yyyy-MM-dd}-error.log">
            <!-- 可以通过该参数来设置获取日志的权限 -->
            <ThresholdFilter level="ERROR"/>
            <PatternLayout>
                <Pattern>%d [%t] %-5p [%c] - %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy interval="24"/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
        </RollingFile>
        <!--报表日志-->
        <RollingFile name="MANAGER_REPORT_LOG_FILE"
                     fileName="${MANAGER_REPORT_LOG_FILE_PATH}/report.log"
                     filePattern="${MANAGER_REPORT_LOG_FILE_PATH}/report.log.%d{yyyy-MM-dd-HH}">
            <PatternLayout>
                <Pattern>%m%n</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
        </RollingFile>
    </Appenders>
 
    <Loggers>
        <!--控制台是否打印spring和mybatis信息-->
        <!--                <logger name="org.mybatis" level="info" additivity="false">-->
        <!--                    <AppenderRef ref="Console"/>-->
        <!--                </logger>-->
        <!--                <Logger name="org.springframework" level="info" additivity="false">-->
        <!--                    <AppenderRef ref="Console"/>-->
        <!--                </Logger>-->
        <!--同步输出日志-->
<!--        <logger name="com.alliance.adx.controller" level="INFO" additivity="false">-->
<!--            <appender-ref ref="ADX_REPORT_LOG_FILE"/>-->
<!--        </logger>-->
        <!--异步输出日志
        <AsyncLogger name="com.demo.util.LogUtil" level="INFO" additivity="false">
            <appender-ref ref="REPORT_LOG_FILE"/>
        </AsyncLogger>-->
 
        <root level="info">
            <!--控制台是否打印输出信息-->
            <appender-ref ref="Console"/>
 
            <appender-ref ref="SYSTEM_INFO_ROLLING_FILE"/>
            <appender-ref ref="SYSTEM_ERROR_ROLLING_FILE"/>
        </root>
    </Loggers>
 
</configuration>

注意:对于AsyncLogger的name属性可以是指定的类,也可以是字符串

//指定类 需要打日志地方直接调用即可
public class LogUitl{
    public static final Logger log = LogManager.getLogger(LogUitl.class);
    /**
     * 记录
     */
    public static void record(String str) {
        log.info(str);
    }
}
 
 
//直接使用字符串  
public class Test{
//AsyncLogger的name属性也为testLog
    public static final Logger log = LogManager.getLogger("testLog");
    /**
     * 记录
     */
    public void test(String str) {
        log.info(str);
    }
}

五、Springboot记录日志方式

上面分别介绍了boot整合日志框架的两种方式,下面介绍在业务代码中如何记录业务日志

1、使用记录器logger记录

  1. 考虑好异常属于什么级别的异常,再打日志。想好是log.error还是log.warn

比如业务异常往往通过用户引导就可以恢复的,那么就只打WARN级别。而ERROR级别的日志一旦出现,就需要人工介入,因此要定期检查ERROR日志排查问题。这段是《阿里Java开发手册》写的

public class TestController{
   //记录器
   Logger logger = LoggerFactory.getLogger(getClass());
   @Test
   public void contextLoads() {
      //日志级别,由低到高,调整日志级别,只输出高级别日志,
      logger.trace("这是trace跟踪信息");
      logger.debug("这是debug调试信息");
      //springboot默认输出info以后的级别,也就是root级别
      logger.info("这是info自定义信息");
      logger.warn("这是warn警告信息");
      logger.error("这是error错误信息");
   }
}

看下logger.error代码就知道error有2个重载方法

public void error(String msg);
public void error(String msg, Throwable t);

上面的代码只有一个参数,因此都会被认为是调用第一种方法,这样造成的结果就是e将会被自动转成String类型,从而丢失的许多错误信息、堆栈信息,即不知道哪个方法调用了service产生的错误,也不知道错误的原因,更不知道代码抛出异常的行数。生产一旦出现问题,根本无从排起,

而正确的日志,我们即可以知道发生错误的原因,抛出异常的行数,同时也能获悉堆栈调用的关系,这样排查生产问题才有解决的可能性。

logger.error("x部分出错", e);

2、通过AOP思想记录日志

代码1:

@Aspect//切面
@Component//组件
public class LogAspect {
 
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
    /**
     * 这里把切点切再controller(web)层下的每个方法
     */
    @Pointcut("execution(* com.javayihao.myweb.controller.*.*(..))")
    public void log() {
    }
 
 
    /**
     * 执行com.javayihao.myweb.controller下所有的方法之前执行这个方法
     *  获取要记录的url ip 请求的方法 以及请求时候所带的参数
     * @param joinPoint
     */
    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();
        String ip = request.getRemoteAddr();
        //类名.方法
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        //参数
        Object[] args = joinPoint.getArgs();
        //调用自己封装的对象
        RequestLog requestLog = new RequestLog(url, ip, classMethod, args);
        logger.info("Request : {}", requestLog);
    }
 
    @After("log()")
    public void doAfter() {
//        logger.info("--------doAfter--------");
    }
 
    //执行com.javayihao.myweb.controller下所有的方法之后要执行的方法
    @AfterReturning(returning = "result",pointcut = "log()")
    public void doAfterRuturn(Object result) {
        logger.info("Result : {}", result);
    }
 
    private class RequestLog {
        private String url;
        private String ip;
        private String classMethod;
        private Object[] args;
 
        public RequestLog(String url, String ip, String classMethod, Object[] args) {
            this.url = url;
            this.ip = ip;
            this.classMethod = classMethod;
            this.args = args;
        }
 
        @Override
        public String toString() {
            return "{" +
                    "url='" + url + '\'' +
                    ", ip='" + ip + '\'' +
                    ", classMethod='" + classMethod + '\'' +
                    ", args=" + Arrays.toString(args) +
                    '}';
        }
    }
 
}

代码2:

@Aspect
@Component
public class LogAopAspect {
 
    private static final Logger logger = LoggerFactory.getLogger(LogAopAspect.class);
 
 
    @Around("execution(* company.controller.*.*(..))")
    public Object process(ProceedingJoinPoint pjp) throws Throwable {
 
        Class<?> currentClass = pjp.getTarget().getClass();
        MethodSignature signature = (MethodSignature) (pjp.getSignature());
        String ClassName = currentClass.getSimpleName();
        String methodName = currentClass.getMethod(signature.getName(), signature.getParameterTypes()).getName();
        logger.info("======= 开始执行:" + ClassName + " — " + methodName + " ========");
        Object obj = pjp.proceed();
        logger.info("======= 执行结束:" + ClassName + " — " + methodName + " ========");
        return obj;
    }
}

六、如何使用其他日志框架

项目中如果要记录日志,不应该直接使用一个日志实现类记录,而是通过一个日志抽象层 + 一个日志抽象实现类,然后调用抽象层里面的接口记录接口,如下使用日志抽象类SLF4j

boot就是这么做的,这里主要总结一下日志抽象类SLF4j和其他日志实现类的使用

官网:SLF4J

使用方法,官网的一张图说的很明白

问题:x系统使用Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx框架实现,每个框架有不同的日志记录框架,如何统一使用都使用slf4j

解决方法

1、将系统中其他日志框架先排除出去;
2、用中间包来替换原有的日志框架;
3、我们导入slf4j其他的实现

比如我们要使用slf4j+log4j的方式来记录日志,依赖如下

再如我们要使用slf4j+log4j2的方式来记录日志,依赖如下

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring‐boot‐starter‐logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐log4j2</artifactId>
</dependency>

但是在boot项目中不推荐slf4j+log4j2或者slf4j+log4j组合来记录日志,springboot官方推荐slf4+logback


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
15天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
128 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
12天前
|
Java 中间件
SpringBoot入门(6)- 添加Logback日志
SpringBoot入门(6)- 添加Logback日志
52 5
|
23天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
1月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
42 4
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
148 1
|
1月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
37 0
|
27天前
|
前端开发 Java 数据库连接
Spring 框架:Java 开发者的春天
Spring 框架是一个功能强大的开源框架,主要用于简化 Java 企业级应用的开发,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,并由Pivotal团队维护。
43 1
Spring 框架:Java 开发者的春天
|
19天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
37 2
|
19天前
|
消息中间件 NoSQL Java
springboot整合常用中间件框架案例
该项目是Spring Boot集成整合案例,涵盖多种中间件的使用示例,每个案例项目使用最小依赖,便于直接应用到自己的项目中。包括MyBatis、Redis、MongoDB、MQ、ES等的整合示例。
76 1
|
20天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
34 1