提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、问题背景
springcloud项目,对外提供的都是restful协议,为了方便日志的查询与管理,在filter中设置了traceId属性,并在logback日志配置文件中进行输出。
近期总是碰到生产环境有好多请求的日志,日志中的traceId不一致,例如一次完整的请求,在filter中输出的日志和正式的业务日志里的traceId就不同。
二、排查过程
1、排查方向:有另外一个地方设置了新的traceId
排查结果:没有找到其他设置traceId的地方,将Filter中唯一的设置位置屏蔽之后,日志中不再输出traceId。
2、怀疑是MDC的问题
排查结果:更换为ThreadLocal、查看MDC的源码,均没有找到问题所在
而且生产环境中,这种情况也不是必现,一度陷入了僵局......
3、偶然发现有个奇怪的现象
①Filter日志输出请求参数
②业务日志
③Filter日志输出响应结果
发现出现不一致的情况时,该请求的完整日志输出中,①和③的traceId一致,②的traceId居然和1中的请求头里的某个属性 hc-traceid 一致。
有了这个发现,我本机做了一个测试。
当请求头里包含hc-traceid属性时,对应的请求的业务日志中,traceId与请求头里的一致。
当请求头里不包含hc-traceid属性时,①②③中的traceId一致。
hc-traceid,从命名上来看,我猜是公司里某个公共jar包里引用的,将其屏蔽掉之后,请求里就没有这个属性了。正常情况下,就顺着情况一或者是多线程方向去找,一般都没有问题。
三、解决方案
有两个思路
1、修改请求头里的属性,将该属性移除。
2、不改变原有请求头里的属性,改变获取方法的实现
这里我们给出第二个方案的代码示例:
public class ContentCacheRequestWrapper extends HttpServletRequestWrapper {
private byte[] body;
private BufferedReader reader;
private ServletInputStream inputStream;
private Map<String, String> headerMap = new HashMap<>();
private Map<String , String[]> params = new HashMap<>();
public ContentCacheRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
//读一次 然后缓存起来
body = IOUtils.toByteArray(request.getInputStream());
inputStream = new RequestCachingInputStream(body);
this.params.putAll(request.getParameterMap());
}
public byte[] getBody() {
return body;
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (inputStream != null) {
return inputStream;
}
return super.getInputStream();
}
@Override
public BufferedReader getReader() throws IOException {
if (reader == null) {
reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));
}
return reader;
}
//代理一下ServletInputStream 里面真是内容为当前缓存的bytes
private static class RequestCachingInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
public RequestCachingInputStream(byte[] bytes) {
inputStream = new ByteArrayInputStream(bytes);
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public boolean isFinished() {
return inputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readlistener) {
}
}
@Override
public String getHeader(String name) {
if (Objects.equals(name, "hc-traceid")) {
return MDC.get("traceId");
}
String headerValue = super.getHeader(name);
if (headerMap.containsKey(name)) {
headerValue = headerMap.get(name);
}
return headerValue;
}
}
这里说明一下,我们在原来的基础上,修改getHeader的实现,覆盖原有实现,当要获取hc-traceid属性的值时,返回的是我们自己设置的traceId值,至此,问题得以解决。
总结
这个问题困扰挺久了,终于解决了!