SpringBoot项目logback日志配置

  • Session 认证和 Token 认证

  • 过滤器和拦截器

  • SpringBoot统一返回和统一异常处理

  • SpringBoot项目logback日志配置

程序运行出现错误时,第一时间想到的是甩锅还是日志?通过查看日志定位出问题的位置,才能更好的甩锅,今天就来学习 springBoot 日志如何配置。

一、日志框架

Java 中的日志框架分为两种,分别为日志抽象/门面、日志实现。

日志门面不负责日志具体实现,它只是为所有日志框架提供一套标准、规范的API框架。其主要意义在于提供接口,具体的实现可以交由其它日志框架,例如 log4jlogback等。

当今主流的的日志门面是SLF4JSpringBoot 中推荐使用该门面技术。

1.1、SLF4J

SLF4J官网地址:https://www.slf4j.org/

SLF4J(Simple Logging Facade For Java),即简单日志门面,它用作各种日志框架(例如Java.util.Logging、logback、log4j)的简单门面或抽象,允许最终用户在部署时插入所需的日志框架。

它和JDBC 差不多,JDBC 不关心具体的数据库实现,同样的,SLF4J 也不关心具体日志框架实现。

application 下面的 SLF4JAPI 表示 SLF4J 的日志门面,包含以下三种情况:

  1. 如果只是导入 slf4j 日志门面,没有导入对应的日志实现框架,则日志功能默认是关闭的,不会进行日志输出。
  2. 蓝色图里 Logback、slf4j-simple、slf4j-nop 遵循 slf4jAPI 规范,只要导入对应的日志实现框架,来实现开发
  3. 中间两个日志框架 slf4j-reload4、JUL(slf4j-jdk14) 没有遵循 slf4jAPI 规范,所有无法直接使用,中间需要增加一个适配层 (Adaptation layer),通过对应的适配器来适配具体的日志实现框架。
1.2、日志实现框架

Java 中的日志实现框架,主流的有以下几种:

  1. log4j :老牌日志框架,已经多年不更新了,性能比 logback、log4j2 差。
  2. logbacklog4j 创始人创建的另一个开源日志框架,SpringBoot 默认的日志框架。
  3. log4j2Apache 官方项目,传闻性能优于 logback,它是 log4j 的新版本。
  4. JUL(Java.Util.Logging), jdk 内置。

在项目中,一般都是日志门面+日志实现框架组合使用,这样更灵活,适配起来更简单。

前面提到logback作为Spring Boot默认的日志框架 ,肯定有相应的考量,我司也是使用logback 作为 Spring Boot 项目中的日志实现框架,下面我们就详细说说 logback

二、SpringBoot 日志框架 logback

2.1、logback 是什么?

logbacklog4j 团队创建的开源日志组件。与 log4j 类似,但是比 log4j 更强大,是log4j 的改良版本。

logback 主要包含三个模块:

  1. logback-core :所有 logback 模块的基础。
  2. logback-classic :是 log4j 的改良版本,完整实现了slf4j API
  3. logback-access :访问模块和 servlet 容器集成,提供通过 http 来访问日志的功能。
2.2、logback 的日志级别有哪些?

日志级别(log level):用来控制日志信息的输出,从高到低共分为七个等级。

  • OFF :最高等级,用于关闭所有信息。
  • FATAL :灾难级的,系统级别,程序无法打印。
  • ERROR :错误信息
  • WARN :告警信息
  • INFO :普通的打印信息
  • DEBUG :调试,对调试应用程序有帮助。
  • TRACE :跟踪

如果项目中日志级别设置为 INFO,则比它更低级别的日志信息将看不到了,即 DEBUG 日志不会显示。
默认情况下,Spring Boot 会用Logback 来记录日志,并用 INFO 级别输出到控制台。

2.3、SpringBoot 中如何使用日志?

首先新建一个 SpringBoot 项目 log ,我们看到 SpringBoot 默认已经引入 logback 依赖。

启动项目,日志打印如下:

从图中可以看出,输出的日志默认元素如下:

  1. 时间日期:精确到毫秒。
  2. 日志级别:默认是 INFO
  3. 进程 Id
  4. 分隔符:—标识日志开始的地方。
  5. 线程名称:方括号括起来的。
  6. Logger 名称:源代码的类名。
  7. 日志内容

在业务中输出日志,常见的有两种方式。

方式一:在业务代码里添加如下代码


private final Logger log = LoggerFactory.getLogger(LoginController.class);
package com.duan.controller;import com.duan.pojo.Result;
import com.duan.pojo.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;/*** @author db* @version 1.0* @description LoginController* @since 2023/12/19*/
@RestController
public class LoginController {private final Logger log = LoggerFactory.getLogger(LoginController.class);@PostMapping("/login")public Result login(@RequestBody User user){log.info("这是正常日志");if("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())){return Result.success("ok");}return Result.error();}
}

每个类中都要添加这行代码才能输出日志,这样代码会很冗余。

方式二:使用 lomback 中的 @Slf4j 注解,但是需要在 pom 中引用 lomback 依赖

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>

使用时只需要在类上标注一个 @Slf4j 注解即可


package com.duan.controller;import com.duan.pojo.Result;
import com.duan.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;/*** @author db* @version 1.0* @description LoginController* @since 2023/12/19*/
@RestController
@Slf4j
public class LoginController {@PostMapping("/login")public Result login(@RequestBody User user){log.info("这是正常日志");if("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())){return Result.success("ok");}return Result.error();}
}
2.4、如何指定具体的日志级别?

前面我们提到, SpringBoot 默认的日志级别是 INFO,根据需要我们还可以具体的日志级别,如下:

logging:level:root: ERROR

将所有的日志级别都改为了 ERROR,同时 SpringBoot 还支持包级别的日志调整,如下:

logging:level:com:duan:controller: ERROR

com.duan.controller 是项目包名。

2.5、日志如何输出到指定文件

SpringBoot 默认是把日志输出到控制台,生成环境中是不行的,需要把日志输出到文件中。
其中有两个重要配置如下:

  1. logging.file.path :指定日志文件的路径
  2. logging.file.name :日志的文件名,默认为 spring.log
    注意:官方文档说这两个属性不能同时配置,否则不生效,因此只需要配置一个即可。

指定日志输出文件存在当前路径的 log 文件夹下,默认生成的文件为 spring.log

logging:file:path: ./logs
2.6、自定义日志配置

SpringBoot 官方优先推荐使用带有 -spring 的文件名称作为项目日志配置,所以只需要在 src/resource 文件夹下创建 logback-spring.xml 即可,配置文件内容如下:

<?xml version="1.0" encoding="UTF-8"?><!-- logback默认每60秒扫描该文件一次,如果有变动则用变动后的配置文件。 -->
<configuration scan="false"><!-- ==============================================开发环境=========================================== --><springProfile name="dev"><!-- 控制台输出 --><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder></appender><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="STDOUT"/></root></springProfile><!-- ==============================================生产环境=========================================== --><springProfile name="prod"><!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--><property name="LOG_HOME" value="./log"/><!-- 控制台输出 --><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder></appender><!-- 按照每天生成日志文件 --><appender name="INFO_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"><!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。--><file>${LOG_HOME}/info.log</file><!--滚动策略,按照大小时间滚动 SizeAndTimeBasedRollingPolicy--><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${LOG_HOME}/info.%d{yyyy-MM-dd}.%i.log</FileNamePattern><!--只保留最近30天的日志--><MaxHistory>30</MaxHistory><!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志--><totalSizeCap>1GB</totalSizeCap><MaxFileSize>10MB</MaxFileSize></rollingPolicy><!--日志输出编码格式化--><encoder><charset>UTF-8</charset><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder><!--过滤器,只有过滤到指定级别的日志信息才会输出,如果level为ERROR,那么控制台只会输出ERROR日志--><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>INFO</level></filter></appender><!-- 按照每天生成日志文件 --><appender name="ERROR_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"><!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。--><file>${LOG_HOME}/error.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern><MaxHistory>30</MaxHistory><!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志--><totalSizeCap>1GB</totalSizeCap><MaxFileSize>10MB</MaxFileSize></rollingPolicy><encoder><charset>UTF-8</charset><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>ERROR</level></filter></appender><!--指定最基础的日志输出级别--><root level="INFO"><!--appender将会添加到这个loger--><appender-ref ref="STDOUT"/><appender-ref ref="INFO_APPENDER"/><appender-ref ref="ERROR_APPENDER"/></root></springProfile>
</configuration>

最基本配置是一个 configuration 里面有零个或多个 appender,零个或多个 logger 和最多一个 root 标签组成。(logback 对大小写敏感)

configuration 节点:根节点,属性如下:

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

root 节点:必须的节点,用来指定基础的日志级别,只有一个属性。该节点可以包含零个或者多个元素,子节点是 appender-ref ,标记 appender 将会添加到这个 logger 中。

  • level :默认值 DEBUG

contextName 节点:标识一个上下文名称,默认 default ,一般用不到。

property 节点:标记一个上下文变量,属性有 namevalue,定义变量之后用 ${} 获取值。

appender 节点:<appender><configuration> 的子节点,主要用于格式化日志输出节点,属性有 nameclassclass 用来指定那种输出策略,常用的就是控制台输出策略和文件输出策略。有几个子节点比较重要。

  • filter :日志输出拦截器,没特殊要求就使用系统自带的,若要将日志分开,比如将 ERROR 级别的日志输出到一个文件中,其他级别的日志输出到另一个文件中,这时候就要用到 filter
  • encoder :和 pattern 节点组合用于具体输出日志的格式和编码方式。
  • file :用来指定日志文件输出位置,绝对路径或者相对路径。
  • rollingPolicy :日志回滚策略,常见的就是按照时间回滚策略(TimeBasedRollingPolicy) 和按照大小时间回滚策略 (SizeAndTimeBasedRollingPolicy)
  • maxHistory :可选节点,控制保留日志文件的最大数量,超出数量就删除旧文件。
  • totalSizeCap :可选节点,指定日志文件的上限大小。

logger 节点:可选节点,用来指定某一个包或者具体某一个类的日志打印级别。

  • name :指定包名。
  • level :可选,日志的级别。
  • addtivity :可选,默认为 true,此 logger 的信息向上传递。

springProfile :多环境输出日志文件,根据配置文件激活参数 (active) 选择性的包含和排查部分配置信息。根据不同环境来定义不同的日志输出。

logback 中一般有三种过滤器 Filter

  1. LevelFilter :级别过滤器,根据日志级别进行过滤,如果日志级别等于配置级别,过滤器会根据onMathonMismatch 接受或者拒绝日志。有以下子节点
  • level :设置过滤级别
  • onMath :配置符合过滤条件的操作
  • onMismath :配置不符合过滤条件的操作
<!-- 在文件中出现级别为INFO的日志内容 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">  <level>INFO</level>  <onMatch>ACCEPT</onMatch>  <onMismatch>DENY</onMismatch>  
</filter> <!-- 在文件中出现级别为INFO、ERROR的日志内容 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">  <level>INFO</level>  <level>ERROR</level>
</filter> 
  1. ThresholdFilter :临界值过滤器,过滤掉低于临界值的日志,当日志级别等于或高于临界值时,过滤器返回 NEUTRAL ;当日志级别低于临界值时,日志会被拒绝。

<configuration>   <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">   <!-- 过滤掉 TRACE 和 DEBUG 级别的日志-->   <filter class="ch.qos.logback.classic.filter.ThresholdFilter">   <level>INFO</level>   </filter>   <encoder>   <pattern>   %-4relative [%thread] %-5level %logger{30} - %msg%n   </pattern>   </encoder>   </appender>   <root level="DEBUG">   <appender-ref ref="CONSOLE" />   </root>   
</configuration>
  1. EvaluatorFilter :求值过滤器,评估、鉴别日志是否符合指定条件。

如果不使用 SpringBoot 推荐的名字,想用自己定制的也可以,只需要在配置文件中配置。

logging:config: logging-config.xml
2.7、异步日志

之前都是用同步去记录日志,这样代码效率会大大降低,logback 提供异步记录日志功能。

原理:

系统会为日志操作单独分配一个线程,原来用来执行当前方法是主线程会继续向下执行,线程1:系统业务代码执行。线程2:打印日志

<!-- 异步输出 -->
<appender name ="async-file-info" class= "ch.qos.logback.classic.AsyncAppender"><!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 --><discardingThreshold >0</discardingThreshold><!-- 更改默认的队列的深度,该值会影响性能.默认值为256 --><queueSize>256</queueSize><!-- 添加附加的appender,最多只能添加一个 --><appender-ref ref ="INFO_APPENDER"/></appender>
<root level="INFO"><!-- 引入appender --><appender-ref ref="async-file-info"/>
</root>
2.8、如何定制日志格式?

上面我们已经看到默认的日志格式,实际项目代码中的日志格式不会是 logback 默认的格式,要根据项目业务要求,进行修改,下面我们来看如何定制日志格式。

# 常见的日志格式
2023-12-21 10:39:44.631----[应用名|主机ip|客户端ip|用户uuid|traceid]----{}
解释
2023-12-21 10:39:44.631:时间,格式为yyyy-MM-dd HH:mm:ss.SSS
应用名称:标识项目应用名称,一般就是项目名
主机ip:本机IP
客户端ip:请求IP
用户uuid:根据用户uuid可以知道是谁调用的
traceid:追溯当前链路操作日志的一种有效手段

创建自定义格式转换符有两步:

  • 首先必须继承 ClassicConverter 类,ClassicConverter 对象负责从 ILoggingEvent提取信息,并产生一个字符串。
  • 然后要让 logback 知道新的 Converter,方法是在配置文件里声明新的转换符。

config 包中新建 HostIpConfig 类、RequestIpConfig 类、UUIDConfig 类,代码如下:

HostIpConfig.java

package com.duan.config;import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.duan.utils.LocalIP;/*** @author db* @version 1.0* @description HostIpConfig 获得主机IP地址* @since 2024/1/9*/
public class HostIpConfig extends ClassicConverter {@Overridepublic String convert(ILoggingEvent event) {String hostIP = LocalIP.getIpAddress();return hostIP;}
}

RequestIpConfig.java

package com.duan.config;import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.duan.utils.IpUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;/*** @author db* @version 1.0* @description RequestIpConfig  获得请求IP* @since 2024/1/9*/
public class RequestIpConfig extends ClassicConverter {@Overridepublic String convert(ILoggingEvent event) {RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();if (requestAttributes == null) {return "127.0.0.1";}HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();String requestIP = IpUtils.getIpAddr(request);return requestIP;}
}

UUIDConfig.java

package com.duan.config;import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;/*** @author db* @version 1.0* @description UUIDConfig* @since 2024/1/9*/
public class UUIDConfig extends ClassicConverter {@Overridepublic String convert(ILoggingEvent iLoggingEvent) {// 这里作为演示,直接生成的一个String,实际项目中可以Servlet获得用户信息return "12344556";}
}

工具类代码如下:

package com.duan.utils;import com.google.common.base.Strings;import javax.servlet.http.HttpServletRequest;// 请求IP
public class IpUtils {private IpUtils(){}public static String getIpAddr(HttpServletRequest request) {String xIp = request.getHeader("X-Real-IP");String xFor = request.getHeader("X-Forwarded-For");if (!Strings.isNullOrEmpty(xFor) && !"unKnown".equalsIgnoreCase(xFor)) {//多次反向代理后会有多个ip值,第一个ip才是真实ipint index = xFor.indexOf(",");if (index != -1) {return xFor.substring(0, index);} else {return xFor;}}xFor = xIp;if (!Strings.isNullOrEmpty(xFor) && !"unKnown".equalsIgnoreCase(xFor)) {return xFor;}if (Strings.nullToEmpty(xFor).trim().isEmpty() || "unknown".equalsIgnoreCase(xFor)) {xFor = request.getHeader("Proxy-Client-IP");}if (Strings.nullToEmpty(xFor).trim().isEmpty() || "unknown".equalsIgnoreCase(xFor)) {xFor = request.getHeader("WL-Proxy-Client-IP");}if (Strings.nullToEmpty(xFor).trim().isEmpty() || "unknown".equalsIgnoreCase(xFor)) {xFor = request.getHeader("HTTP_CLIENT_IP");}if (Strings.nullToEmpty(xFor).trim().isEmpty() || "unknown".equalsIgnoreCase(xFor)) {xFor = request.getHeader("HTTP_X_FORWARDED_FOR");}if (Strings.nullToEmpty(xFor).trim().isEmpty() || "unknown".equalsIgnoreCase(xFor)) {xFor = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(xFor) ? "127.0.0.1" : xFor;}}
package com.duan.utils;import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;// 获得主机IP
public class LocalIP {public static InetAddress getLocalHostExactAddress() {try {InetAddress candidateAddress = null;// 从网卡中获取IPEnumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();while (networkInterfaces.hasMoreElements()) {NetworkInterface iface = networkInterfaces.nextElement(); // 该网卡接口下的ip会有多个,也需要一个个的遍历,找到自己所需要的for (Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {InetAddress inetAddr = inetAddrs.nextElement();// 排除loopback回环类型地址(不管是IPv4还是IPv6 只要是回环地址都会返回true)if (!inetAddr.isLoopbackAddress()) {if (inetAddr.isSiteLocalAddress()) {// 如果是site-local地址,就是它了 就是我们要找的// ~~~~~~~~~~~~~绝大部分情况下都会在此处返回你的ip地址值~~~~~~~~~~~~~return inetAddr;}// 若不是site-local地址 那就记录下该地址当作候选if (candidateAddress == null) {candidateAddress = inetAddr;}}}}// 如果出去loopback回环地之外无其它地址了,那就回退到原始方案吧return candidateAddress == null ? InetAddress.getLocalHost() : candidateAddress;} catch (Exception e) {e.printStackTrace();}return null;}public static String getIpAddress() {try {//从网卡中获取IPEnumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();InetAddress ip;while (allNetInterfaces.hasMoreElements()) {NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();//用于排除回送接口,非虚拟网卡,未在使用中的网络接口if (!netInterface.isLoopback() && !netInterface.isVirtual() && netInterface.isUp()) {//返回和网络接口绑定的所有IP地址Enumeration<InetAddress> addresses = netInterface.getInetAddresses();while (addresses.hasMoreElements()) {ip = addresses.nextElement();if (ip instanceof Inet4Address) {return ip.getHostAddress();}}}}} catch (Exception e) {System.err.println("IP地址获取失败" + e.toString());}return "";}
}

traceId :用于标识摸一次具体的请求 Id,通过 traceId 可以把一次用户请求在系统中的调用路径串联起来。

logback 自定义日志格式 traceId 使用 MDC 进行实现。

MDC(Mapped Diagnostic Context) 映射诊断环境,是 log4jlogback 提供的一种方便在线多线程条件下记录日志的功能,可以看成是一个与当前线程绑定的 ThreadLocal


public class MDC {// 添加 key-valuepublic static void put(String key, String val) {...}// 根据 key 获取 valuepublic static String get(String key) {...}// 根据 key 删除映射public static void remove(String key) {...}// 清空public static void clear() {...}
}

用拦截器或者过滤器实现 MDC,在这里使用拦截器实现,首先在 interceptor 包中创建 TraceInterceptor 类并实现 HandlerInterceptor 方法。


package com.duan.interceptor;import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;/*** @author db* @version 1.0* @description TraceInterceptor* @since 2024/1/9*/
@Component
public class TraceInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {MDC.put("traceid", UUID.randomUUID().toString());return true;}@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Object handler,Exception e) throws Exception {MDC.remove("traceid");}
}

config 包中新建 WebConfig 类并继承 WebMvcConfigurerAdapter,把 TraceInterceptor 拦截器注入。


package com.duan.config;import com.duan.interceptor.TraceInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;/*** @author db* @version 1.0* @description WebConfig* @since 2024/1/9*/
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {@Autowiredprivate TraceInterceptor traceInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(traceInterceptor);}
}

第二步,在 logback-spring.xml 配置文件中进行配置,配置文件如下:

<?xml version="1.0" encoding="UTF-8"?><!-- logback默认每60秒扫描该文件一次,如果有变动则用变动后的配置文件。 -->
<configuration scan="false"><!-- ==============================================开发环境=========================================== --><springProfile name="dev"><conversionRule conversionWord="hostIp" converterClass="com.duan.config.HostIpConfig"/><conversionRule conversionWord="requestIp" converterClass="com.duan.config.RequestIpConfig"/><conversionRule conversionWord="uuid" converterClass="com.duan.config.UUIDConfig"/><property name="CONSOLE_LOG_PATTERN"value="%yellow(%date{yyyy-MM-dd HH:mm:ss.SSS})----[%magenta(cxykk)|%magenta(%hostIp)|%magenta(%requestIp)|%magenta(%uuid)|%magenta(%X{traceid})]----%cyan(%msg%n)"/><!-- 控制台输出 --><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出--><pattern>${CONSOLE_LOG_PATTERN}</pattern></encoder></appender><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="STDOUT"/></root></springProfile><!-- ==============================================生产环境=========================================== --><springProfile name="prod"><!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--><property name="LOG_HOME" value="./log"/><!-- 控制台输出 --><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder></appender><!-- 按照每天生成日志文件 --><appender name="INFO_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"><!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。--><file>${LOG_HOME}/info.log</file><!--滚动策略,按照大小时间滚动 SizeAndTimeBasedRollingPolicy--><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${LOG_HOME}/info.%d{yyyy-MM-dd}.%i.log</FileNamePattern><!--只保留最近30天的日志--><MaxHistory>30</MaxHistory><!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志--><totalSizeCap>1GB</totalSizeCap><MaxFileSize>10MB</MaxFileSize></rollingPolicy><!--日志输出编码格式化--><encoder><charset>UTF-8</charset><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder><!--过滤器,只有过滤到指定级别的日志信息才会输出,如果level为ERROR,那么控制台只会输出ERROR日志--><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>INFO</level></filter></appender><!-- 按照每天生成日志文件 --><appender name="ERROR_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_HOME}/error.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern><MaxHistory>30</MaxHistory><!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志--><totalSizeCap>1GB</totalSizeCap><MaxFileSize>10MB</MaxFileSize></rollingPolicy><encoder><charset>UTF-8</charset><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>ERROR</level></filter></appender><!--指定最基础的日志输出级别--><root level="INFO"><!--appender将会添加到这个loger--><appender-ref ref="STDOUT"/><appender-ref ref="INFO_APPENDER"/><appender-ref ref="ERROR_APPENDER"/></root></springProfile>
</configuration>

启动项目,通过 postman 调用 login 接口,查看结果输出日志格式。

代码地址:https://gitee.com/duan138/practice-code/tree/dev/logback

三、总结

SpringBoot 中日志讲解就到这里,上面提到的知识点都是项目中常用的,比如日志怎么配置、根据日志级别把日志输出到不同的文件里、或者将 INFOERROR 级别的日志输出到同一个文件中、或者定制日志格式等等。

下篇文章将学习 spring 事务,后续的文章会使用 AOP 或者拦截器描述在实际项目中怎么去记录日志。


改变你能改变的,接受你不能改变的,关注公众号:程序员康康,一起成长,共同进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/443137.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java之mybatis入门

大前题 正确创建好了springboot工程&#xff0c;极其依赖 配置数据库连接 application.yml spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/cangqiongusername: rootpassword: rootlombok Data 自动生成代码&#xff08…

完整地实现日期类(分3个文件+测试结果)

&#x1f308;完整地实现日期类 &#x1f47b;主要使用了构造函数、运算符重载函数。 &#x1f47b;以下日期类包含接收日期、日期与日期减加减、日期与天数间加减、日期自增减&#xff08;自动加减一天&#xff09;、日期间大小比较等功能。 ☀️一、分析与优化各函数实现 …

【论文复现】Conditional Generative Adversarial Nets(CGAN)

文章目录 GAN基础理论2.1 算法来源2.2 算法介绍2.3 基于CGAN的手写数字生成实验2.3.1 网络结构2.3.2 训练过程一、 D的loss (discriminator_train_step)二、 G的loss (generator_train_step) 2.4 实验分析2.4.1 超参数调整一、batch size二、 epochs三、 Adam&#xff1a;learn…

java+springboot企业员工工作日志审批管理系统ssm+vue

企业OA管理系统具有管理员角色&#xff0c;用户角色&#xff0c;这两个操作权限。 ①管理员 管理员在企业OA管理系统里面查看并管理人事信息&#xff0c;工作审批信息&#xff0c;部门信息&#xff0c;通知公告信息以及内部邮件信息。 管理员功能结构图如下&#xff1a; ide工具…

isctf---web

圣杯战争 php反序列 ?payloadO:6:"summon":2:{s:5:"Saber";O:8:"artifact":2:{s:10:"excalibuer";O:7:"prepare":1:{s:7:"release";O:5:"saber":1:{s:6:"weapon";s:52:"php://filter…

1 月 30 日算法练习-思维和贪心

文章目录 重复字符串翻硬币乘积最大 重复字符串 思路&#xff1a;判断是否能整除&#xff0c;如果不能整除直接退出&#xff0c;能整除每次从每组对应位置中找出出现最多的字母将其他值修改为它&#xff0c;所有修改次数即为答案。 #include<iostream> using namespace …

【JS逆向实战-入门篇】某gov网站加密参数分析与Python算法还原

文章目录 1. 写在前面2. 请求分析3. 断点分析4. 算法还原 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向感兴趣的朋…

如何对Ajax请求进行封装操作,解决跨域问题的方法,如何使用core解决跨域

目录 1.Ajax原理 2.为什么要封装 3.如何进行封装 4.如何请求 5.如何解决Ajax跨域问题 6.使用CORS解决Ajax跨域问题 1.服务端 2.客户端 1.Ajax原理 Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;是一种通过在后台与服务器进行少量数据交换&…

微信小程序(二十八)网络请求数据进行列表渲染

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.GET请求的规范 2.数据赋值的方法 源码&#xff1a; index.wxml <!-- 列表渲染基础写法&#xff0c;不明白的看上一篇 --> <view class"students"><view class"item">&…

Shell脚本之 -------------免交互操作

一、Here Document 1.Here Document概述 Here Document 使用I/O重定向的方式将命令列表提供给交互式程序 Here Document 是标准输 入的一种替代品&#xff0c;可以帮助脚本开发人员不必使用临时文件来构建输入信息&#xff0c;而是直接就地 生产出一个文件并用作命令的标准…

排序链表---归并--链表OJ

https://leetcode.cn/problems/sort-list/submissions/499363940/?envTypestudy-plan-v2&envIdtop-100-liked 这里我们直接进阶&#xff0c;用时间复杂度O(nlogn)&#xff0c;空间复杂度O(1)&#xff0c;来解决。 对于归并&#xff0c;如果自上而下的话&#xff0c;空间复…

Netty源码二:服务端创建NioEventLoopGroup

示例 还是拿之前启动源码的示例&#xff0c;来分析NioEventLoopGroup源码 NioEventLoopGroup构造函数 这里能看到会调到父类的MultiThread EventLoopGroup的构造方法 MultiThreadEventLoopGroup 这里我们能看到&#xff0c;如果传入的线程数目为0&#xff0c;那么就会设置2倍…