AOP/Filter+MDC实现traceId日志追踪

AOP/Filter+MDC实现traceId日志追踪

在应用日志查询时,我们常常希望可以有个关键字可以查询某个业务的整生命周期,log4j 和 logback提供了MDC(Mapped Diagnostic Context,映射调试上下文)功能,可以在多线程条件下记录日志。

在微服务、分布式中更是希望可以进行链路追踪。


一、AOP+MDC简单实现


/**
 * ************************************************************
 * Copyright © 2021 cnzz Inc.All rights reserved.  *    **
 * ************************************************************
 *
 * @program: Unknown
 * @description: 业务日志追踪
 * @author: cnzz
 * @create: 2021-01-12 13:49
 **/
@Slf4j
@Component
@Aspect
public class TraceIdHandler {
    private static final String TRACE_ID = "traceId";


   /* 参数部分允许使用通配符:
            *  匹配任意字符,但只能匹配一个元素
            .. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用
            +  必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类
  */
    @Before(value = "execution(* com.ytkj.feec..*(..))")
    public void excuteBefore() {
        if (StringUtils.isBlank(MDC.get(TRACE_ID))) {
            String traceId = UUID.randomUUID().toString().replace("-", "");
            MDC.put(TRACE_ID, traceId);
        }
    }
}

 

log日志文件xml配置  输出格式Pattern      添加   

%X{traceId}
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

    <!-- 属性文件:在properties文件中找到对应的配置项 -->
    <springProperty scope="context" name="logging.path" source="logging.path"/>

    <!--logger上下文名称,区分不同应用程序-->
    <contextName>rcbcloud-settle</contextName>
    <property name="LOG_PATH" value="/data/logs/paycentre" />
    <property name="project_name" value="zuul" />
    <property name="LOG_HOME" value="${LOG_PATH}/%d{yyyyMMdd}/${project_name}/${project_name}"/>

    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出(配色):%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%yellow(%d{yyyy-MM-dd HH:mm:ss.SSS }) %magenta([%thread]) - %red([%-5level]) %cyan(%c [%L]) - %blue([%X{corrId}-%X{traceId}]) - %highlight(%msg) %n

            </pattern>
            <charset class="java.nio.charset.Charset">UTF-8</charset>
        </encoder>
    </appender>

    <!--根据日志级别分离日志,分别输出到不同的文件-->
    <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">

            <level>INFO</level>

          <!--  <level>ERROR</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>-->
        </filter>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!--滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--按时间保存日志 修改格式可以按小时、按天、月来保存-->
            <fileNamePattern>${LOG_HOME}.info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!--保存时长-天数  MaxHistory指的是文件数量,超过MaxHistory数量才会删除,只有当每天生成且只生成一个文件时才表示保留天数-->
            <MaxHistory>90</MaxHistory>
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
            <!--文件大小-->
            <totalSizeCap>1GB</totalSizeCap>

        </rollingPolicy>
    </appender>

    <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
            </pattern>
        </encoder>
        <!--滚动策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--路径-->
            <fileNamePattern>${LOG_HOME}.error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <MaxHistory>90</MaxHistory>
        </rollingPolicy>
    </appender>
    <root level="debug">
        <appender-ref ref="consoleLog"/>
        <appender-ref ref="fileInfoLog"/>
        <appender-ref ref="fileErrorLog"/>
    </root>
</configuration>

二、使用filter+MDC实现



/**
 * ************************************************************
 * Copyright © 2020 cnzz Inc.All rights reserved.  *    **
 * ************************************************************
 *
 * @program: demo
 * @description: api过滤器
 * @author: cnzz
 * @create: 2020-12-17 11:31
 * <p>
 * 对接口api进行签名验证
 **/
@Configuration
@Slf4j
public class ApiHeaderFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        long startTime=System.currentTimeMillis();
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        //参数偷换,直留data
        //HttpServletRequest没有提供相关的set方法来修改body,所以需要用修饰类
        servletRequest = new BodyRequestWrapper2((HttpServletRequest) request);
        filterChain.doFilter(servletRequest, servletResponse);

        //交易响应时长
        String servletPath = request.getServletPath();
        log.info("响应时长--time={}ms,servletPath={}",System.currentTimeMillis()-startTime,servletPath);
    }

    @Override
    public void destroy() {

    }

}

 



/**
 * ************************************************************
 * Copyright © 2020 cnzz Inc.All rights reserved.  *    **
 * ************************************************************
 *
 * @program: demo
 * @description: 重新请求对象
 * @author: cnzz
 * @create: 2020-12-17 14:03
 * <p>
 * 整理请求参数
 **/
@Slf4j
public class BodyRequestWrapper2 extends HttpServletRequestWrapper {
    private byte[] body;
    private static final String TRACE_ID = "traceId";
    public BodyRequestWrapper2(HttpServletRequest request){
        super(request);

//        StreamUtil.readBytes(request.getReader(), "utf-8");
//        //由于request并没有提供现成的获取json字符串的方法,所以我们需要将body中的流转为字符串
//        String json = new String(StreamUtil.readBytes(request.getReader(), "utf-8"));
        String data = HttpUtil.getDataFromRequest2(request);
        body = data.getBytes();

        //1、获取请求头
        SysHeader header = getSysHeader(request, data);

        //2、MDC + trace_id添加
        String traceId = StringUtils.isNotBlank(header.getCorrId()) ? header.getCorrId() : UUID.randomUUID().toString().replace("-", "");
        MDC.put(TRACE_ID, traceId);

        //3、servletPath+userInfo+ipAddr+userAgent
        String ipAddr = IpUtils.getIpAddr(request);
        String servletPath = request.getServletPath();
        String userAgent = request.getHeader("User-Agent");
        log.info("【请求filter】servletPath={},ipAddr={},userAgent={}", servletPath, ipAddr,userAgent);
        if (!ApiRouter.isApiRouter(servletPath)) {

            log.info("【需要验签】{}", servletPath);
            //校验
            ValidSysReqUtil2.validSysHeader(header);
        }

    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    /**
     * 在使用@RequestBody注解的时候,其实框架是调用了getInputStream()方法,所以我们要重写这个方法
     *
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }


    private SysHeader getSysHeader(HttpServletRequest request, String data) {
        return new SysHeader()
                .setTimestamp(request.getHeader("timestamp"))
                .setSign(request.getHeader("sign"))
                .setSignType(request.getHeader("signType"))
                .setCorrId(request.getHeader("corrId"))
                .setData(data);
    }

}

效果展示

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值