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-idfrom 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
FilterUtilityto 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
