Last active
May 27, 2021 22:38
-
-
Save matzegebbe/bf631b2d3ab6d55f58f4b6c1d3511189 to your computer and use it in GitHub Desktop.
SpringCloudGatewayLogging
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ApiGatewayApplication { | |
private static final Logger LOGGER = LoggerFactory.getLogger(ApiGatewayApplication.class); | |
public static void main(String[] args) { | |
SpringApplication.run(ApiGatewayApplication.class, args); | |
} | |
@Bean | |
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { | |
return builder.routes() | |
.route("413ead0e-ee90-11e9-adcd-d3c2baeefbaf", r -> r | |
.path("/413ead0e-ee90-11e9-adcd-d3c2baeefbaf/") | |
.and() | |
.readBody(String.class, i -> !StringUtils.isEmpty(i)) | |
.uri("http://localhost:8081")) | |
.build(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.hellmann.apigateway.filter; | |
import org.reactivestreams.Publisher; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.cloud.gateway.filter.GatewayFilterChain; | |
import org.springframework.cloud.gateway.filter.GlobalFilter; | |
import org.springframework.core.Ordered; | |
import org.springframework.core.io.buffer.DataBuffer; | |
import org.springframework.core.io.buffer.DataBufferFactory; | |
import org.springframework.core.io.buffer.DataBufferUtils; | |
import org.springframework.http.HttpHeaders; | |
import org.springframework.http.MediaType; | |
import org.springframework.http.server.reactive.ServerHttpRequest; | |
import org.springframework.http.server.reactive.ServerHttpResponse; | |
import org.springframework.http.server.reactive.ServerHttpResponseDecorator; | |
import org.springframework.stereotype.Component; | |
import org.springframework.util.MultiValueMap; | |
import org.springframework.web.server.ServerWebExchange; | |
import reactor.core.publisher.Flux; | |
import reactor.core.publisher.Mono; | |
import java.net.URI; | |
import java.nio.charset.StandardCharsets; | |
import java.util.List; | |
@Component | |
public class DetailedRequestResponseLogFilter implements GlobalFilter, Ordered { | |
private static final String MAGIC_HEADER = "x-debug"; | |
private static final Logger LOGGER = LoggerFactory.getLogger(DetailedRequestResponseLogFilter.class); | |
private static final String START_TIME = "startTime"; | |
private static final String HTTP_SCHEME = "http"; | |
private static final String HTTPS_SCHEME = "https"; | |
@Override | |
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { | |
List<String> debugHeader = exchange.getRequest().getHeaders().get(MAGIC_HEADER); | |
if (!LOGGER.isDebugEnabled() && debugHeader == null) { | |
// DO NOTHING | |
return chain.filter(exchange); | |
} | |
ServerHttpRequest request = exchange.getRequest(); | |
URI requestURI = request.getURI(); | |
String scheme = requestURI.getScheme(); | |
if (debugHeader != null) { | |
String debugHeaderContent = debugHeader.get(0); | |
if (!debugHeaderContent.equalsIgnoreCase("true") && !requestURI.getPath().toLowerCase().contains(debugHeaderContent.toLowerCase())) { | |
return chain.filter(exchange); | |
} | |
} | |
if ((!HTTP_SCHEME.equalsIgnoreCase(scheme) && !HTTPS_SCHEME.equals(scheme))) { | |
return chain.filter(exchange); | |
} | |
long startTime = System.currentTimeMillis(); | |
exchange.getAttributes().put(START_TIME, startTime); | |
logRequest(request, exchange.getAttribute("cachedRequestBodyObject")); | |
return chain.filter(exchange.mutate().response(logResponse(exchange)).build()); | |
} | |
@Override | |
public int getOrder() { | |
return Integer.MIN_VALUE; | |
} | |
private void logRequest(ServerHttpRequest request, String body) { | |
URI requestURI = request.getURI(); | |
String scheme = requestURI.getScheme(); | |
HttpHeaders headers = request.getHeaders(); | |
LOGGER.info("Request Scheme:{},Path:{}", scheme, requestURI.getPath()); | |
LOGGER.info("Request Method:{},IP:{},Host:{}", request.getMethod(), request.getRemoteAddress(), requestURI.getHost()); | |
headers.forEach((key, value) -> LOGGER.debug("Request Headers:Key->{},Value->{}", key, value)); | |
MultiValueMap<String, String> queryParams = request.getQueryParams(); | |
if (!queryParams.isEmpty()) { | |
queryParams.forEach((key, value) -> LOGGER.info("Request Query Param :Key->({}),Value->({})", key, value)); | |
} | |
MediaType contentType = headers.getContentType(); | |
long length = headers.getContentLength(); | |
LOGGER.info("Request ContentType:{},Content Length:{}", contentType, length); | |
if (body != null) { | |
LOGGER.info("Request Body:{}", body); | |
} | |
} | |
private ServerHttpResponseDecorator logResponse(ServerWebExchange exchange) { | |
ServerHttpResponse origResponse = exchange.getResponse(); | |
Long startTime = exchange.getAttribute(START_TIME); | |
LOGGER.info("Response HttpStatus:{}", origResponse.getStatusCode()); | |
HttpHeaders headers = origResponse.getHeaders(); | |
headers.forEach((key, value) -> LOGGER.debug("[RequestLogFilter]Headers:Key->{},Value->{}", key, value)); | |
MediaType contentType = headers.getContentType(); | |
long length = headers.getContentLength(); | |
LOGGER.info("Response ContentType:{},Content Length:{}", contentType, length); | |
Long executeTime = (System.currentTimeMillis() - startTime); | |
LOGGER.info("Response Original Path:{},Cost:{} ms", exchange.getRequest().getURI().getPath(), executeTime); | |
DataBufferFactory bufferFactory = origResponse.bufferFactory(); | |
return new ServerHttpResponseDecorator(origResponse) { | |
@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 -> { | |
try { | |
byte[] content = new byte[dataBuffer.readableByteCount()]; | |
dataBuffer.read(content); | |
var bodyContent = new String(content, StandardCharsets.UTF_8); | |
LOGGER.info("Response:{}", bodyContent); | |
return bufferFactory.wrap(content); | |
} finally { | |
DataBufferUtils.release(dataBuffer); | |
} | |
})); | |
} | |
return super.writeWith(body); | |
} | |
}; | |
} | |
} |
Thanks. Never had trouble with that on prod. But there
there was recently a bug in Spring Cloud Gateway and a Hoxton release (netty) that caused sockets not to close
but i added the DataBufferUtils.release(dataBuffer);
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The response logging part will cause out of direct memory error since data buffer is not being released