实现一个请求的所有日志都拥有同一个标识,简称:实现基于RPC调用的轻量服务追踪。

本文介绍了如何通过日志标识、拦截器和dubbo Filter实现跨项目的请求日志追踪。在消费者和提供者两端分别设置过滤器,并确保线程名称一致,从而在异步操作中也能保持日志的可追踪性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

第一步:消费者项目里:使用的日志工具是logback ,下面看日志配置文件logback-spring.xml内容,重点是:[%thread]:打印日志时获取当前线程的名称

第二步:消费者项目里:写个拦截器,主要是preHandle方法,给当前请求的线程设置一个线程名称

第三步:消费者项目里:将刚才的拦截器注册

第四步:消费者项目里:写个过滤器,注意当前过滤器实现的dubbo的Filter类,即:com.alibaba.dubbo.rpc.Filter类。不是spring的Filter。

第五步:消费者项目里:写完dubbo的Filter之后,还不算完,一定要在消费者项目的src/main/resources/META-INF下,建立dubbo文件夹,dubbo文件夹下在建立名为"com.alibaba.dubbo.rpc.Filter"的文件,这个文件内容为:logFilter=com.quanran.service.consumer.LogFilter。如图:

第六步:消费者项目里:你写好了dubbo的Filter之后,就要用,怎么用呢?看下图:

第七步:消费者项目里:消费者调用dubbo服务

第八步:提供者项目里:日志工具使用的是log4j,虽然与消费者使用的日志工具不同但是不影响。下面看logback-spring.xml内容,重点是:[%t]:打印日志时获取当前线程的名称。

第九步:提供者项目里:也要写个dubbo的Filter过滤器,注意当前过滤器实现的dubbo的Filter类,即:com.alibaba.dubbo.rpc.Filter类。不是spring的Filter。内容如下:

第十步:提供者项目里:也要在提供者项目的src/main/resources/META-INF下,建立dubbo文件夹,dubbo文件夹下在建立名为"com.alibaba.dubbo.rpc.Filter"的文件,这个文件内容为:logFilter=com.quanran.dubbo.filter.LogFilter。如图:

第十一步:提供者项目里:写好了提供者的dubbo Filter之后,就要用,怎么用呢?看下面代码的@service注解的filter属性,将你的logFilter作为filter属性的属性值即可。

第十二步:启动项目,消费者调用提供者,我们可以看到提供者的日志为:

第十三步:请允许我调皮一下,不是都完了了么,为什么还有第十三步?因为上面从消费者到提供者的执行过程都是同步的,那实际开发过程中,不可避免的要碰到一些异步操作。那异步操作打印的日志,是一个新的线程,那么线程名称肯定和之前的同步线程名称不一样,怎么办呢?看下面代码:

第十四步:转发请尊重原创,请带 原创地址:https://blog.youkuaiyun.com/qq_23167527


背景:互联网金融app的后台,非常的频繁调用复杂的借还款流程就会打印很多日志,当出现问题的时候,由于系统间的嵌套调用,我们就可能在一台服务器里的多个项目(甚至多台服务器里的多个项目)去追日志。就算只是一个项目,当请求的频繁时一个请求的日志也可能被不是当前请求的日志无规律插队。所以我们就只能花费更多的时间根据接口名、方法名或参数等数据去查日志。为此,我特意在我当前正在开发的项目里解决了这个问题,让一个请求的所有日志拥有同一个标识。

开发框架:springboot+dubbo+zk

第一步:消费者项目里:使用的日志工具是logback ,下面看日志配置文件logback-spring.xml内容,重点是:[%thread]:打印日志时获取当前线程的名称

<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 控制台设置 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoder 默认配置为PatternLayoutEncoder -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %level %c[%L] : %msg%n
            </pattern>
        </encoder>
    </appender>

    <!-- * 通配符 设置log打印级别 对所有类有效TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <!-- <appender-ref ref="rollingFileAppender" /> -->
    </root>
</configuration>

第二步:消费者项目里:写个拦截器,主要是preHandle方法,给当前请求的线程设置一个线程名称

package com.quanran.third.web.interceptors;

import java.util.Random;

import com.quanran.common.util.RandomUtil;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
@Slf4j
public class ThreadInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 当前线程上下文唯一标示
        Thread.currentThread().setName(getRandomStr(12));
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) {
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    }
    
    /**
     * 生成指定长度的随机字符串
     *
     * @len 要生成的字符串的长度
     * @return 随机字符串
     */
    private String getRandomStr(int length) {
        if (length <= 0) {
            length = 32;
        }
        String CHARS = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            int index = new Random().nextInt(CHARS.length());
            sb.append(CHARS.charAt(index));
        }
        return sb.toString();
    }
}

第三步:消费者项目里:将刚才的拦截器注册

package com.quanran.config.context;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.quanran.third.web.interceptors.ErrorInterceptor;

@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ThreadInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

至此,如果你的项目没有调用别的dubbo服务,只是本项目自身的注入调用,那么你现在启动项目,打印出来的日志就实现了一个请求的日志追踪。如下图,黄色标识的是同一个请求的日志:

第四步:消费者项目里:写个过滤器,注意当前过滤器实现的dubbo的Filter类,即:com.alibaba.dubbo.rpc.Filter类。不是spring的Filter。

package com.quanran.service.consumer;

import java.lang.reflect.Field;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import com.quanran.dubbo.exception.QuanRanException;

/**
 * <p>Description: [dubbo的过滤器Filter]</p>
 * Created on 2018年9月10日 下午5:50:35
 * @author  <a href="mailto: 15175223269@163.com">全冉</a>
 * @version 1.0 
 * Copyright (c) 2018 全冉技术部
 */
@Slf4j
public class LogFilter implements Filter {
	
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

    	/**
         * 这个for循环的作用:将消费者里此次请求的线程名称赋值到调用dubbo的参数属性里,由此让dubbo服务里的线程名和消费者统一
         */
        for(Object object : invocation.getArguments()){
            Field field = getFieldByClasss("traceId",object);// 字符串traceId是此次dubbo请求参数对象里的一个字段属性
            if(field != null) {
                field.setAccessible(true);// 设置成true的作用就是让我们在用反射时可以对私有变量操作
                try {
                    field.set(object, Thread.currentThread().getName());// field的set方法两个参数:第一个参数是对象,第二个参数是将第一个参数里field字段的终值(终值就是要赋的值)
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        /**
         * 拼接dubbo服务的参数成字符串
         */
        StringBuffer sb = new StringBuffer();
        int index = 1;
        // 请求参数的循环拼接
        for(Object object : invocation.getArguments()){
        	// isPrimitive()为true则当前object的类类型是基本类型,比如:byte,char,short,int,long,float,double,boolean和void
            if(object.getClass().isPrimitive() || 
                    "class java.lang.String".equals(object.getClass().toString()) ||
                    "class java.lang.Boolean".equals(object.getClass().toString())) {
                sb.append("参数" + index + " :").append(String.valueOf(object));
                if(index != invocation.getArguments().length){
                    sb.append("\r\n");
                }
            }else{
            	// isPrimitive()为false则当前object的类类型不是基本类型的,是Object,是对象
                try {
                    sb.append("参数" + index + " :").append(JSONObject.fromObject(object).toString());
                    if(index != invocation.getArguments().length){
                        sb.append("\r\n");
                    }
                }catch (Exception e){
                    sb.append("参数" + index + " :").append(object.toString());
                    if(index != invocation.getArguments().length){
                        sb.append("
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值