当前位置: 首页 > news >正文

springboot学习(八十) springboot中使用Log4j2记录分布式链路日志

在分布式环境中一般统一收集日志,但是在并发大时不好定位问题,大量的日志导致无法找出日志的链路关系。

可以为每一个请求分配一个traceId,记录日志时,记录此traceId,从网关开始,依次将traceId记录到请求头中,并借用log4j2的MDC功能实现traceId的打印。

1、添加traceId过滤器

其中TRACE_ID_HEADER和LOG_TRACE_ID为上下游约定好的key。

public class LogTraceIdFilter extends OncePerRequestFilter implements Constants {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {String traceId = request.getHeader(TRACE_ID_HEADER);if (StringUtils.isBlank(traceId)) {traceId = UUID.randomUUID().toString();}MDC.put(LOG_TRACE_ID, traceId);log.debug("设置traceId:{}", traceId);chain.doFilter(request, response);}
}

2、注册traceId过滤器

@AutoConfiguration
@ConditionalOnClass(LogTraceIdFilter.class)
@Slf4j
public class LogTraceIdAutoConfiguration {@Beanpublic LogTraceIdFilter logTraceIdFilter() {return new LogTraceIdFilter();}@SuppressWarnings("unchecked")@Beanpublic FilterRegistrationBean traceIdFilterRegistrationBean() {log.info("-----注册LogTraceId过滤器-------");FilterRegistrationBean frb = new FilterRegistrationBean();frb.setOrder(Ordered.HIGHEST_PRECEDENCE);frb.setFilter(logTraceIdFilter());frb.addUrlPatterns("/*");frb.setName("logTraceIdFilter");log.info("-----注册LogTraceId过滤器结束-------");return frb;}
}

将LogTraceIdAutoConfiguration全限定名 写入resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,如果是老版的springboot,写入resources/spring.factories

3、调用其他服务时传递traceId

如果需调用其他服务,需要将获取到的traceId,写入请求头的TRACE_ID_HEADER中,例如使用openfeign可统一处理,这里略。

4、log4j2的配置文件中添加打印traceId的配置

在这里插入图片描述

完整的配置文件文件如下:

<?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 个字符 --><!--如果在PATTERN中配置了%C、%M、%L、%l、%F需要在appender中将includeLocation设置为true,会影响日志性能,项目上线后根据实际情况调整--><property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%X{traceId}] %-5level [%logger{50}:%L] - %msg%n"/><property name="LOG_CONSOLE_PATTERN" value="%style{%d{ISO8601}}{bright,green} %highlight{%-5level} [%style{%t}{bright,blue}] %style{%C{}}{bright,yellow}: %msg%n%style{%throwable}{red}"/><property name="ACCESS_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} - %msg%n"/><!-- 定义日志存储的路径 --><property name="FILE_PATH" value="logs"/><property name="FILE_NAME" value="newframe"/></properties><Appenders><!--*********************控制台日志***********************--><Console name="consoleAppender" target="SYSTEM_OUT"><!--设置日志格式及颜色--><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/></Console><!--info级别日志--><!-- 这个会打印出所有的info及以上级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--><RollingFile name="infoFileAppender"fileName="${FILE_PATH}/${FILE_NAME}/log_info.log"filePattern="${FILE_PATH}/${FILE_NAME}/info/log-info-%d{yyyy-MM-dd}_%i.log.gz"append="true"><!--设置日志格式--><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/><Filters><!--过滤掉warn及更高级别日志--><ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL" /><ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/></Filters><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><!--warn级别日志--><!-- 这个会打印出所有的warn及以上级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--><RollingFile name="warnFileAppender"fileName="${FILE_PATH}/${FILE_NAME}/log_warn.log"filePattern="${FILE_PATH}/${FILE_NAME}/warn/log-warn-%d{yyyy-MM-dd}_%i.log.gz"append="true"><!--设置日志格式--><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/><Filters><!--过滤掉error及更高级别日志--><ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL" /><ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/></Filters><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><!--error级别日志--><!-- 这个会打印出所有的error及以上级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--><RollingFile name="errorFileAppender"fileName="${FILE_PATH}/${FILE_NAME}/log_error.log"filePattern="${FILE_PATH}/${FILE_NAME}/error/log-error-%d{yyyy-MM-dd}_%i.log.gz"append="true"><!--设置日志格式--><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/><Filters><ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/></Filters><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><!--访问日志--><RollingFile name="accessAppender"fileName="${FILE_PATH}/${FILE_NAME}/log_access.log"filePattern="${FILE_PATH}/${FILE_NAME}/access/log-access-%d{yyyy-MM-dd}_%i.log.gz"append="true"><!--设置日志格式--><PatternLayout pattern="${ACCESS_LOG_PATTERN}" charset="UTF-8"/><Filters><ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/></Filters><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><!--<Async name="Async" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></Async>--></Appenders><Loggers><AsyncLogger name="org.apache.http" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="io.lettuce" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="io.netty" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.quartz" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.springframework" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.springdoc" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="druid.sql" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="io.undertow" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="sun.rmi" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.sun.mail" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="javax.management" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="de.codecentric" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.hibernate.validator" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.mybatis.spring.mapper" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.xnio" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="springfox" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.baomidou" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="io.micrometer.core" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="Validator" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.neo4j" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.apache.zookeeper" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.apache.curator" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="oshi.util" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="net.javacrumbs" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.atomikos" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.springframework.boot.actuate.mail.MailHealthIndicator" level="error" includeLocation="true" additivity="false"><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.flowable" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.apache.ibatis.transaction" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.iscas.base.biz.schedule" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.sun.jna.Native" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="io.swagger" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.sun.jna.NativeLibrary" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.alibaba.druid.pool.PreparedStatementPool" level="info" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="com.iscas.biz.config.log.AccessLogInterceptor" level="debug" includeLocation="true" additivity="false"><AppenderRef ref="accessAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncLogger name="org.apache.http.impl.execchain.RetryExec" level="error" includeLocation="true" additivity="false"><AppenderRef ref="errorFileAppender"/></AsyncLogger><AsyncLogger name="accessLogger" level="debug" includeLocation="true" additivity="false"><AppenderRef ref="accessAppender"/><AppenderRef ref="consoleAppender"/></AsyncLogger><AsyncRoot level="debug" includeLocation="true" additivity="false"><AppenderRef ref="infoFileAppender"/><AppenderRef ref="warnFileAppender"/><AppenderRef ref="errorFileAppender"/><AppenderRef ref="consoleAppender"/></AsyncRoot></Loggers>
</configuration>

5、添加阿里巴巴transmittable依赖

为解决调用子线程后,无法打印traceId的问题,引用transmittable依赖
gradle:

// https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local
implementation group: 'com.alibaba', name: 'transmittable-thread-local', version: '2.14.2'

maven:

<!-- https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local -->
<dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId><version>2.14.2</version>
</dependency>

6、自定义ThreadContextMap

package org.slf4j;import com.alibaba.ttl.TransmittableThreadLocal;
import org.apache.logging.log4j.spi.ThreadContextMap;import java.util.Collections;
import java.util.HashMap;
import java.util.Map;public class TtlThreadContextMap implements ThreadContextMap {private final ThreadLocal<Map<String, String>> localMap;public TtlThreadContextMap() {this.localMap = new TransmittableThreadLocal<Map<String, String>>();}@Overridepublic void put(final String key, final String value) {Map<String, String> map = localMap.get();map = map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);map.put(key, value);localMap.set(Collections.unmodifiableMap(map));}@Overridepublic String get(final String key) {final Map<String, String> map = localMap.get();return map == null ? null : map.get(key);}@Overridepublic void remove(final String key) {final Map<String, String> map = localMap.get();if (map != null) {final Map<String, String> copy = new HashMap<String, String>(map);copy.remove(key);localMap.set(Collections.unmodifiableMap(copy));}}@Overridepublic void clear() {localMap.remove();}@Overridepublic boolean containsKey(final String key) {final Map<String, String> map = localMap.get();return map != null && map.containsKey(key);}@Overridepublic Map<String, String> getCopy() {final Map<String, String> map = localMap.get();return map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);}@Overridepublic Map<String, String> getImmutableMapOrNull() {return localMap.get();}@Overridepublic boolean isEmpty() {final Map<String, String> map = localMap.get();return map == null || map.size() == 0;}@Overridepublic String toString() {final Map<String, String> map = localMap.get();return map == null ? "{}" : map.toString();}@Overridepublic int hashCode() {final int prime = 31;int result = 1;final Map<String, String> map = this.localMap.get();result = prime * result + ((map == null) ? 0 : map.hashCode());return result;}@Overridepublic boolean equals(final Object obj) {if (this == obj) {return true;}if (obj == null) {return false;}if (!(obj instanceof TtlThreadContextMap)) {return false;}final TtlThreadContextMap other = (TtlThreadContextMap) obj;final Map<String, String> map = this.localMap.get();final Map<String, String> otherMap = other.getImmutableMapOrNull();if (map == null) {if (otherMap != null) {return false;}} else if (!map.equals(otherMap)) {return false;}return true;}
}

7、注册ThreadContextMap

在resources下添加log4j2.component.properties文件,文件中配置自定义的ThreadContextMap

log4j2.threadContextMap=org.slf4j.TtlThreadContextMap

8、自定义MDCAdapter

注意这里包名最好使用org.slf4j,否则访问MDC中的mdcAdapter属性需要使用反射。

package org.slf4j;import com.alibaba.ttl.TransmittableThreadLocal;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.status.StatusLogger;
import org.slf4j.spi.MDCAdapter;import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.Objects;/*** @author zhuquanwen* @version 1.0* @date 2023/2/12 11:46*/
public class TtlMDCAdapter implements MDCAdapter {private static Logger LOGGER = StatusLogger.getLogger();private final ThreadLocalMapOfStacks mapOfStacks = new ThreadLocalMapOfStacks();private static TtlMDCAdapter mtcMDCAdapter;static {mtcMDCAdapter = new TtlMDCAdapter();MDC.mdcAdapter = mtcMDCAdapter;}public static MDCAdapter getInstance() {return mtcMDCAdapter;}@Overridepublic void put(final String key, final String val) {ThreadContext.put(key, val);}@Overridepublic String get(final String key) {return ThreadContext.get(key);}@Overridepublic void remove(final String key) {ThreadContext.remove(key);}@Overridepublic void clear() {ThreadContext.clearMap();}@Overridepublic Map<String, String> getCopyOfContextMap() {return ThreadContext.getContext();}@Overridepublic void setContextMap(final Map<String, String> map) {ThreadContext.clearMap();ThreadContext.putAll(map);}@Overridepublic void pushByKey(String key, String value) {if (key == null) {ThreadContext.push(value);} else {final String oldValue = mapOfStacks.peekByKey(key);if (!Objects.equals(ThreadContext.get(key), oldValue)) {LOGGER.warn("The key {} was used in both the string and stack-valued MDC.", key);}mapOfStacks.pushByKey(key, value);ThreadContext.put(key, value);}}@Overridepublic String popByKey(String key) {if (key == null) {return ThreadContext.getDepth() > 0 ? ThreadContext.pop() : null;}final String value = mapOfStacks.popByKey(key);if (!Objects.equals(ThreadContext.get(key), value)) {LOGGER.warn("The key {} was used in both the string and stack-valued MDC.", key);}ThreadContext.put(key, mapOfStacks.peekByKey(key));return value;}@Overridepublic Deque<String> getCopyOfDequeByKey(String key) {if (key == null) {final ThreadContext.ContextStack stack = ThreadContext.getImmutableStack();final Deque<String> copy = new ArrayDeque<>(stack.size());stack.forEach(copy::push);return copy;}return mapOfStacks.getCopyOfDequeByKey(key);}@Overridepublic void clearDequeByKey(String key) {if (key == null) {ThreadContext.clearStack();} else {mapOfStacks.clearByKey(key);ThreadContext.put(key, null);}}private static class ThreadLocalMapOfStacks {private final ThreadLocal<Map<String, Deque<String>>> tlMapOfStacks = new TransmittableThreadLocal<>();public void pushByKey(String key, String value) {tlMapOfStacks.get().computeIfAbsent(key, ignored -> new ArrayDeque<>()).push(value);}public String popByKey(String key) {final Deque<String> deque = tlMapOfStacks.get().get(key);return deque != null ? deque.poll() : null;}public Deque<String> getCopyOfDequeByKey(String key) {final Deque<String> deque = tlMapOfStacks.get().get(key);return deque != null ? new ArrayDeque<>(deque) : null;}public void clearByKey(String key) {final Deque<String> deque = tlMapOfStacks.get().get(key);if (deque != null) {deque.clear();}}public String peekByKey(String key) {final Deque<String> deque = tlMapOfStacks.get().get(key);return deque != null ? deque.peek() : null;}}
}

9、初始化MDCAdapter

public class TtlMDCAdapterInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {//加载TtlMDCAdapter实例TtlMDCAdapter.getInstance();}
}

在resources下spring.factories中注册:

org.springframework.context.ApplicationContextInitializer=\
com.iscas.base.biz.config.TtlMDCAdapterInitializer

10、自定义线程池

package com.iscas.base.biz.schedule;import com.alibaba.ttl.TtlCallable;
import com.alibaba.ttl.TtlRunnable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;import java.util.concurrent.Callable;
import java.util.concurrent.Future;/*** @author zhuquanwen* @version 1.0* @date 2023/2/12 12:11*/
public class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {@Overridepublic void execute(Runnable runnable) {Runnable ttlRunnable = TtlRunnable.get(runnable);super.execute(ttlRunnable);}@Overridepublic <T> Future<T> submit(Callable<T> task) {Callable ttlCallable = TtlCallable.get(task);return super.submit(ttlCallable);}@Overridepublic Future<?> submit(Runnable task) {Runnable ttlRunnable = TtlRunnable.get(task);return super.submit(ttlRunnable);}@Overridepublic ListenableFuture<?> submitListenable(Runnable task) {Runnable ttlRunnable = TtlRunnable.get(task);return super.submitListenable(ttlRunnable);}@Overridepublic <T> ListenableFuture<T> submitListenable(Callable<T> task) {Callable ttlCallable = TtlCallable.get(task);return super.submitListenable(ttlCallable);}
}

11、注册线程池

package com.iscas.biz.config;import com.iscas.base.biz.schedule.CustomThreadPoolTaskExecutor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.ThreadPoolExecutor;/**** @author zhuquanwen* @version 1.0* @date 2021/2/26 10:41* @since jdk1.8*/
@SuppressWarnings("unused")
@AutoConfiguration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer, BizConstant {private static final int ASYNC_KEEPALIVE_SECONDS = 60;private static final int  ASYNC_QUEUE_CAPACITY = 20000;@Bean("asyncExecutor")public ThreadPoolTaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new CustomThreadPoolTaskExecutor();executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);executor.setQueueCapacity(20000);executor.setKeepAliveSeconds(ASYNC_KEEPALIVE_SECONDS);executor.setThreadNamePrefix(ASYNC_EXECUTOR_NAME_PREFIX);executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}}

12、编写示例

package com.iscas.base.biz.test.controller;import com.iscas.base.biz.util.LogLevelUtils;
import com.iscas.base.biz.util.SpringUtils;
import com.iscas.templet.common.ResponseEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.logging.LogLevel;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author zhuquanwen* @version 1.0* @date 2023/2/11 17:51*/
@RestController
@RequestMapping("/test/log")
@Slf4j
public class LogControllerTest {@GetMappingpublic ResponseEntity t1 () {log.debug("test-debug");return ResponseEntity.ok(null);}/*** 调整日志级别* */@GetMapping("/t2")public ResponseEntity t2 () {LogLevelUtils.updateLevel("com.iscas.base.biz.test.controller", LogLevel.INFO);return ResponseEntity.ok(null);}/*** 测试子线程打印traceId* */@GetMapping("/t3")public ResponseEntity t3() {ThreadPoolTaskExecutor threadPoolTaskExecutor = SpringUtils.getBean("asyncExecutor");threadPoolTaskExecutor.execute(() -> {log.info("测试子线程打印traceId");});return ResponseEntity.ok(null);}}

日志打印如下:

在这里插入图片描述

相关文章:

springboot学习(八十) springboot中使用Log4j2记录分布式链路日志

在分布式环境中一般统一收集日志&#xff0c;但是在并发大时不好定位问题&#xff0c;大量的日志导致无法找出日志的链路关系。 可以为每一个请求分配一个traceId&#xff0c;记录日志时&#xff0c;记录此traceId&#xff0c;从网关开始&#xff0c;依次将traceId记录到请求头…...

10种ADC软件滤波方法及程序

10种ADC软件滤波方法及程序一、10种ADC软件滤波方法1、限幅滤波法&#xff08;又称程序判断滤波法&#xff09;2、中位值滤波法3、算术平均滤波法4、递推平均滤波法&#xff08;又称滑动平均滤波法&#xff09;5、中位值平均滤波法&#xff08;又称防脉冲干扰平均滤波法&#x…...

第五章:Windows server加域

加入AD域&#xff1a;教学视频&#xff1a;https://www.bilibili.com/video/BV1xM4y1D7oL/?spm_id_from333.999.0.0首先我们选择一个干净的&#xff0c;也就是新建的没动过的Windows server虚拟机。我们将DNS改成域的ip地址&#xff0c;还要保证它们之间能ping的通&#xff0c…...

Elasticsearch:获取 nested 类型数组中的所有元素

在我之前的文章 “Elasticsearch: object 及 nested 数据类型” 对 nested 数据类型做了一个比较详细的介绍。在实际使用中&#xff0c;你在构建查询时肯定会遇到一些问题。根据官方文档介绍&#xff0c;nested 类型字段在隐藏数组中索引其每个项目&#xff0c;这允许独立于索引…...

English Learning - Day53 作业打卡 2023.2.7 周二

English Learning - Day53 作业打卡 2023.2.7 周二引言1. 我必须承认&#xff0c;我之前学习没你用功。have to VS must2. 这跟我想得一样简单。3. 生活并不像它看上去那么顺风顺水&#xff0c;但也不会像我们想象得那么难。Look VS seem4. 你比去年高多了。5. 你关心你的工作胜…...

SpringMVC--注解配置SpringMVC、SpringMVC执行流程

注解配置SpringMVC 使用配置类和注解代替web.xml和SpringMVC配置文件的功能 创建初始化类&#xff0c;代替web.xml 在Servlet3.0环境中&#xff0c;容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类&#xff0c; 如果找到的话就用它来配置Servle…...

JavaScript中数组常用的方法

文章目录前言常用数组方法1、 join( )2、push&#xff08;&#xff09;与 pop&#xff08;&#xff09;3、shift&#xff08;&#xff09;与 unshift&#xff08;&#xff09;4、sort&#xff08;&#xff09;5、reverse&#xff08;&#xff09;6、slice&#xff08;&#xff…...

ModuleNotFoundError: No module named ‘pip‘

项目场景&#xff1a;pip 错误 Traceback (most recent call last): File "E:\KaiFa\Python\Python38\lib\runpy.py", line 194, in _run_module_as_main return _run_code(code, main_globals, None, File "E:\KaiFa\Python\Python38\lib\runpy.py&qu…...

ROS2 入门应用 发布和订阅(C++)

ROS2 入门应用 发布和订阅&#xff08;C&#xff09;1. 创建功能包2. 创建源文件2.1. 话题发布2.2. 话题订阅3. 添加依赖关系4. 添加编译信息4.1. 添加搜索库4.2. 增加可执行文件4.3. 增加可执行文件位置5. 编译和运行1. 创建功能包 在《ROS2 入门应用 工作空间》中已创建和加…...

XSS漏洞,通过XSS实现网页挂马

**今天讲下通过XSS实现网页挂马~*&#xff0c;目的是了解安全方面知识&#xff0c;提升生活网络中辨别度 原理&#xff1a; 实验分为两部分&#xff1a; 1、通过Kali linux&#xff0c;利用MS14_064漏洞&#xff0c;制作一个木马服务器。存在该漏洞的用户一旦通过浏览器访问木…...

家政服务小程序实战教程09-图文卡片

小程序还有一类需求就是展示服务的列表&#xff0c;我们这里用图文卡片组件来实现&#xff0c;我们先要添加一个标题&#xff0c;使用网格布局来实现 第一列添加一个文本组件&#xff0c;第二列添加一个图标组件 修改文本组件的文本内容&#xff0c;设置外边距 设置第二列的样式…...

国内唯一一部在CentOS下正确编译安装和使用RediSearch的教程

开篇 Redis6开始增加了诸多激动人心的模块&#xff0c;特别是&#xff1a;RedisJSON和RediSearch。这两个模块已经完全成熟了。它们可以直接使用我们的生产上的Redis服务器来做全文搜索&#xff08;二级搜索&#xff09;以取得更廉价的硬件成本、同时在效率上竟然超过了Elastic…...

前端对于深拷贝和浅拷贝的应用和思考

浅拷贝 浅拷贝 &#xff1a; 浅拷贝是指对基本类型的值拷贝&#xff0c;以及对对象类型的地址拷贝。它是将数据中所有的数据引用下来&#xff0c;依旧指向同一个存放地址&#xff0c;拷贝之后的数据修改之后&#xff0c;也会影响到原数据的中的对象数据。最简单直接的浅拷贝就…...

Java基础常见面试题(三)

String 字符型常量和字符串常量的区别&#xff1f; 形式上: 字符常量是单引号引起的一个字符&#xff0c;字符串常量是双引号引起的若干个字符&#xff1b; 含义上: 字符常量相当于一个整型值( ASCII 值)&#xff0c;可以参加表达式运算&#xff1b;字符串常量代表一个地址值…...

C++设计模式(13)——装饰模式

亦称&#xff1a; 装饰者模式、装饰器模式、Wrapper、Decorator 意图 装饰模式是一种结构型设计模式&#xff0c; 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 问题 假设你正在开发一个提供通知功能的库&#xff0c; 其他程序可使用它向用户发…...

ESP-01S通过AT指令上报数据到阿里云物模型

ESP-01S使用AT指令上报数据到阿里云物模型 上篇文章介绍了如何用AT指令连接阿里云并进行通信&#xff1a;https://blog.csdn.net/weixin_46251230/article/details/128995530 但最终需要将传感器数据上报到云平台显示&#xff0c;所以需要建立阿里云物模型 阿里云平台建立物…...

【强化学习】马尔可夫决策过程MDP

1.马尔可夫决策过程MDP 1.1 MDP五元组 MDP<S,A,P,R,γ>MDP<\mathcal{S},\mathcal{A},\mathcal{P},\mathcal{R},\mathcal{\gamma}>MDP<S,A,P,R,γ>&#xff0c;其中&#xff1a; S\mathcal{S}S&#xff1a;状态空间A\mathcal{A}A&#xff1a;动作空间P\mathc…...

刘润:五维思考,让你站得更高、看得更远

原标题&#xff1a;刘润&#xff1a;五维思考&#xff0c;让你站得更高、看得更远 前言&#xff1a;遇到问题时&#xff0c;有的人很快就能想明白&#xff0c;有的人需要很久才能想明白&#xff0c;还有的人始终都想不明白。 而且&#xff0c;那些很快就能想明白的人&#xff0…...

从运维角度看微服务 k8s部署微服务【偏理论】【AL】

从运维角度看微服务 & 部署微服务【偏理论】 1、微服务的特点 服务组件化&#xff1a; 每个服务独立开发、部署&#xff0c;有效避免一个服务的修改引起整个系统重新部署。 技术栈灵活&#xff1a; 约定通信方式&#xff0c;使得服务本身功能实现对技术要求不再那么敏感。…...

专题 | 防抖和节流

一 防抖&#xff1a;单位时间内&#xff0c;频繁触发事件&#xff0c;只执行最后一次 场景&#xff1a;搜索框搜索输入&#xff08;利用定时器&#xff0c;每次触发先清掉以前的定时器&#xff0c;从新开始&#xff09; 节流&#xff1a;单位时间内&#xff0c;频繁触发事件&…...

C++入门:重载运算符和重载函数

C 允许在同一作用域中的某个函数和运算符指定多个定义&#xff0c;分别称为函数重载和运算符重载。重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明&#xff0c;但是它们的参数列表和定义&#xff08;实现&#xff09;不相同。当您调用一个重载函…...

conda 新建虚拟环境 等等

1&#xff1a;conda create -n env_name package_name #创建名为env_name的新环境&#xff0c;并在该环境下安装名为package_name 的包&#xff0c;例如&#xff1a;conda create -n Arg python3.8 # 创建名字为Arg python为3.8版本的虚拟环境2&#xff1a; conda activate env…...

【C++:STL之栈和队列 | 模拟实现 | 优先级队列 】

目录 1. stack的介绍和使用 1.1 stack的介绍 1.2 stack的使用 2 栈的模拟实现 3 queue的介绍和使用 3.1 queue的介绍 3.2 queue的使用 4 queue的模拟实现 5 deque的介绍 5.1deque的原理介绍 5.2 deque的缺陷 5.3 为什么选择deque作为stack和queue的底层默认容器 6 p…...

基于SpringBoot+Vue的疫苗预约管理系统(Java项目)

【辰兮要努力】&#xff1a;hello你好我是辰兮&#xff0c;很高兴你能来阅读&#xff0c;昵称是希望自己能不断精进&#xff0c;向着优秀程序员前行&#xff01; 博客来源于项目以及编程中遇到的问题总结&#xff0c;偶尔会有读书分享&#xff0c;我会陆续更新Java前端、后台、…...

华为OD机试 - 计算网络信号(Python),真题含思路

计算网络信号 题目 网络信号经过传递会逐层衰减,且遇到阻隔物无法直接穿透,在此情况下需要计算某个位置的网络信号值。 注意:网络信号可以绕过阻隔物 array[m][n] 的二维数组代表网格地图,array[i][j] = 0 代表 i 行 j 列是空旷位置;array[i][j] = x ( x 为正整数)代表 i …...

【Spring】注解实现IOC操作,你理解了吗?

作者&#xff1a;狮子也疯狂 专栏&#xff1a;《spring开发》 坚持做好每一步&#xff0c;幸运之神自然会驾凌在你的身上 专栏推荐&#xff1a;写文章刚刚起步&#xff0c;各个专栏的知识点后续会补充完善&#xff0c;不断更新好文&#xff0c;希望大 家支持一下。 专栏名字El…...

微搭低代码从入门到精通01-总体介绍

在过去我们开发小程序&#xff0c;要学习各类知识。比如前端知识、后端知识、服务器知识及各种中间件及数据库的知识。 要想学会这些知识&#xff0c;既需要投入大量的学习时间&#xff0c;而且要经过相当的实践才可以掌握。 如果立志从事开发行业&#xff0c;投入精力去学习…...

类的继承

类的继承&#xff1a;一个类继承另一个类&#xff0c;自动拥有这个类的属性和方法&#xff0c;类似于包含与被包含的关系。被继承的类称为父类--子类则是继承父类的类。一个父类可以有多个子类&#xff1b;一个子类可以有多个父类&#xff08;多继承&#xff09;问题创建子类时…...

应用场景一:西门子PLC通过桥接器连接MQTT服务器

应用场景描述&#xff1a; 云平台、MES等数据采集、设备管理系统&#xff0c;需要通过MQTT的方式&#xff0c;上传和下发数据&#xff0c;MQTT服务器可以获取PLC的实时状态数据&#xff0c;也可以下发控制指令。桥接器提供4G、WIFI和有线三种连接方式。 网络拓扑&#xff1a;…...

计算机组成原理(四)

1.理解存储器的分类方法&#xff1b;理解存储器的层次结构&#xff1b;熟悉存储器的几个技术指标&#xff08;主要是存储容量、存取时间、存取周期、存储器带宽等&#xff09;&#xff1b; 存储器分类方法&#xff1a;   按与CPU的连接和功能分类&#xff1a;     主存储…...

做淘宝内部优惠券网站要钱么/百度关键词工具入口

Hello, I’m Shendi 最近小程序要接入微信的内容安全方面 这里记录一下 获取 Token 封装 首先这两个接口都需要使用到 Token 这里我直接贴上代码&#xff0c;其中的参数都是直接追加在 url 上 JSON方面使用的是 fastjson /** * 判断返回的数据是否为错误数据.* param json…...

克拉玛依商城网站建设平台/seo助理

http协议是我们日常都会接触的&#xff0c;在python中&#xff0c;有大量的模块供我们使用来编写Web和Http客户端。 其中urllib2模块中的HTTP是最常用的协议。 既然有urllib2模块&#xff0c;也就应该有urllib模块。这两个模块都提供了同样的基本功能&#xff0c;但urllib2的扩…...

农村小伙创业做网站/网络推广服务合同范本

1. 我们不禁要问&#xff0c;什么是"服务集群"&#xff1f;什么是"企业级开发"&#xff1f; 既然说了EJB 是为了"服务集群"和"企业级开发"&#xff0c;那么&#xff0c;总得说说什么是所谓的"服务集群"和"企业级开发&q…...

网站如何收录/网红推广团队去哪里找

MATLAB同其他高级语言一致&#xff0c;有三种基本程序结构&#xff1a; 顺序结构&#xff1b;选择结构&#xff1b;循环结构 MATLAB流程控制语句主要有&#xff1a; ForWhileif-else-endswitch-case 常用命令&#xff1a; BreakContinue以及matlab特有的try命令&#xff0…...

做网站可能遇到的困难/优化营商环境条例解读

###转载于:https://www.cnblogs.com/tinaluo/p/9278671.html...

建设银行在上海的招聘网站/推广方式营销方案

20&#xff09;&#xff1a;题目&#xff1a;猴子吃桃问题&#xff1a;猴子第一天摘下若干个桃子&#xff0c;当即吃了一半&#xff0c;还不瘾&#xff0c;又多吃了一个第二天早上又将剩下的桃子吃掉一半&#xff0c;又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。…...