分布式应用都会有服务调用链路的问题,需要记录日志来定位问题, 如果是方法或者接口层面进行参数传递。对代码的耦合度太高。 我们可以通过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();
} } ````