需要在客户端添加5个类
1.ResponseWrapper.java
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* 返回值输出代理类
*/
public class ResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream buffer;
private ServletOutputStream out;
public ResponseWrapper(HttpServletResponse httpServletResponse) {
super(httpServletResponse);
buffer = new ByteArrayOutputStream();
out = new WrapperOutputStream(buffer);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}
@Override
public void flushBuffer() throws IOException {
if (out != null)
{
out.flush();
}
}
public byte[] getContent() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
class WrapperOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos;
public WrapperOutputStream(ByteArrayOutputStream bos) {
this.bos = bos;
}
@Override
public void write(int b) throws IOException {
bos.write(b);
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setWriteListener(WriteListener arg0) {
// TODO Auto-generated method stub
}
}
}
2.CustomHttpServletResponseSpanInjector.java
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanTextMap;
import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector;
public class CustomHttpServletResponseSpanInjector extends ZipkinHttpSpanInjector {
@Override
public void inject(Span span, SpanTextMap carrier) {
super.inject(span, carrier);
carrier.put(Span.TRACE_ID_NAME, span.traceIdString());
carrier.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
}
}
3.HttpResponseInjectingTraceFilter.java(关键代码)
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.web.filter.GenericFilterBean;
import com.alibaba.fastjson.JSONObject;
public class HttpResponseInjectingTraceFilter extends GenericFilterBean {
private static final Logger logger = LoggerFactory
.getLogger(HttpResponseInjectingTraceFilter.class);
private final Tracer tracer;
public HttpResponseInjectingTraceFilter(Tracer tracer) {
this.tracer = tracer;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
Span currentSpan = this.tracer.getCurrentSpan();
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 添加头部信息
Map headerMap = new HashMap();
Enumeration enume = httpRequest.getHeaderNames();
while (enume.hasMoreElements()) {
String key = enume.nextElement();
String value = httpRequest.getHeader(key);
headerMap.put(key, value);
}
if (headerMap.size() > 0) {
currentSpan.tag("http.head", JSONObject.toJSONString(headerMap));
}
//方法名
String method = httpRequest.getMethod();
currentSpan.tag("request.method", method);
RequestWrapper wrapperRequest = new RequestWrapper(httpRequest);// 转换成代理类
ResponseWrapper wrapperResponse = new ResponseWrapper(
(HttpServletResponse) response);// 转换成代理类
// 拦截返回
filterChain.doFilter(wrapperRequest, wrapperResponse);
// 添加参数信息
String params = this.getRequestParameter(wrapperRequest);
currentSpan.tag("http.params", params);
// 添加返回值信息
byte[] content = wrapperResponse.getContent();// 获取返回值
// 判断是否有值
if (content.length > 0) {
try {
String result = new String(content, "UTF-8");
currentSpan.tag("http.result", result);
} catch (Exception e) {
logger.error("添加返回值信息异常", e);
}
}
// 把返回值输出到客户端
ServletOutputStream out = null;
try {
out = response.getOutputStream();
} catch (Exception e) {
} finally {
try {
if (out != null) {
out.write(content);
out.flush();
out.close();
}
} catch (IOException e) {
}
}
}
/**
* 方法功能说明: 获取请求参数包括form表单和json参数
*/
public String getRequestParameter(RequestWrapper wrapperRequest) {
if (null == wrapperRequest) {
return null;
}
String params = null;
String method = wrapperRequest.getMethod();
if (StringUtils.isNotBlank(method) && "GET".equals(method.toUpperCase())) {
// 获取请求体中的字符串(get)
params = wrapperRequest.getQueryString();
try {
if (StringUtils.isNotBlank(params))
params = URLDecoder.decode(params, "UTF-8");
} catch (UnsupportedEncodingException e) {
// Logger.error("获取到的请求参数解码错误 : {}", e.getMessage());
}
return params;
}else {
return wrapperRequest.getBodyString(wrapperRequest);
}
}
}
4.HttpSpanConfig.java
package com.keeplotus.config;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.keeplotus.filter.HttpResponseInjectingTraceFilter;
@Configuration
public class HttpSpanConfig {
@Bean
HttpResponseInjectingTraceFilter responseInjectingTraceFilter(Tracer tracer) {
return new HttpResponseInjectingTraceFilter(tracer);
}
}
5.RequestWrapper.java
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.StreamUtils;
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String sessionStream = getBodyString(request);
body = sessionStream.getBytes(Charset.forName("UTF-8"));
}
/**
* 获取请求Body
*
* @param request
* @return
*/
public String getBodyString(final ServletRequest request) {
String contentType = request.getContentType();
String bodyString ="";
if (StringUtils.isNotBlank(contentType) &&
(contentType.contains("multipart/form-data") ||
contentType.contains("x-www-form-urlencoded"))){
Enumeration pars=request.getParameterNames();
while(pars.hasMoreElements()){
String n=pars.nextElement();
bodyString+=n+"="+request.getParameter(n)+"&";
}
bodyString=bodyString.endsWith("&")?bodyString.substring(0, bodyString.length()-1):bodyString;
return bodyString;
}
try {
byte[] byteArray = StreamUtils.copyToByteArray(request.getInputStream());
bodyString = new String(byteArray, "UTF-8");
} catch (IOException e) {
}
return bodyString;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
注意:是添加到客服端服务,添加到zipkin-server服务端无效,也就是集成zipkin的客户端都需要添加,大家可以打成jar包的形式添加到项目中,也可以上传到maven仓库。
源码地址:https://download.csdn.net/download/u011974797/12357644
如果是servlet项目可以参考:https://download.csdn.net/download/u011974797/12357915
spring cloud中文官网参考地址:https://springcloud.cc/spring-cloud-dalston.html#_custom_sa_tag_in_zipkin