【JAVA日志:logback升级log4j2过程及注意点,增加TraceId + SkyWalking配置全链路TID日志显示】


前言

Springcloud微服务项目为了在最小的改动下,加入链路追踪TraceId,决定把日志框架从logback升级为log4j2
(以下是升级的过程和可能遇到的问题,仅以记录~)


提示:以下是本篇文章正文内容,下面案例可供参考

一、logback升级log4j2

1、添加相关log4j2的依赖

代码如下(示例):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <version>2.5.15</version>
</dependency>

2、去除logback相关依赖

logback是web自带的,所以要去除springboot-web里的logging包:

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

如果没有删除干净就会报下方的错:

在这里插入图片描述
小tip:如何查看依赖冲突

查看依赖冲突:直接在idea的plugin插件中下载maven-helper

在这里插入图片描述
(安装完成pom.xml文件下方会出现Dependency Analyzer,点击会出现当前pom文件里的所有引用依赖,冲突的话会标蓝/绿异常)

3、log4j2.xml相关配置

{[TraceId:%X{trace_id}]}:打印TraceId的日志配置

代码如下(示例):

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<!--log4j2.xml改成log4j2-spring.xml交由spring加载-->
<configuration monitorInterval="5">
    <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

    <!--变量配置-->
    <Properties>
        <!-- 定义日志输出的根目录 -->
        <Property name="LOG_PATH">./all-logs</Property>
        <property name="LOG_FILE">product-server</property>
        <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
        <!-- %style,%highlight:彩色控制台输出    %logger{36} 表示 Logger 名字最长36个字符 -->
        <property name="LOG_PATTERN"  value="%d{yyyy-MM-dd HH:mm:ss.SSS} %style{%-5level}{bright,magenta} [%thread] %style{[TraceId:%X{trace_id}]}{bright,Cyan} %logger{36} - %msg%n" />
    </Properties>

    <appenders>

        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式  disableAnsi="false"为了开启log4j2的彩色日志-->
            <PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false"/>
            <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>

        <!--  按照每天生成日志文件  -->
        <RollingFile name="RollingFile" fileName="${LOG_PATH}/${LOG_FILE}.log"
                     filePattern="${LOG_PATH}/${LOG_FILE}.log.%d{yyyy-MM-dd}.%i.log.history">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--每个文件的大小限制-->
                <SizeBasedTriggeringPolicy size="50MB"/>
                <!--每天零点滚动一次-->
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <!-- 每天的文件分片数量-按最大来算(按个人需求划分) -->
            <DefaultRolloverStrategy max="102">
                <Delete basePath="${LOG_PATH}/logs/" maxDepth="2">
                    <IfFileName glob="*.log.history">
                        <IfAny>
                            <!-- 日志文件保留30天,超过的日志会被清除 -->
                            <IfLastModified age="30d" />
                            <!-- 文件总大小,超过的日志会被清除 -->
                            <IfAccumulatedFileSize exceeds="5GB" />
                        </IfAny>
                    </IfFileName>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

    </appenders>

    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
    
</configuration>

二、插入链路TraceId相关配置

本篇是采用slf4j.MDC方式,因为是Springcloud项目所以在网关、log配置处进行了过滤器处理(相关配置是通过Nacos)

网关代码如下(示例):调用方如果没有传入TraceId,则后端自己生成。
(以下的CommonConstant.LOG_TRACE_ID = "trace_id";和日志里的值对应)

package com.example.gatewayserver.filter;



import com.example.common.core.constant.CommonConstant;
import com.example.common.log.properties.TraceProperties;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.UUID;


/**
 * 请求头部TraceId过滤器
 *
 */
@Component
public class HeaderTraceIdFilter implements GlobalFilter , Ordered {

    @Value("${mc.gateway.headerTraceIdFilter:0}")
    private int filterOrder;

    @Autowired
    private TraceProperties traceProperties;
    @Override
    public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterChain chain) {
        if (traceProperties.getEnable()) {
            final ServerHttpRequest request = exchange.getRequest();
            final Map<String, String> headers = request.getHeaders().toSingleValueMap();
            //增加请求id
            if (StringUtils.isBlank(headers.get(CommonConstant.LOG_TRACE_ID))) {
                final ServerHttpRequest requestLast = request.mutate()
                                                             .header(CommonConstant.LOG_TRACE_ID, UUID.randomUUID().toString())
                                                             .build();
                exchange.mutate().request(requestLast).build();
            }
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return filterOrder;
    }
}

common-log代码如下(示例):TraceFilter

package com.example.common.log.filter;


import com.example.common.log.properties.TraceProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * 日志链路追踪过滤器.
 **/
@ConditionalOnClass(Filter.class)
@Slf4j
public class TraceFilter extends OncePerRequestFilter {

    @Resource
    private TraceProperties traceProperties;

    @Override
    protected void doFilterInternal(final HttpServletRequest request,
                                    final HttpServletResponse response,
                                    final FilterChain filterChain) {
        try {
            final List<String> requestHeaders = traceProperties.getRequestHeadParamNames();
            requestHeaders.stream()
                    .filter(s -> StringUtils.isNotBlank(request.getHeader(s)))
                    .forEach(s -> MDC.put(s, request.getHeader(s)));
            filterChain.doFilter(request, response);
        } catch (Exception e){
            log.error("traceFilter Error", e);
        } finally {
            MDC.clear();
        }
    }

}

TraceProperties代码如下(示例):

package com.example.common.log.properties;

import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * 日志链路追踪配置
 **/
@Setter
@Getter
@Component
public class TraceProperties {
    /** 是否开启日志链路追踪. */
    private Boolean enable = Boolean.TRUE;

    /** 需要进行放入日志中的参数值. */
    @Value("${common.trace.requestHeadParamNames}")
    private List<String> requestHeadParamNames;

    /**
     * 需要进行放入日志中的参数值.
     * @return
     */
    public List<String> getRequestHeadParamNames() {

        // 接收到请求,记录请求内容
        return Optional.ofNullable(this.requestHeadParamNames)
                       .filter(o -> !o.isEmpty())
                       .orElse(new ArrayList<>());
           //不用nacos配置,直接塞值          
//        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//        HttpServletRequest request = attributes.getRequest();
//        Enumeration<String> headers = request.getHeaderNames();
//        return Optional.ofNullable(Collections.list(headers))
    }
}

组件和组件之间通信此项目用的是Feign,所以要想TraceId链路完整、唯一,需要增加一个Feign请求拦截器

FeignLogInterceptor代码如下(示例):
(以下的CommonConstant.LOG_TRACE_ID = "trace_id";和日志里的值对应)

package com.example.common.log.interceptor;


import com.example.common.core.constant.CommonConstant;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 * Feign请求拦截器
 */
@Slf4j
@Component
@Configuration
public class FeignLogInterceptor implements RequestInterceptor {

    private static final String TRACE_ID = CommonConstant.LOG_TRACE_ID;

    @Override
    public void apply(final RequestTemplate template) {
        final String traceId = MDC.get(TRACE_ID);
        if(StringUtils.isNotEmpty(traceId)){
            template.header(TRACE_ID, traceId);
        }
    }
}


三、log4j2异步子线程无法获取TraceId的问题

这也是本次改造升级最重要的原因:log4j2可以做到无侵入性的解决异步子线程的问题!!!

解决方法:

resources下新增log4j2.component.properties配置类,内容:isThreadContextMapInheritable=true

在这里插入图片描述
(这里有个糗事:当初配置的时候,其中单词大小写写错了,直接用的图片复制内容粘贴的,导致这个方法不生效。)

最终效果:
在这里插入图片描述


三、SkyWalking全链路追踪TID(拓展)

官网地址:
Apache SkyWalking

Skywalking搭建、优点及基础功能可参考其他博主:

skywalking搭建与使用

Skywalking全链路追踪使用说明

1.SkyWalking相关依赖

代码如下(示例):

<!-- skywalking 日志输出查询-->
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-log4j-2.x</artifactId>
    <version>8.5.0</version>
</dependency>
<!-- skywalking  链路TID生成-->
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-trace</artifactId>
    <version>8.5.0</version>
</dependency>

TID生成:TraceContext.traceId()

2.TraceFilter-过滤器MDC增加TID(日志用):

在这里插入图片描述

3.log4j2.xml中的相关配置(加入SkyWalking-TID)

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<!--log4j2.xml改成log4j2-spring.xml交由spring加载-->
<configuration monitorInterval="5">
    <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

    <!--变量配置-->
    <Properties>
        <!-- 定义日志输出的根目录 -->
        <Property name="LOG_PATH">./all-logs</Property>
        <property name="LOG_FILE">auth-server</property>
        <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
        <!-- %style,%highlight:彩色控制台输出    %logger{36} 表示 Logger 名字最长36个字符 -->
        <property name="LOG_PATTERN"  value="%d{yyyy-MM-dd HH:mm:ss.SSS} %style{%-5level}{bright,magenta} [%thread] %style{[TraceId:%X{trace_id}]}{bright,Cyan} %style{[TID:%X{TID}]}{bright,Yellow} %logger{36} - %msg%n" />
    </Properties>

    <appenders>

        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式  disableAnsi="false"为了开启log4j2的彩色日志-->
            <PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false"/>
            <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>

        <!-- skywalking grpc 日志收集 8.4.0版本开始支持 -->
        <GRPCLogClientAppender name="grpc-log">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [traceId:%X{trace_id}] [%t] %-5level %logger{36} - %msg%n"/>
        </GRPCLogClientAppender>

        <!--  按照每天生成日志文件  -->
        <RollingFile name="RollingFile" fileName="${LOG_PATH}/${LOG_FILE}.log"
                     filePattern="${LOG_PATH}/${LOG_FILE}.log.%d{yyyy-MM-dd}.%i.log.history">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--每个文件的大小限制-->
                <SizeBasedTriggeringPolicy size="50MB"/>
                <!--每天零点滚动一次-->
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <!-- 每天的文件分片数量-按最大来算 -->
            <DefaultRolloverStrategy max="102">
                <Delete basePath="${LOG_PATH}/logs/" maxDepth="2">
                    <IfFileName glob="*.log.history">
                        <IfAny>
                            <!-- 日志文件保留30天,超过的日志会被清除 -->
                            <IfLastModified age="30d" />
                            <!-- 文件总大小,超过的日志会被清除 -->
                            <IfAccumulatedFileSize exceeds="5GB" />
                        </IfAny>
                    </IfFileName>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

    </appenders>


    <Loggers>
        <logger name="com.a.eye.skywalking.ui" level="debug" additivity="false">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="grpc-log"/>
        </logger>
        <logger name="org.apache.skywalking.apm.dependencies" level="INFO"></logger>
        <Root level="INFO">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="grpc-log"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>


</configuration>

此处注意:
只有加入grpc-log配置,SkyWalking的可视化界面的日志、以及追踪里面的相关日志才能生成!

4.最终显示效果

日志效果:
在这里插入图片描述
SkyWalking效果:
追踪TID:
在这里插入图片描述
相关日志:
①:
在这里插入图片描述
②:
在这里插入图片描述③:
在这里插入图片描述
日志:
在这里插入图片描述
TID都是:03c9039466ba4c5fb816ea0e6226f61c.153.17090021036670001实现全链路追踪。


说明

关于SkyWalking的可视化界面相关作用优点可另行查询,这里只做日志的补充。我们都知道一个好的日志打印,对真实生产中排除问题及分析的帮助有多大。

如果文中有疑问的欢迎讨论、指正(抱拳),互相学习,感谢关注。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值