Skip to content

Lab#28 Cross cutting concerns – tracing and logging

In this lab we will add custom filters in the gateway to generate a correlation id Step#1 Create a new package with classes as shown (code given).

Custom Filters

Figure 1. Customer Filters
FilterUtility.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.tus.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 = "tusbank-correlation-id";

    public String getCorrelationId(HttpHeaders requestHeaders) {
        if (requestHeaders.get(CORRELATION_ID) != null) {
            List<String> header = requestHeaders.get(CORRELATION_ID);
            return header.stream().findFirst().get();
        } else {
            return null;
        }
    }

    public ServerWebExchange setCorrelationId(ServerWebExchange exchange, String correlationId) {
        return this.setRequestHeader(exchange, CORRELATION_ID, correlationId);
    }

    public ServerWebExchange setRequestHeader(ServerWebExchange exchange, String name, String value) {
        return exchange.mutate().request(exchange.getRequest().mutate().header(name, value).build()).build();
    }
}
RequestTraceFilter.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.tus.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.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.annotation.Order;
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) {
        String correlationId = filterUtility.getCorrelationId(exchange.getRequest().getHeaders());
        if (correlationId != null) {
            logger.debug("Tusbank-correlation-id found in the incoming request: {}. ", correlationId);
        } else {
            correlationId = generateCorrelationId();
            logger.debug("Tusbank-correlation-id generated: {}.", correlationId);
        }

        exchange = filterUtility.setCorrelationId(exchange, correlationId);
        return chain.filter(exchange);
    }

    private String generateCorrelationId() {
        return java.util.UUID.randomUUID().toString();
    }
}
ResponseTraceFilter.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.tus.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.Configuration;
import reactor.core.publisher.Mono;

@Configuration
public class ResponseTraceFilter {

    private static final Logger logger = LoggerFactory.getLogger(ResponseTraceFilter.class);

    @Autowired
    FilterUtility filterUtility;

    @org.springframework.context.annotation.Bean
    GlobalFilter postGlobalFilter() {
        return (exchange, chain) -> {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                String correlationId = filterUtility.getCorrelationId(exchange.getRequest().getHeaders());
                logger.debug("Updated the tusbank-correlation-id in the response headers: {}", correlationId);
                exchange.getResponse().getHeaders().add(FilterUtility.CORRELATION_ID, correlationId);
            }));
        };
    }
}

Step#2 Update the .yml file for the gateway with the logging information to allow all logger statements of type DEBUG to be logged.

Gateway Server: application.yml
40
41
42
43
44
45
46
47
server:
  port: 8072

logging:
  level:
    com:
      tus:
        gatewayserver: DEBUG

Step#3 We need to make changes inside the individual microservices (accounts, loans, cards) because the gateway is going to send a new request header with the correlationid value which the services need to receive the request header that the gateway is going to forward with the request. We can use the customercontroller (fetchCustomerDetails) to show this since it in turn invokes the cards and loans.

Accounts: CustomerController.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.tus.accounts.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.tus.accounts.dto.CustomerDetailsDto;
import com.tus.accounts.service.ICustomersService;

import jakarta.validation.constraints.Pattern;

@RestController
@RequestMapping(path = "/api", produces = MediaType.APPLICATION_JSON_VALUE)
@Validated
public class CustomerController {

    private static final Logger logger = LoggerFactory.getLogger(CustomerController.class);
    private final ICustomersService iCustomerService;

    public CustomerController(ICustomersService iCustomerService) {
        this.iCustomerService = iCustomerService;
    }

    @GetMapping("/customers")
    public ResponseEntity<CustomerDetailsDto> fetchCustomerDetails(
            @RequestHeader("tusbank-correlation-id") String correlationId, 
            @RequestParam @Pattern(regexp = "(^$|[0-9]{10})", message = "Mobile Number must be 10 digits") String mobileNumber) {
        logger.debug("TusBank-correlation-id: found:{}", correlationId);
        CustomerDetailsDto customerDetailsDto = iCustomerService.fetchCustomerDetails(mobileNumber, correlationId);
        return ResponseEntity.ok(customerDetailsDto);
    }
}
Accounts: ICustomersService.java
1
2
3
4
5
6
7
8
package com.tus.accounts.service;

import com.tus.accounts.dto.CustomerDetailsDto;

public interface ICustomersService {

    CustomerDetailsDto fetchCustomerDetails(String mobileNumber, String correlationId);
}
Accounts: ICustomerServiceImpl.java
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    @Override
    public CustomerDetailsDto fetchCustomerDetails(String mobileNumber, String correlationId) {
        Customer customer = customerRepository.findByMobileNumber(mobileNumber)
                .orElseThrow(() -> new ResourceNotFoundException("Customer", "mobileNumber", mobileNumber));
        Accounts accounts = accountsRepository.findByCustomerId(customer.getCustomerId()).orElseThrow(
                () -> new ResourceNotFoundException("Account", "customerId", customer.getCustomerId().toString()));

        CustomerDetailsDto customerDetailsDto = CustomerMapper.mapToCustomerDetailsDto(customer,
                new CustomerDetailsDto());
        customerDetailsDto.setAccountsDto(AccountsMapper.mapToAccountsDto(accounts, new AccountsDto()));

        ResponseEntity<LoansDto> loansDtoResponseEntity = loansFeignClient.fetchLoanDetails(correlationId, mobileNumber);
        customerDetailsDto.setLoansDto(loansDtoResponseEntity.getBody());

        ResponseEntity<CardsDto> cardsDtoResponseEntity = cardsFeignClient.fetchCardDetails(correlationId, mobileNumber);
        customerDetailsDto.setCardsDto(cardsDtoResponseEntity.getBody());

        return customerDetailsDto;
    }
Accounts: CardsFeignClient.java
11
12
13
14
15
16
@FeignClient("cards")
public interface CardsFeignClient {
    @GetMapping(value="/api/cards", consumes="application/json")
    public ResponseEntity<CardsDto> fetchCardDetails(@RequestHeader("tusbank-correlation-id") String correlationId,
            @RequestParam String mobileNumber);
}
Accounts: LoansFeignClient.java
11
12
13
14
15
16
@FeignClient("loans")
public interface LoansFeignClient {
    @GetMapping(value = "/api/loans", consumes = "application/json")
    public ResponseEntity<LoansDto> fetchLoanDetails(@RequestHeader("tusbank-correlation-id") String correlationId,
            @RequestParam String mobileNumber);
}
Loans: LoansController.java
35
36
37
38
39
40
@Validated
public class LoansController {

    private ILoansService iLoansService;

    private static final Logger logger = LoggerFactory.getLogger(LoansController.class);
Loans: LoansController.java
83
84
85
86
87
88
89
90
    @GetMapping()
    public ResponseEntity<LoansDto> fetchLoanDetails(@RequestHeader("tusbank-correlation-id") String correlationId,
            @RequestParam @Pattern(regexp = "(^$|[0-9]{10})", message = "Mobile number must be 10 digits") String mobileNumber) {
        LoansDto loansDto = iLoansService.fetchLoan(mobileNumber);
        logger.debug("TusBank-correlation-id: found:{}", correlationId);
        System.out.println("Build Version: " + buildVersion);
        return ResponseEntity.status(HttpStatus.OK).body(loansDto);
    }
Cards: CardsController.java
34
35
36
37
38
39
@Validated
public class CardsController {

    private ICardsService iCardsService;

    private static final Logger logger = LoggerFactory.getLogger(CardsController.class);
Cards: CardsController.java
82
83
84
85
86
87
88
    @GetMapping()
    public ResponseEntity<CardsDto> fetchCardDetails(@RequestHeader("tusbank-correlation-id") String correlationId,
            @RequestParam @Pattern(regexp = "(^$|[0-9]{10})", message = "Mobile number must be 10 digits") String mobileNumber) {
        logger.debug("TusBank-correlation-id: found:{}", correlationId);
        CardsDto loansDto = iCardsService.fetchCard(mobileNumber);
        return ResponseEntity.status(HttpStatus.OK).body(loansDto);
    }

Step#4 enable logging in the accounts, loans and cards

Accounts: application.yml linenums=
logging:
  level:
    com:
      tus:
        accounts: DEBUG

Restart all the services followed by restarting the gateway.

Eureka Server

Figure 2. Eureka Server

Clear the console for cars, accounts and loans as well as the gateway.

Invoke the fetchCustomerDetails request from the Gateway.

N.B. Make sure an account, loan and card exist with the same number

POST localhost:8072/tusbank/accounts/api/accounts

Sample Account
{
    "name": "Joe O'Regan",
    "email": "joe@student.tus.ie",
    "mobileNumber": "0871234567"    
}

POST localhost:8072/tusbank/cards/api/cards?mobileNumber=0871234567

Sample Card
{
    "mobileNumber": "0871234567",
    "cardType": "Credit Card",
    "totalLimit": 100000,
    "amountUsed": 0,
    "availableAmount": 100000
}

POST localhost:8072/tusbank/loans/api/loans?mobileNumber=0871234567

Sample Loan
{
    "mobileNumber": "0871234567",
    "loanType": "Home Loan",
    "totalLoan": 100000,
    "amountPaid": 0,
    "outstandingAmount": 100000
}

Response

Figure 3. Response

Response Header tusbank-correlation-id

Figure 4. Response Header contains generated tusbank-correlation-id

Gateway Server tusbank-correlation-id

Figure 5. Gateway Server tusbank-correlation-id

Accounts tusbank-correlation-id

Figure 6. Accounts tusbank-correlation-id

Cards tusbank-correlation-id

Figure 7. Cards tusbank-correlation-id

Loans tusbank-correlation-id

Figure 8. Loans tusbank-correlation-id