优化
This commit is contained in:
@@ -9,8 +9,11 @@ import org.slf4j.MDC;
|
||||
public final class CorrelationContext {
|
||||
public static final String REQUEST_ID = "requestId";
|
||||
public static final String TRACE_ID = "traceId";
|
||||
public static final String SPAN_ID = "spanId";
|
||||
public static final String TRACE_PARENT = "traceparent";
|
||||
public static final String REQUEST_ID_HEADER = "X-Request-Id";
|
||||
public static final String TRACE_ID_HEADER = "X-Trace-Id";
|
||||
public static final String TRACE_PARENT_HEADER = TraceParent.HEADER;
|
||||
|
||||
private CorrelationContext() {}
|
||||
|
||||
@@ -22,6 +25,14 @@ public final class CorrelationContext {
|
||||
return value(request, TRACE_ID);
|
||||
}
|
||||
|
||||
public static String spanId(HttpServletRequest request) {
|
||||
return value(request, SPAN_ID);
|
||||
}
|
||||
|
||||
public static String traceParent(HttpServletRequest request) {
|
||||
return value(request, TRACE_PARENT);
|
||||
}
|
||||
|
||||
private static String value(HttpServletRequest request, String key) {
|
||||
Object attribute = request.getAttribute(key);
|
||||
return attribute == null ? MDC.get(key) : attribute.toString();
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -25,24 +26,60 @@ public class CorrelationIdFilter extends OncePerRequestFilter {
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
String requestId = acceptedOrGenerated(request.getHeader(CorrelationContext.REQUEST_ID_HEADER), false);
|
||||
String traceId = acceptedOrGenerated(request.getHeader(CorrelationContext.TRACE_ID_HEADER), true);
|
||||
String requestId = requestId(request);
|
||||
TraceParent traceParent = traceParent(request);
|
||||
String traceId = traceParent.traceId();
|
||||
request.setAttribute(CorrelationContext.REQUEST_ID, requestId);
|
||||
request.setAttribute(CorrelationContext.TRACE_ID, traceId);
|
||||
request.setAttribute(CorrelationContext.SPAN_ID, traceParent.parentId());
|
||||
request.setAttribute(CorrelationContext.TRACE_PARENT, traceParent.value());
|
||||
response.setHeader(CorrelationContext.REQUEST_ID_HEADER, requestId);
|
||||
response.setHeader(CorrelationContext.TRACE_ID_HEADER, traceId);
|
||||
response.setHeader(CorrelationContext.TRACE_PARENT_HEADER, traceParent.value());
|
||||
|
||||
try (MDC.MDCCloseable ignoredRequest = MDC.putCloseable(CorrelationContext.REQUEST_ID, requestId);
|
||||
MDC.MDCCloseable ignoredTrace = MDC.putCloseable(CorrelationContext.TRACE_ID, traceId)) {
|
||||
MDC.MDCCloseable ignoredTrace = MDC.putCloseable(CorrelationContext.TRACE_ID, traceId);
|
||||
MDC.MDCCloseable ignoredSpan = MDC.putCloseable(CorrelationContext.SPAN_ID, traceParent.parentId())) {
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private String acceptedOrGenerated(String candidate, boolean compact) {
|
||||
if (candidate != null && SAFE_ID.matcher(candidate).matches()) {
|
||||
return candidate;
|
||||
private String requestId(HttpServletRequest request) {
|
||||
HeaderValue header = singleHeader(request, CorrelationContext.REQUEST_ID_HEADER, 128);
|
||||
if (header.valid() && SAFE_ID.matcher(header.value()).matches()) {
|
||||
return header.value();
|
||||
}
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
private TraceParent traceParent(HttpServletRequest request) {
|
||||
HeaderValue standard = singleHeader(request, CorrelationContext.TRACE_PARENT_HEADER,
|
||||
TraceParent.HEADER_LENGTH);
|
||||
if (standard.present()) {
|
||||
if (standard.valid()) {
|
||||
return TraceParent.parse(standard.value()).map(TraceParent::childOf)
|
||||
.orElseGet(() -> TraceParent.newRoot(null));
|
||||
}
|
||||
return TraceParent.newRoot(null);
|
||||
}
|
||||
|
||||
HeaderValue legacy = singleHeader(request, CorrelationContext.TRACE_ID_HEADER, 32);
|
||||
return TraceParent.newRoot(legacy.valid() ? legacy.value() : null);
|
||||
}
|
||||
|
||||
private HeaderValue singleHeader(HttpServletRequest request, String name, int maxLength) {
|
||||
Enumeration<String> values = request.getHeaders(name);
|
||||
if (values == null || !values.hasMoreElements()) {
|
||||
return HeaderValue.missing();
|
||||
}
|
||||
String value = values.nextElement();
|
||||
boolean duplicate = values.hasMoreElements();
|
||||
return new HeaderValue(true, !duplicate && value != null && value.length() <= maxLength, value);
|
||||
}
|
||||
|
||||
private record HeaderValue(boolean present, boolean valid, String value) {
|
||||
private static HeaderValue missing() {
|
||||
return new HeaderValue(false, false, null);
|
||||
}
|
||||
String id = UUID.randomUUID().toString();
|
||||
return compact ? id.replace("-", "") : id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.baiyue.common.web;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Strict W3C {@code traceparent} version 00 value.
|
||||
*
|
||||
* <p>This type deliberately accepts only the currently frozen version-00 shape. Unsupported,
|
||||
* ambiguous or malformed input must start a new trace instead of being partially trusted.</p>
|
||||
*/
|
||||
public record TraceParent(String traceId, String parentId, String traceFlags) {
|
||||
public static final String HEADER = "traceparent";
|
||||
public static final int HEADER_LENGTH = 55;
|
||||
|
||||
private static final Pattern VERSION_00 = Pattern.compile(
|
||||
"00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})");
|
||||
private static final Pattern LEGACY_TRACE_ID = Pattern.compile("[0-9A-Fa-f]{32}");
|
||||
private static final String ZERO_TRACE_ID = "0".repeat(32);
|
||||
private static final String ZERO_PARENT_ID = "0".repeat(16);
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
private static final HexFormat HEX = HexFormat.of();
|
||||
|
||||
public TraceParent {
|
||||
if (!validTraceId(traceId) || !validParentId(parentId)
|
||||
|| traceFlags == null || !traceFlags.matches("[0-9a-f]{2}")) {
|
||||
throw new IllegalArgumentException("Invalid W3C traceparent components");
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses one exact, lowercase W3C version-00 header value. */
|
||||
public static Optional<TraceParent> parse(String value) {
|
||||
if (value == null || value.length() != HEADER_LENGTH) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Matcher matcher = VERSION_00.matcher(value);
|
||||
if (!matcher.matches() || !validTraceId(matcher.group(1)) || !validParentId(matcher.group(2))) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(new TraceParent(matcher.group(1), matcher.group(2), matcher.group(3)));
|
||||
}
|
||||
|
||||
/** Creates the local inbound context for a valid remote parent. */
|
||||
public static TraceParent childOf(TraceParent remoteParent) {
|
||||
return new TraceParent(remoteParent.traceId(), randomNonZero(8), remoteParent.traceFlags());
|
||||
}
|
||||
|
||||
/** Creates a new root context, optionally reusing a valid legacy trace ID. */
|
||||
public static TraceParent newRoot(String legacyTraceId) {
|
||||
String traceId = validLegacyTraceId(legacyTraceId)
|
||||
? legacyTraceId.toLowerCase(java.util.Locale.ROOT) : randomNonZero(16);
|
||||
return new TraceParent(traceId, randomNonZero(8), "00");
|
||||
}
|
||||
|
||||
public static boolean validLegacyTraceId(String value) {
|
||||
return value != null && LEGACY_TRACE_ID.matcher(value).matches()
|
||||
&& !ZERO_TRACE_ID.equalsIgnoreCase(value);
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return "00-" + traceId + '-' + parentId + '-' + traceFlags;
|
||||
}
|
||||
|
||||
private static boolean validTraceId(String value) {
|
||||
return value != null && value.matches("[0-9a-f]{32}") && !ZERO_TRACE_ID.equals(value);
|
||||
}
|
||||
|
||||
private static boolean validParentId(String value) {
|
||||
return value != null && value.matches("[0-9a-f]{16}") && !ZERO_PARENT_ID.equals(value);
|
||||
}
|
||||
|
||||
private static String randomNonZero(int byteCount) {
|
||||
byte[] bytes = new byte[byteCount];
|
||||
String value;
|
||||
do {
|
||||
RANDOM.nextBytes(bytes);
|
||||
value = HEX.formatHex(bytes);
|
||||
} while (value.chars().allMatch(character -> character == '0'));
|
||||
return value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user