dubbo分布式日志追踪

Posted by Clear Blog on September 3, 2018

分布式应用都会有服务调用链路的问题,需要记录日志来定位问题, 如果是方法或者接口层面进行参数传递。对代码的耦合度太高。 我们可以通过dubbo的filter 结合slf4j的MDC或者log4j2的ThreadContext的进行参数的注入, 可以直接在日志文件中配置被注入的参数,这样就对系统和日志id打印进行了解耦。 其中当用logback日志的时候是需要调用MDC的方法,而log4j2则需要调用ThreadContext的方法。

MDC[http://logback.qos.ch/manual/mdc.html]是为每个线程建立一个独立的存储空间, 开发人员可以根据需要把信息存入其中。MDC使用ThreadLocalMap机制来存储信息,信息以key/value对的形式存储在Map中。

消费端实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import org.apache.dubbo.common.Constants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.*;
import org.apache.log4j.MDC;

import java.util.UUID;


/**
 * 下游从RpcContext中获取参数
 * String traceId = RpcContext.getContext().getAttachment("trace_id");
 *
 * @author xuguangwu
 */
@Activate(group = Constants.CONSUMER)
public class ConsumerRpcTraceFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String traceId = (String) MDC.get("traceId");
        if (StringUtils.isBlank(traceId)) {
            traceId = this.getUUID();
        }

        RpcContext.getContext().setAttachment("trace_id", traceId);
        return invoker.invoke(invocation);
    }

    /**
     * 获取UUID
     *
     * @return String UUID
     */
    public String getUUID() {
        String uuid = UUID.randomUUID().toString();
        return uuid.replaceAll("-", "");
    }
}

服务提供端实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.apache.dubbo.common.Constants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.*;
import org.apache.log4j.MDC;

import java.util.UUID;

@Activate(group = {Constants.PROVIDER}, order = 1)
public class ProviderRpcTraceFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String traceId = RpcContext.getContext().getAttachment("trace_id");
        if (StringUtils.isBlank(traceId)) {
            traceId = this.getUUID();
        }
        MDC.put("traceId", traceId);
        RpcContext.getContext().setAttachment("trace_id", traceId);
        try {
            return invoker.invoke(invocation);
        } finally {
            //在方法调用完成后移除该ID
            MDC.remove("traceId");
        }
    }

    public String getUUID() {
        String uuid = UUID.randomUUID().toString();
        return uuid.replaceAll("-", "");
    }

}

如果我们在有web页面的项目中,可能需要将traceId往上层抛,在返回前端的对象中,注入traceId,根据traceId快速定位日志。 实现方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Configuration
public class JacksonConfig {

	@Bean
	public SimpleModule simpleModule() {
		SimpleModule module = new SimpleModule();
		GenericJsonSerializer genericJsonSerializer=new GenericJsonSerializer();
		//Result为自定义返前端的对象
		module.addSerializer(Result.class, genericJsonSerializer);
		return module;
	}
}

public class GenericJsonSerializer<T> extends JsonSerializer<T> {
	@Override
	public void serialize(T result, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {

		String traceId = MDC.get("traceId");
		if (traceId == null) {
			traceId = "";
		}
		JSONObject jsonObject;
		if (result == null) {
			jsonObject = new JSONObject();
		} else {
			jsonObject = (JSONObject) JSON.toJSON(result);
		}
		jsonObject.put("traceId", traceId);
		jsonGenerator.writeObject(jsonObject);
	}
}

在/src/main/resources/META-INF/dubbo/com.apache.dubbo.rpc.Filter文件中配置filter

最后在日志配置中加上%X{traceId} 即可。

以上为单线程情况,多线程则需将子线程id也传入到MDC中。建一个最简单的spring项目。

  • spring配置文件,声明一个线程池 ````xml <?xml version=”1.0” encoding=”UTF-8”?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
* 声明线程上下文,并交由声名的线程池来管理。
````java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Component
public class TreadContext {

    @Autowired
    private ThreadPoolTaskExecutor executor;

    private static TreadContext context;

    private List<AbstractRunnable> threads = new ArrayList();

    @PostConstruct
    private void init() {
        context = this;
    }

    /**
     * 关闭线程池
     */
    public void close() {
        executor.shutdown();
    }

    /**
     * 执行所有线程池
     */
    public void open() {
        for (Runnable thread : this.threads) {
            executor.execute(thread);
        }
    }

    /**
     * 添加多个线程任务
     *
     * @param threads
     * @return
     */
    public TreadContext source(List<AbstractRunnable> threads) {
        this.threads.addAll(threads);
        return context;
    }

    /**
     * 添加单个线程任务
     *
     * @param thread
     */
    public void source(AbstractRunnable thread) {
        this.threads.add(thread);
    }

    /**
     * 获取线程池管理对象
     *
     * @return
     */
    public static TreadContext getContext() {
        return context;
    }
}
  • 上下文中内容定制 ````java import org.slf4j.MDC;

public abstract class AbstractRunnable implements Runnable { private String traceId; private String ip;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public AbstractRunnable(String traceId, String ip) {
    this.traceId = traceId;
    this.ip = ip;
}

private void log() {
    MDC.put(Constant.TRANC_ID, traceId);
    MDC.put(Constant.LOCAL_IP, ip);
}

protected abstract void thread();

@Override
public void run() {
    log();
    thread();
}

public String getTraceId() {
    return traceId;
}

public void setTraceId(String traceId) {
    this.traceId = traceId;
}

public String getIp() {
    return ip;
}

public void setIp(String ip) {
    this.ip = ip;
} } ```` * 涉及到的常量 ````java public class Constant {
public static final String TRANC_ID = "TRANC_ID";
public static final String LOCAL_IP = "LOCAL_IP"; } ````
  • 测试 ````java import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;

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

public class Main {

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static Logger logger = LoggerFactory.getLogger(Main.class);

private static String ID = "10000000000001";
private static String IP = "192.168.80.123";

public static void main(String[] args) {
    AbstractApplicationContext appContext = new ClassPathXmlApplicationContext("spring.xml");
    TreadContext context = appContext.getBean(TreadContext.class);
    MDC.put(Constant.TRANC_ID, ID);
    MDC.put(Constant.LOCAL_IP, "192.168.80.123");
    AbstractRunnable thread1 = new MyThread(ID, IP);
    AbstractRunnable thread2 = new MyThread(ID, IP);
    List<AbstractRunnable> threads = new ArrayList<AbstractRunnable>();
    threads.add(thread1);
    threads.add(thread2);
    context.source(threads).open();
    logger.info("thread name:{}  ,trace_id:{}  ,local_ip:{}", Thread.currentThread().getName(), MDC.get(Constant.TRANC_ID), MDC.get(Constant.LOCAL_IP));
    appContext.registerShutdownHook();
} } ````