Request response log is an important means for daily development and debugging to locate problems. After the introduction of SpringCloud Gateway in microservices, we hope to uniformly collect logs at the Gateway layer.
This section implements the following two functions:
- Gets the input and output parameters of the request, encapsulated into a custom log
- Logs are sent to MongoDB for storage
Gets input and output parameters
- First let’s define a log body
@Data
public class GatewayLog {
/** Access the instance */
private String targetServer;
/** Request path */
private String requestPath;
/** Request method */
private String requestMethod;
/ * * * / agreement
private String schema;
/** Request body */
private String requestBody;
/** Response body */
private String responseData;
Request IP / * * * /
private String ip;
/** Request time */
private Date requestTime;
/** Response time */
private Date responseTime;
/** Execution time */
private long executeTime;
}
Copy the code
- 【 Key 】 Define log filters on the gateway and obtain input and output parameters
/** * Log filter, used to record logs *@author jianzh5
* @date2020/3/24 ready * /
@Slf4j
@Component
public class AccessLogFilter implements GlobalFilter.Ordered {
@Autowired
private AccessLogService accessLogService;
private finalList<HttpMessageReader<? >> messageReaders = HandlerStrategies.withDefaults().messageReaders();The order must be <-1, otherwise the standard NettyWriteResponseFilter will send the response before your filter has a chance to be called@return* /
@Override
public int getOrder(a) {
return -100;
}
@Override
@SuppressWarnings("unchecked")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// Request path
String requestPath = request.getPath().pathWithinApplication().value();
Route route = getGatewayRoute(exchange);
String ipAddress = WebUtils.getServerHttpRequestIpAddress(request);
GatewayLog gatewayLog = new GatewayLog();
gatewayLog.setSchema(request.getURI().getScheme());
gatewayLog.setRequestMethod(request.getMethodValue());
gatewayLog.setRequestPath(requestPath);
gatewayLog.setTargetServer(route.getId());
gatewayLog.setRequestTime(new Date());
gatewayLog.setIp(ipAddress);
MediaType mediaType = request.getHeaders().getContentType();
if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){
return writeBodyLog(exchange, chain, gatewayLog);
}else{
returnwriteBasicLog(exchange, chain, gatewayLog); }}private Mono<Void> writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) {
StringBuilder builder = new StringBuilder();
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ","));
}
accessLog.setRequestBody(builder.toString());
// Get the response body
ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog);
return chain.filter(exchange.mutate().response(decoratedResponse).build())
.then(Mono.fromRunnable(() -> {
// Prints logs
writeAccessLog(accessLog);
}));
}
/** * The request body can only be read once. org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory *@param exchange
* @param chain
* @param gatewayLog
* @return* /
@SuppressWarnings("unchecked")
private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) {
ServerRequest serverRequest = ServerRequest.create(exchange,messageReaders);
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
.flatMap(body ->{
gatewayLog.setRequestBody(body);
return Mono.just(body);
});
// Use BodyInserter to insert body(support for modifying body) to avoid request body fetching only once
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
// the new content type will be computed by bodyInserter
// and then set in the request decorator
headers.remove(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage,new BodyInserterContext())
.then(Mono.defer(() -> {
// Rewrap the request
ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage);
// Record the response log
ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog);
// Record normal
returnchain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build()) .then(Mono.fromRunnable(() - > {// Prints logs
writeAccessLog(gatewayLog);
}));
}));
}
/** * Prints logs *@author javadaily
* @date 2021/3/24 14:53
* @paramGatewayLog gatewayLog */
private void writeAccessLog(GatewayLog gatewayLog) {
log.info(gatewayLog.toString());
}
private Route getGatewayRoute(ServerWebExchange exchange) {
return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
}
/** * request decorator to recalculate headers *@param exchange
* @param headers
* @param outputMessage
* @return* /
private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
return new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public HttpHeaders getHeaders(a) {
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
} else {
// TODO:This causes a 'HTTP/1.1 411 Length Required' // on
// httpbin.org
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody(a) {
returnoutputMessage.getBody(); }}; }Use DataBufferFactory to solve the problem of response body segmentation transfer. * /
private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) {
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory bufferFactory = response.bufferFactory();
return new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Date responseTime = new Date();
gatewayLog.setResponseTime(responseTime);
// Calculate the execution time
long executeTime = (responseTime.getTime() - gatewayLog.getRequestTime().getTime());
gatewayLog.setExecuteTime(executeTime);
// Get the response type, print it if it is JSON
String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
if (ObjectUtil.equal(this.getStatusCode(), HttpStatus.OK)
&& StringUtil.isNotBlank(originalResponseContentType)
&& originalResponseContentType.contains("application/json")) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
// Merge multiple stream sets to solve the return body segment transmission
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
// Free up memory
DataBufferUtils.release(join);
String responseResult = new String(content, StandardCharsets.UTF_8);
gatewayLog.setResponseData(responseResult);
returnbufferFactory.wrap(content); })); }}// if body is not a flux. never got there.
return super.writeWith(body); }}; }}Copy the code
Long code is recommended to copy directly to the editor, note the following key points:
The getOrder() method must return a value <-1, or the standard NettyWriteResponseFilter will send the response before your filter is called, that is, the method to get the backend response parameters will not be executed
We have already obtained the input and output parameters of the request through the above two steps. WriteAccessLog () is used to write the request to a log file. You can send the request to Postman to observe the log.
Store the log
If you need to persist logs to facilitate later retrieval, you can consider storing logs in MongoDB. The implementation process is simple. (installation mongo can refer to this article: combat | mongo’s installation configuration)
- Introducing the mongo
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
Copy the code
Because Gateway is based on WebFlux, we need to choose the Reactive version.
- Add the corresponding annotation to the GatewayLog
@Data
@Document
public class GatewayLog {
@Id
privateString id; . }Copy the code
- Establish AccessLogRepository
@Repository
public interface AccessLogRepository extends ReactiveMongoRepository<GatewayLog.String> {}Copy the code
- Set up the Service
public interface AccessLogService {
/** * Save AccessLog *@paramGatewayLog Request response log *@returnResponse log */
Mono<GatewayLog> saveAccessLog(GatewayLog gatewayLog);
}
Copy the code
- Creating an implementation class
@Service
public class AccessLogServiceImpl implements AccessLogService {
@Autowired
private AccessLogRepository accessLogRepository;
@Override
public Mono<GatewayLog> saveAccessLog(GatewayLog gatewayLog) {
returnaccessLogRepository.insert(gatewayLog); }}Copy the code
- Add the MongoDB configuration in the Nacos configuration center
spring:
data:
mongodb:
host: xxx.xx.x.xx
port: 27017
database: accesslog
username: accesslog
password: xxxxxx
Copy the code
- Execute the request, open the MongoDB client, and view the log result
SpringCloud Alibaba micro service practice series source code has been uploaded to GitHub, need to pay attention to this public account JAVA daily record and reply to the keyword 2214 to obtain the source address.