目录
第一步:消费者项目里:使用的日志工具是logback ,下面看日志配置文件logback-spring.xml内容,重点是:[%thread]:打印日志时获取当前线程的名称
第二步:消费者项目里:写个拦截器,主要是preHandle方法,给当前请求的线程设置一个线程名称
第四步:消费者项目里:写个过滤器,注意当前过滤器实现的dubbo的Filter类,即:com.alibaba.dubbo.rpc.Filter类。不是spring的Filter。
第六步:消费者项目里:你写好了dubbo的Filter之后,就要用,怎么用呢?看下图:
第八步:提供者项目里:日志工具使用的是log4j,虽然与消费者使用的日志工具不同但是不影响。下面看logback-spring.xml内容,重点是:[%t]:打印日志时获取当前线程的名称。
第十一步:提供者项目里:写好了提供者的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("