Skip to content

Commit

Permalink
feat(trace): 添加响应体黑名单功能
Browse files Browse the repository at this point in the history
- 新增 tracerResponseBodyBlackListUrls 配置项(-Ddd.trace.request.body.blacklist.urls 多个url用区分)
- 实现响应体黑名单逻辑,避免特定 URL 的响应体被追踪
- 优化请求和响应体的处理流程,提高性能
- 优化spring-webmvc-6.0 的响应体返回逻辑
  • Loading branch information
liurui committed Jan 20, 2025
1 parent f326ddd commit 1202fe0
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import datadog.trace.api.GlobalTracer;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
Expand Down Expand Up @@ -54,87 +55,54 @@ protected void doFilterInternal(
filterChain.doFilter(request, response);
return;
}

AgentSpan span = (AgentSpan) parentSpan;
if (tracerHeader && !requestBodyEnabled && !responseBodyEnabled) {
filterChain.doFilter(request, response);
buildHeaderTags(span,request, response);
return;
}

ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
ContentCachingRequestWrapper requestWrapper = null;
String contextType = null;
String methodType = null;

requestWrapper = new ContentCachingRequestWrapper(request);
filterChain.doFilter(requestWrapper, responseWrapper);

byte[] data = responseWrapper.getContentAsByteArray();
responseWrapper.copyBodyToResponse();

if (requestBodyEnabled){
requestWrapper = new ContentCachingRequestWrapper(request);
}
String contextType = requestWrapper==null?request.getContentType():requestWrapper.getContentType();
String methodType = requestWrapper==null?request.getMethod():requestWrapper.getMethod();
String url = requestWrapper==null?request.getRequestURI():requestWrapper.getRequestURI();
boolean hasBlackList = false;
if (!isEmpty(Config.get().getTracerResponseBodyBlackListUrls())){
hasBlackList = Arrays.stream(Config.get().getTracerResponseBodyBlackListUrls().split(",")).anyMatch(uri -> uri.equals(url));
}
ContentCachingResponseWrapper responseWrapper = null;
boolean responseBodyEnabledTmp = responseBodyEnabled && !hasBlackList;
if (responseBodyEnabledTmp){
responseWrapper = new ContentCachingResponseWrapper(response);
}
filterChain.doFilter(requestWrapper==null?request:requestWrapper, responseWrapper==null?response:responseWrapper);
byte[] data =null;
if (responseBodyEnabledTmp) {
// 必须放到 filterChain.doFilter 之后,否则 responseWrapper.getContentAsByteArray() 为空
data = responseWrapper.getContentAsByteArray();
responseWrapper.copyBodyToResponse();
}
if (tracerHeader) {

contextType = requestWrapper.getContentType();
methodType = requestWrapper.getMethod();
StringBuffer requestHeader = new StringBuffer("");
StringBuffer responseHeader = new StringBuffer("");
Enumeration<String> headerNames = requestWrapper.getHeaderNames();
int count = 0;
while (headerNames.hasMoreElements()) {
if (count == 0) {
requestHeader.append("{");
} else {
requestHeader.append(",\n");
}
String headerName = headerNames.nextElement();
requestHeader
.append("\"")
.append(headerName)
.append("\":")
.append("\"")
.append(requestWrapper.getHeader(headerName).replace("\"", ""))
.append("\"");
count++;
}
if (count > 0) {
requestHeader.append("}");
}
count = 0;
for (String headerName : responseWrapper.getHeaderNames()) {
if (count == 0) {
responseHeader.append("{");
//
// responseHeader.append("\"guance_trace_id\":").append("\"").append(GlobalTracer.get().getTraceId()).append("\"");
} else {
responseHeader.append(",\n");
}
responseHeader
.append("\"")
.append(headerName)
.append("\":")
.append("\"")
.append(responseWrapper.getHeader(headerName))
.append("\"");
count++;
}

if (count > 0) {
responseHeader.append("}");
}
span.setTag("request_header", requestHeader.toString());
span.setTag("response_header", responseHeader.toString());
buildHeaderTags(span,requestWrapper==null?request:requestWrapper, responseWrapper==null?response:responseWrapper);
}

if (Config.get().isTracerRequestBodyEnabled()
&& "POST".equalsIgnoreCase(methodType)
&& contextType != null
&& (contextType.contains("application/json"))) {
span.setTag("request_body", new String(requestWrapper.getContentAsByteArray()));
}
int dataLength = data.length;
int dataLength = data==null?0:data.length;
log.debug(
"response.getContentType() >>>> :{},traceId:{},spanId:{},dataLength:{},responseBodyEnabled:{}",
responseWrapper.getContentType(),
"traceId:{},spanId:{},dataLength:{},responseBodyEnabled:{}",
GlobalTracer.get().getTraceId(),
span.getSpanId(),
dataLength,
Config.get().isTracerResponseBodyEnabled());
if (Config.get().isTracerResponseBodyEnabled()) {
responseBodyEnabled);
if (responseBodyEnabledTmp) {
if (responseWrapper.getContentType() != null
&& (responseWrapper.getContentType().contains("application/json")
|| responseWrapper.getContentType().contains("text/plain"))) {
Expand All @@ -158,6 +126,57 @@ protected void doFilterInternal(
}
}

private boolean isEmpty(String str) {
return str == null || str.length() == 0;
}

private void buildHeaderTags(AgentSpan span,final HttpServletRequest request, final HttpServletResponse response) {
StringBuffer requestHeader = new StringBuffer("");
StringBuffer responseHeader = new StringBuffer("");
Enumeration<String> headerNames = request.getHeaderNames();
int count = 0;
while (headerNames.hasMoreElements()) {
if (count == 0) {
requestHeader.append("{");
} else {
requestHeader.append(",\n");
}
String headerName = headerNames.nextElement();
requestHeader
.append("\"")
.append(headerName)
.append("\":")
.append("\"")
.append(request.getHeader(headerName).replace("\"", ""))
.append("\"");
count++;
}
if (count > 0) {
requestHeader.append("}");
}
count = 0;
for (String headerName : response.getHeaderNames()) {
if (count == 0) {
responseHeader.append("{");
} else {
responseHeader.append(",\n");
}
responseHeader
.append("\"")
.append(headerName)
.append("\":")
.append("\"")
.append(response.getHeader(headerName))
.append("\"");
count++;
}

if (count > 0) {
responseHeader.append("}");
}
span.setTag("request_header", requestHeader.toString());
span.setTag("response_header", responseHeader.toString());
}
/**
* When a HandlerMapping matches a request, it sets HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE
* as an attribute on the request. This attribute is read by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
Expand Down Expand Up @@ -50,66 +51,140 @@ protected void doFilterInternal(
} catch (final Exception ignored) {
// mapping.getHandler() threw exception. Ignore
}
ContentCachingResponseWrapper wrapper = new ContentCachingResponseWrapper(response);
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
filterChain.doFilter(requestWrapper, wrapper);
wrapper.copyBodyToResponse();
response.setHeader("guance_trace_id", GlobalTracer.get().getTraceId());
boolean tracerHeader = Config.get().isTracerHeaderEnabled();
boolean requestBodyEnabled = Config.get().isTracerRequestBodyEnabled();
boolean responseBodyEnabled = Config.get().isTracerResponseBodyEnabled();
if (!(tracerHeader || requestBodyEnabled || responseBodyEnabled)) {
log.debug("尚未开启 request|response 功能");
filterChain.doFilter(request, response);
return;
}

String contextType = requestWrapper.getContentType();
String methodType = requestWrapper.getMethod();
if (tracerHeader && !requestBodyEnabled && !responseBodyEnabled) {
filterChain.doFilter(request, response);
buildHeaderTags(span,request, response);
return;
}

boolean tracerHeader = Config.get().isTracerHeaderEnabled();
if (tracerHeader) {
wrapper.addHeader("guance_trace_id", GlobalTracer.get().getTraceId());
StringBuffer requestHeader = new StringBuffer("");
StringBuffer responseHeader = new StringBuffer("");
Enumeration<String> headerNames = requestWrapper.getHeaderNames();
int count = 0;
while (headerNames.hasMoreElements()) {
if (count==0){
requestHeader.append("{");
}else{
requestHeader.append(",\n");
}
String headerName = headerNames.nextElement();
requestHeader.append("\"").append(headerName).append("\":").append("\"").append(requestWrapper.getHeader(headerName).replace("\"","")).append("\"");
count ++;
}
if (count>0){
requestHeader.append("}");
}
count = 0;
for (String headerName : wrapper.getHeaderNames()) {
if (count==0){
responseHeader.append("{");
}else{
responseHeader.append(",\n");
}
responseHeader.append("\"").append(headerName).append("\":").append("\"").append(wrapper.getHeader(headerName)).append("\"");
count ++;
}
ContentCachingRequestWrapper requestWrapper = null;
if (requestBodyEnabled){
requestWrapper = new ContentCachingRequestWrapper(request);
}
String contextType = requestWrapper==null?request.getContentType():requestWrapper.getContentType();
String methodType = requestWrapper==null?request.getMethod():requestWrapper.getMethod();
String url = requestWrapper==null?request.getRequestURI():requestWrapper.getRequestURI();
boolean hasBlackList = false;
if (!isEmpty(Config.get().getTracerResponseBodyBlackListUrls())){
hasBlackList = Arrays.stream(Config.get().getTracerResponseBodyBlackListUrls().split(",")).anyMatch(uri -> uri.equals(url));
}
ContentCachingResponseWrapper responseWrapper = null;
boolean responseBodyEnabledTmp = responseBodyEnabled && !hasBlackList;
if (responseBodyEnabledTmp){
responseWrapper = new ContentCachingResponseWrapper(response);
}

if (count>0){
responseHeader.append("}");
}
span.setTag("request_header",requestHeader.toString());
span.setTag("response_header",responseHeader.toString());
filterChain.doFilter(requestWrapper==null?request:requestWrapper, responseWrapper==null?response:responseWrapper);

byte[] data =null;
if (responseBodyEnabledTmp) {
// 必须放到 filterChain.doFilter 之后,否则 responseWrapper.getContentAsByteArray() 为空
data = responseWrapper.getContentAsByteArray();
responseWrapper.copyBodyToResponse();
}
if (Config.get().isTracerRequestBodyEnabled() && "POST".equalsIgnoreCase(methodType) && contextType != null && (contextType.contains("application/json"))) {
if (tracerHeader) {
buildHeaderTags(span,requestWrapper==null?request:requestWrapper, responseWrapper==null?response:responseWrapper);
}

if (Config.get().isTracerRequestBodyEnabled()
&& "POST".equalsIgnoreCase(methodType)
&& contextType != null
&& (contextType.contains("application/json"))) {
span.setTag("request_body", new String(requestWrapper.getContentAsByteArray()));
}
log.debug("response.getContentType() >>>> :{},traceId:{},responseBodyEnabled:{}",wrapper.getContentType(),GlobalTracer.get().getTraceId(),Config.get().isTracerResponseBodyEnabled());
if (Config.get().isTracerResponseBodyEnabled()) {
if (wrapper.getContentType() != null && (wrapper.getContentType().contains("application/json") || wrapper.getContentType().contains("text/plain"))) {
byte[] data = wrapper.getContentAsByteArray();
span.setTag("response_body", new String(data));
int dataLength = data==null?0:data.length;
log.debug(
"traceId:{},spanId:{},dataLength:{},responseBodyEnabled:{}",
GlobalTracer.get().getTraceId(),
span.getSpanId(),
dataLength,
responseBodyEnabled);
if (responseBodyEnabledTmp) {
if (responseWrapper.getContentType() != null
&& (responseWrapper.getContentType().contains("application/json")
|| responseWrapper.getContentType().contains("text/plain"))) {
try {
if (dataLength < 1024 * 2) {
span.setTag("response_body", new String(data));
} else {
span.setTag("response_body", new String(data).substring(0, 1024 * 2 - 1));
}
} catch (Exception e) {
log.error(
"traceId:{},span:{},response_body",
GlobalTracer.get().getTraceId(),
span.getSpanId(),
e.getMessage());
}
}
}
} else {
filterChain.doFilter(request, response);
}


}
private boolean isEmpty(String str) {
return str == null || str.length() == 0;
}

private void buildHeaderTags(AgentSpan span,final HttpServletRequest request, final HttpServletResponse response) {
StringBuffer requestHeader = new StringBuffer("");
StringBuffer responseHeader = new StringBuffer("");
Enumeration<String> headerNames = request.getHeaderNames();
int count = 0;
while (headerNames.hasMoreElements()) {
if (count == 0) {
requestHeader.append("{");
} else {
requestHeader.append(",\n");
}
String headerName = headerNames.nextElement();
requestHeader
.append("\"")
.append(headerName)
.append("\":")
.append("\"")
.append(request.getHeader(headerName).replace("\"", ""))
.append("\"");
count++;
}
if (count > 0) {
requestHeader.append("}");
}
count = 0;
for (String headerName : response.getHeaderNames()) {
if (count == 0) {
responseHeader.append("{");
} else {
responseHeader.append(",\n");
}
responseHeader
.append("\"")
.append(headerName)
.append("\":")
.append("\"")
.append(response.getHeader(headerName))
.append("\"");
count++;
}

if (count > 0) {
responseHeader.append("}");
}
span.setTag("request_header", requestHeader.toString());
span.setTag("response_header", responseHeader.toString());
}
/**
* When a HandlerMapping matches a request, it sets HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE
* as an attribute on the request. This attribute is read by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ public final class TracerConfig {

public static final String TRACE_RESPONSE_BODY_ENABLED = "trace.response.body.enabled";

public static final String TRACE_RESPONSE_BODY_BLACKLIST_URLS = "trace.request.body.blacklist.urls";

public static final String TRACE_RESPONSE_BODY_ENCODING = "trace.response.body.encoding";

public static final String TRACE_DUBBO_PROVIDER_PROPAGATE_ENABLED =
Expand Down
Loading

0 comments on commit 1202fe0

Please sign in to comment.