Skip to content

Instantly share code, notes, and snippets.

@MdShohanurRahman
Created July 21, 2024 04:43
Show Gist options
  • Save MdShohanurRahman/0d27148e7a1c9e05f6542f11c2ad98a0 to your computer and use it in GitHub Desktop.
Save MdShohanurRahman/0d27148e7a1c9e05f6542f11c2ad98a0 to your computer and use it in GitHub Desktop.
Spring Cloud Gateway Custom Logging
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
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.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
@Slf4j
@RequiredArgsConstructor
@Component
public class LoggingFilter implements GlobalFilter, Ordered {
private final ObjectMapper objectMapper;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
return DataBufferUtils.join(request.getBody())
.defaultIfEmpty(bufferFactory.wrap(new byte[0]))
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
// Log the request details
logRequestDetails(request, bytes);
// Mutate the request to include the body
ServerHttpRequest mutatedRequest = request.mutate().build();
DataBuffer bodyDataBuffer = bufferFactory.wrap(bytes);
Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
mutatedRequest = new ServerHttpRequestDecorator(mutatedRequest) {
@Override
public Flux<DataBuffer> getBody() {
return bodyFlux;
}
};
// Decorate the response
ServerHttpResponseDecorator decoratedResponse = getResponseDecorator(exchange);
return chain.filter(exchange.mutate().request(mutatedRequest).response(decoratedResponse).build());
});
}
private void logRequestDetails(ServerHttpRequest request, byte[] requestBodyBytes) {
String requestUri = request.getURI().toString();
String method = request.getMethod().name();
HttpHeaders headers = request.getHeaders();
String headersString = headers.entrySet().stream()
.map(entry -> entry.getKey() + ": " + String.join(", ", entry.getValue()))
.collect(Collectors.joining("\n"));
log.info("Request URI: {}", requestUri);
log.info("Request Method: {}", method);
log.info("Request Headers: \n{}", headersString);
log.info("Request QueryParams: \n{}", request.getQueryParams());
String requestBody = new String(requestBodyBytes, StandardCharsets.UTF_8);
if (headers.getContentType() != null && headers.getContentType().includes(MediaType.APPLICATION_JSON)) {
try {
Object json = objectMapper.readValue(requestBody, Object.class);
requestBody = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
} catch (Exception e) {
log.error("Failed to pretty print JSON request body", e);
}
}
log.info("Request Body: \n{}", requestBody);
}
private ServerHttpResponseDecorator getResponseDecorator(ServerWebExchange exchange) {
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
return new ServerHttpResponseDecorator(originalResponse) {
@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[] responseBytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(responseBytes);
DataBufferUtils.release(dataBuffer);
String responseBody = new String(responseBytes, StandardCharsets.UTF_8);
// Pretty print JSON responses
if (originalResponse.getHeaders().getContentType() != null &&
originalResponse.getHeaders().getContentType().includes(MediaType.APPLICATION_JSON)) {
try {
Object json = objectMapper.readValue(responseBody, Object.class);
responseBody = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
} catch (Exception e) {
log.error("Failed to pretty print JSON response", e);
}
}
log.info("Response Code: {}", exchange.getResponse().getStatusCode());
log.info("Response Body: \n{}", responseBody);
return bufferFactory.wrap(responseBytes);
}));
}
return super.writeWith(body);
}
};
}
@Override
public int getOrder() {
return -2;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment