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

news2024/12/22 18:39:44

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

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

1、添加traceId过滤器

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

public class LogTraceIdFilter extends OncePerRequestFilter implements Constants {

    @Override
    protected 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 {
    @Bean
    public LogTraceIdFilter logTraceIdFilter() {
        return new LogTraceIdFilter();
    }

    @SuppressWarnings("unchecked")
    @Bean
    public 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>>();
    }

    @Override
    public 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));
    }

    @Override
    public String get(final String key) {
        final Map<String, String> map = localMap.get();
        return map == null ? null : map.get(key);
    }

    @Override
    public 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));
        }
    }

    @Override
    public void clear() {
        localMap.remove();
    }

    @Override
    public boolean containsKey(final String key) {
        final Map<String, String> map = localMap.get();
        return map != null && map.containsKey(key);
    }

    @Override
    public Map<String, String> getCopy() {
        final Map<String, String> map = localMap.get();
        return map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);
    }

    @Override
    public Map<String, String> getImmutableMapOrNull() {
        return localMap.get();
    }

    @Override
    public boolean isEmpty() {
        final Map<String, String> map = localMap.get();
        return map == null || map.size() == 0;
    }

    @Override
    public String toString() {
        final Map<String, String> map = localMap.get();
        return map == null ? "{}" : map.toString();
    }

    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public void put(final String key, final String val) {
        ThreadContext.put(key, val);
    }

    @Override
    public String get(final String key) {
        return ThreadContext.get(key);
    }

    @Override
    public void remove(final String key) {
        ThreadContext.remove(key);
    }

    @Override
    public void clear() {
        ThreadContext.clearMap();
    }

    @Override
    public Map<String, String> getCopyOfContextMap() {
        return ThreadContext.getContext();
    }

    @Override
    public void setContextMap(final Map<String, String> map) {
        ThreadContext.clearMap();
        ThreadContext.putAll(map);
    }

    @Override
    public 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);
        }
    }

    @Override
    public 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;
    }

    @Override
    public 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);
    }

    @Override
    public 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> {
    @Override
    public 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 {
    @Override
    public void execute(Runnable runnable) {
        Runnable ttlRunnable = TtlRunnable.get(runnable);
        super.execute(ttlRunnable);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        Callable ttlCallable = TtlCallable.get(task);
        return super.submit(ttlCallable);
    }

    @Override
    public Future<?> submit(Runnable task) {
        Runnable ttlRunnable = TtlRunnable.get(task);
        return super.submit(ttlRunnable);
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        Runnable ttlRunnable = TtlRunnable.get(task);
        return super.submitListenable(ttlRunnable);
    }

    @Override
    public <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 {

    @GetMapping
    public 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);
    }

}

日志打印如下:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C#】[带格式的字符串] 复合格式设置字符串与使用 $ 的字符串内插 | 如何格式化输出字符串

复合格式输出 string name "Fred"; String.Format("Name {0}, hours {1:hh}", name, DateTime.Now);通过指定相同的参数说明符&#xff0c;多个格式项可以引用对象列表中的同一个元素。 例如&#xff0c;通过指定“0x{0:X} {0:E} {0:N}”等复合格式字符…

凸优化学习:PART3凸优化问题(持续更新)

凸优化问题 凸优化问题的广义定义&#xff1a; 目标函数为凸函数约束集合为凸集 一、优化问题 基本用语 一般优化问题的描述&#xff1a; minimize⁡f0(x)subject to fi(x)⩽0,i1,⋯,mhi(x)0,i1,⋯,p(1)\begin{array}{ll} \operatorname{minimize} & f_0(x) \\ \text { s…

Centos7 安装Hadoop3 单机版本(伪分布式版本)

环境版本CentOS-7JDK-8Hadoop-3CentOS-7 服务器设置设置静态IP查看IP配置在/etc/sysconfig/network-scripts/目录下的ifcfg-ens33文件中。[rootHadoop3-master sbin]# cd /etc/sysconfig/network-scripts [rootHadoop3-master network-scripts]# ll 总用量 232 -rw-r--r--. 1 r…

云计算培训靠谱吗?

怎么算靠谱的培训呢&#xff1f; 举个例子&#xff1a; 我想参加云计算培训找个工作&#xff0c;机构满足了我的要求&#xff0c;有工作了&#xff0c;但是不是做云计算相关的。 小强也参加了云计算培训&#xff0c;想学好云计算成为技术大牛&#xff0c;最后专业学得普普通…

java环境配置

java环境配置步骤下载jdk安装jdk配置环境变量通过控制台命令验证配置是否成功大功告成安装教程&#xff1a; https://blog.csdn.net/m0_37220730/article/details/103585266 下载jdk 若不理解JDK/JRE/JVM的关系&#xff0c;可以点此查看初识Java&#xff08;概念、版本迭代、…

DIN解读

传统的Embedding&MLP架构将用户特征编码进一个固定长度的向量。当推出一个商品时&#xff0c;该架构无法捕捉用户丰富的历史行为中的多样性兴趣与该商品的关联。阿里妈妈团队提出了DIN网络进行改进&#xff0c;主要有如下两点创新&#xff1a; 引入注意力机制来捕捉历史行为…

【Linux下代码调试工具】gdb 的基本使用

gdb的基本使用前言准备gdb工具调试须知gdb的基本指令进入调试退出调试显示代码及函数内容运行程序给程序打断点查看断点位置断点使能取消断点逐过程调试逐语句调试运行到下一个断点查看变量的值变量值常显示取消变量值常显示前言 在主页前面的几篇文章已经介绍了Vim编辑器及Ma…

C语言(内联函数(C99)和_Noreturn)

1.内联函数 通常&#xff0c;函数调用都有一定的开销&#xff0c;因为函数的调用过程包含建立调用&#xff0c;传递参数&#xff0c;跳转到函数代码并返回。而使用宏是代码内联&#xff0c;可以避开这样的开销。 内联函数&#xff1a;使用内联diamagnetic代替函数调用。把函数…

【MySQL】 事务

&#x1f60a;&#x1f60a;作者简介&#x1f60a;&#x1f60a; &#xff1a; 大家好&#xff0c;我是南瓜籽&#xff0c;一个在校大二学生&#xff0c;我将会持续分享Java相关知识。 &#x1f389;&#x1f389;个人主页&#x1f389;&#x1f389; &#xff1a; 南瓜籽的主页…

opencv窗口的创建/显示/销毁

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…

Docker-harbor私有仓库

一、Harbor概述 1、Harbor的概念 • Harbor是VMware公司开源的企业级Docker Registry项目&#xff0c;其目标是帮助用户迅速搭建一个企业级的Docker Registry服务 • Harbor以 Docker 公司开源的Registry 为基础&#xff0c;提供了图形管理UI、基于角色的访问控制(Role Base…

力扣SQL刷题9

目录1107. 每日新用户统计-勉强579. 查询员工的累计薪水 - 各种绕易错点&#xff1a;range与rows区别615. 平均工资&#xff1a;部门与公司比较with建临时表注意点1107. 每日新用户统计-勉强 题型&#xff1a;每个日期中首次登录人数 解答&#xff1a;从原表中用按用户分组后m…

嵌入式开发----示波器入门

示波器入门前言一、示波器介绍关键指标工作原理二、功能按钮介绍三、一键入门四、 典型应用场景校准捕捉测试总线通讯总结前言 对于嵌入式工程师来说&#xff0c;示波器的使用极为重要&#xff0c;他就像是“电子工程师的眼睛”&#xff0c;把被测信号的实际波形显示在屏幕上&…

Java特性之设计模式【桥接模式】

一、桥接模式 概述 桥接&#xff08;Bridge&#xff09;是用于把抽象化与实现化解耦&#xff0c;使得二者可以独立变化。这种类型的设计模式属于结构型模式&#xff0c;它通过提供抽象化和实现化之间的桥接结构&#xff0c;来实现二者的解耦 这种模式涉及到一个作为桥接的接口…

67. 二进制求和

文章目录题目描述竖式模拟转换为十进制计算题目描述 给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 示例 1&#xff1a; 输入:a “11”, b “1” 输出&#xff1a;“100” 示例 2&#xff1a; 输入&#xff1a;a “1010”, b “1011” …

“白学”了十几年?这两年才感悟出的高效学习方法

作者&#xff1a;元青 微信公众号 「技乐书香」 引言 虽然从小学开始算起&#xff0c;一直到我现在硕士快毕业&#xff0c;已经学习了接近十九年了&#xff0c;但是只有最近两年算得上真正的在"学习"&#xff0c;之前的十七年都没有明白也没有认可学习的意义&#…

网络安全实验室7.综合关

7.综合关 1.渗透测试第一期 url&#xff1a;http://lab1.xseclab.com/base14_2d7aae2ae829d1d5f45c59e8046bbc54/ 进入忘记密码页面&#xff0c;右键查看源码&#xff0c;发现一个手机号 解题思路&#xff1a;通过给admin用户绑定13388758688手机号码&#xff0c;然后再进行…

【软件测试】企业测试面试题9道,从自我介绍到项目考察+回答......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 1、自我介绍 您好&a…

leaflet 上传WKT文件,在地图上显示图形(示例代码055)

第055个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载WKT文件,将图形显示在地图上。WKT(Well-known text)是一种文本标记语言,可以表示的几何对象包括:点,线,多边形,TIN(不规则三角网)及多面体。可以通过几何集合的方式来表示不同维度的几何对象。 …

centos学习记录

遇到的问题及其解决办法 centos7安装图形化界面 yum groupinstall ‘X Window System’ yum groupinstall -y ‘GNOME Desktop’ 安装完成后输入init 5进入图形化界面 centos7安装vmware-tools 第一步卸载open-vm-tools 输入命令 yum remove open-vm-tools 输入命令 reboot 在…