SpringCloud Gateway之请求应答日志打印方式
Gateway请求应答日志打印
请求应答日志时在日常开发调试问题的重要手段之一,那么如何基于Spring Cloud Gateway做呢,请看我上代码。
第一步
创建RecorderServerHttpRequestDecorator,缓存请求参数,解决body只能读一次问题。
public class RecorderServerHttpRequestDecorator extends ServerHttpRequestDecorator {
private final List<DataBuffer> dataBuffers = new ArrayList<>();
public RecorderServerHttpRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
super.getBody().map(dataBuffer -> {
dataBuffers.add(dataBuffer);
return dataBuffer;
}).subscribe();
}
@Override
public Flux<DataBuffer> getBody() {
return copy();
}
private Flux<DataBuffer> copy() {
return Flux.fromIterable(dataBuffers)
.map(buf -> buf.factory().wrap(buf.asByteBuffer()));
}
}
第二步
创建访问日志全局过滤器,然后在此过滤器进行日志构造。
@Slf4j
public class AccessLogGlobalFilter implements GlobalFilter , Ordered {
private static final String REQUEST_PREFIX = "Request Info [ ";
private static final String REQUEST_TAIL = " ]";
private static final String RESPONSE_PREFIX = "Response Info [ ";
private static final String RESPONSE_TAIL = " ]";
private StringBuilder normalMsg = new StringBuilder();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
RecorderServerHttpRequestDecorator requestDecorator = new RecorderServerHttpRequestDecorator(request);
InetSocketAddress address = requestDecorator.getRemoteAddress();
HttpMethod method = requestDecorator.getMethod();
URI url = requestDecorator.getURI();
HttpHeaders headers = requestDecorator.getHeaders();
Flux<DataBuffer> body = requestDecorator.getBody();
//读取requestBody传参
AtomicReference<String> requestBody = new AtomicReference<>("");
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
requestBody.set(charBuffer.toString());
});
String requestParams = requestBody.get();
normalMsg.append(REQUEST_PREFIX);
normalMsg.append(";header=").append(headers);
normalMsg.append(";params=").append(requestParams);
normalMsg.append(";address=").append(address.getHostName() + address.getPort());
normalMsg.append(";method=").append(method.name());
normalMsg.append(";url=").append(url.getPath());
normalMsg.append(REQUEST_TAIL);
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory bufferFactory = response.bufferFactory();
normalMsg.append(RESPONSE_PREFIX);
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
// probably should reuse buffers
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
String responseResult = new String(content, Charset.forName("UTF-8"));
normalMsg.append("status=").append(this.getStatusCode());
normalMsg.append(";header=").append(this.getHeaders());
normalMsg.append(";responseResult=").append(responseResult);
normalMsg.append(RESPONSE_TAIL);
log.info(normalMsg.toString());
return bufferFactory.wrap(content);
}));
}
return super.writeWith(body); // if body is not a flux. never got there.
}
};
return chain.filter(exchange.mutate().request(requestDecorator).response(decoratedResponse).build());
}
@Override
public int getOrder() {
return -2;
}
}
最后结果:
Request Info [ ;header={cache-control=[no-cache], Postman-Token=[790488a5-a284-4a0e-968f-1b588cb26688], Content-Type=[application/json], User-Agent=[PostmanRuntime/3.0.9], Accept=[* public class GlobalCacheRequestBodyFilter implements GlobalFilter, Ordered { private static final Logger logger = LoggerFactory.getLogger(GlobalCacheRequestBodyFilter.class); private int order; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //logger.info("GlobalCacheRequestBodyFilter ..."); // 将 request body 中的内容 copy 一份,记录到 exchange 的一个自定义属性中 Object cachedRequestBodyObject = exchange.getAttributeOrDefault(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, null); // 如果已经缓存过,略过 if (cachedRequestBodyObject != null) { return chain.filter(exchange); } // 如果没有缓存过,获取字节数组存入 exchange 的自定义属性中 return DataBufferUtils.join(exchange.getRequest().getBody()) .map(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); return bytes; }).defaultIfEmpty(new byte[0]) .doOnNext(bytes -> exchange.getAttributes().put(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, bytes)) .then(chain.filter(exchange)); } @Override public int getOrder() { return this.order; } public GlobalCacheRequestBodyFilter(int order){ this.order = order; } }
编写全局日志拦截器代码
public class GlobalLogFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(GlobalLogFilter.class);
private int order;
private static final String REQUEST_PREFIX = "\n--------------------------------- Request Info -----------------------------";
private static final String REQUEST_TAIL = "\n-----------------------------------------------------------------------------";
private static final String RESPONSE_PREFIX = "\n--------------------------------- Response Info -----------------------------";
private static final String RESPONSE_TAIL = "\n-------------------------------------------------------------------------->>>";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long start = DateUtil.getCurrentTime();
StringBuilder reqMsg = new StringBuilder();
StringBuilder resMsg = new StringBuilder();
// 获取请求信息
ServerHttpRequest request = exchange.getRequest();
InetSocketAddress address = request.getRemoteAddress();
String method = request.getMethodValue();
URI uri = request.getURI();
HttpHeaders headers = request.getHeaders();
// 获取请求body
Object cachedRequestBodyObject = exchange.getAttributeOrDefault(ConstantFilter.CACHED_REQUEST_BODY_OBJECT_KEY, null);
byte[] body = (byte[]) cachedRequestBodyObject;
String params = new String(body);
// 获取请求query
Map queryMap = request.getQueryParams();
String query = JSON.toJSONString(queryMap);
// 拼接请求日志
reqMsg.append(REQUEST_PREFIX);
reqMsg.append("\n header=").append(headers);
reqMsg.append("\n query=").append(query);
reqMsg.append("\n params=").append(params);
reqMsg.append("\n address=").append(address.getHostName()).append(address.getPort());
reqMsg.append("\n method=").append(method);
reqMsg.append("\n url=").append(uri.getPath());
reqMsg.append(REQUEST_TAIL);
logger.info(reqMsg.toString()); // 打印入参日志
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory bufferFactory = response.bufferFactory();
resMsg.append(RESPONSE_PREFIX);
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
String responseResult = new String(content, Charset.forName("UTF-8"));
resMsg.append("\n status=").append(this.getStatusCode());
resMsg.append("\n header=").append(this.getHeaders());
resMsg.append("\n responseResult=").append(responseResult);
resMsg.append(RESPONSE_TAIL);
// 计算请求时间
long end = DateUtil.getCurrentTime();
long time = end - start;
resMsg.append("耗时ms:").append(time);
logger.info(resMsg.toString()); // 打印结果日志
return bufferFactory.wrap(content);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
@Override
public int getOrder() {
return this.order;
}
public GlobalLogFilter(int order){
this.order = order;
}
}
在代码中配置全局拦截器
@Configuration
public class GatewayConfig {
@Bean
public GlobalLogFilter globalLogFilter(){
// 该值越小权重却大,所以应根据具体项目配置。需要尽早的获取到参数,一般会是一个比较小的值
return new GlobalLogFilter(-20);
}
// 其他的路由配置 ...
}
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。