Cross-cutting concerns such as logging and tracing are crucial in microservices architectures to ensure observability and maintainability. Implementing these concerns at the gateway level allows uniform handling of requests and responses before they reach backend services.
Implementation
We’ll create a package com.learnitweb.gatewayserver.filters
and add following classes:
FilterUtility.java
package com.learnitweb.gatewayserver.filters; import java.util.List; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; @Component public class FilterUtility { public static final String CORRELATION_ID = "eshop-correlation-id"; public String getCorrelationId(HttpHeaders requestHeaders) { if (requestHeaders.get(CORRELATION_ID) != null) { List<String> requestHeaderList = requestHeaders.get(CORRELATION_ID); return requestHeaderList.stream().findFirst().get(); } else { return null; } } public ServerWebExchange setRequestHeader(ServerWebExchange exchange, String name, String value) { return exchange.mutate().request(exchange.getRequest().mutate().header(name, value).build()).build(); } public ServerWebExchange setCorrelationId(ServerWebExchange exchange, String correlationId) { return this.setRequestHeader(exchange, CORRELATION_ID, correlationId); } }
Key functions:
getCorrelationId(HttpHeaders requestHeaders)
: Retrieves theeshop-correlation-id
from request headers.setRequestHeader(ServerWebExchange exchange, String name, String value)
: Modifies the request to add/update a specific header.setCorrelationId(ServerWebExchange exchange, String correlationId)
: CallssetRequestHeader()
to ensure the correlation ID is set in the request.
RequestTraceFilter.java
package com.learnitweb.gatewayserver.filters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.annotation.Order; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Order(1) @Component public class RequestTraceFilter implements GlobalFilter { private static final Logger logger = LoggerFactory.getLogger(RequestTraceFilter.class); @Autowired FilterUtility filterUtility; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { HttpHeaders requestHeaders = exchange.getRequest().getHeaders(); if (isCorrelationIdPresent(requestHeaders)) { logger.debug("eshop-correlation-id found in RequestTraceFilter : {}", filterUtility.getCorrelationId(requestHeaders)); } else { String correlationID = generateCorrelationId(); exchange = filterUtility.setCorrelationId(exchange, correlationID); logger.debug("eshop-correlation-id generated in RequestTraceFilter : {}", correlationID); } return chain.filter(exchange); } private boolean isCorrelationIdPresent(HttpHeaders requestHeaders) { if (filterUtility.getCorrelationId(requestHeaders) != null) { return true; } else { return false; } } private String generateCorrelationId() { return java.util.UUID.randomUUID().toString(); } }
- Checks if a correlation ID (
eshop-correlation-id
) exists in request headers.- If present, logs the existing ID.
- If absent, generates a new UUID, adds it to the request, and logs it.
- Uses
FilterUtility
to retrieve and set the correlation ID. - Passes the request to the next filter in the chain.
ResponseTraceFilter.java
package com.learnitweb.gatewayserver.filters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import reactor.core.publisher.Mono; @Configuration public class ResponseTraceFilter { private static final Logger logger = LoggerFactory.getLogger(ResponseTraceFilter.class); @Autowired FilterUtility filterUtility; @Bean public GlobalFilter postGlobalFilter() { return (exchange, chain) -> { return chain.filter(exchange).then(Mono.fromRunnable(() -> { HttpHeaders requestHeaders = exchange.getRequest().getHeaders(); String correlationId = filterUtility.getCorrelationId(requestHeaders); logger.debug("Updated the correlation id to the outbound headers: {}", correlationId); exchange.getResponse().getHeaders().add(filterUtility.CORRELATION_ID, correlationId); })); }; } }
The ResponseTraceFilter
is a post-processing filter in Spring Cloud Gateway that adds a correlation ID to the outbound response headers.
- Intercepts responses after processing the request.
- Retrieves the correlation ID from the request headers using
FilterUtility
. - Logs the correlation ID for debugging.
- Adds the correlation ID to the response headers to maintain traceability.
application.yml
Add the following to the application.yml
:
logging: level: com: learnitweb: gatewayserver: DEBUG
Now, read the eshop-correlation-id
header in productservice
and log using logger
.
@RestController @RequestMapping("/api") public class InventoryController { @Autowired private InventoryFeignClient inventoryFeignClient; private static final Logger logger = LoggerFactory.getLogger(InventoryController.class); @GetMapping("/inventory/{name}") public ResponseEntity<?> getProductInventory(@RequestHeader("eshop-correlation-id") String correlationId, @PathVariable String name) { logger.debug("eshop-correlation-id found: {} ", correlationId); ResponseEntity<InventoryDto> result = inventoryFeignClient.getProductInventory(name, correlationId); return result; } }
Make RequestHeader
changes to the feign client.
@FeignClient(name = "InventoryService") public interface InventoryFeignClient { @GetMapping("/api/inventory/{name}") public ResponseEntity<InventoryDto> getProductInventory(@PathVariable("name") String name, @RequestHeader("eshop-correlation-id") String correlationId); }
Make changes to the inventoryservice
for RequestHeader
.
@RestController @RequestMapping("/api") public class InventoryController { private static final Logger logger = LoggerFactory.getLogger(InventoryController.class); @GetMapping("/inventory/{name}") public ResponseEntity<?> getProductInventory(@PathVariable String name, @RequestHeader("eshop-correlation-id") String correlationId) { logger.debug("eshop-correlation-id found: {} ", correlationId); if (name.equals("car")) { InventoryDto inventory = new InventoryDto(); inventory.setProductName("some car"); inventory.setQuantityRemaining(100); return new ResponseEntity<InventoryDto>(inventory, HttpStatus.OK); } return new ResponseEntity(null, HttpStatus.OK); } }
In gateway server we see something like the following:
eshop-correlation-id generated in RequestTraceFilter : 33614ca0-3989-42fd-b7f8-aeaddcf3caaf Updated the correlation id to the outbound headers: 33614ca0-3989-42fd-b7f8-aeaddcf3caaf