Correlation ID — Microservices

Khushboo patel
Vedity
Published in
4 min readMay 17, 2022

Microservices architecture has many advantages over monolithic architecture, however, there is one disadvantage that I observed that microservices architecture has over monolithic, is tracking down the issue/errors in production.

In my past company, we had one production issue where one of the prospects’ data was not able to process properly through the final step, in spite of passing all the validation and checks.

Our system was comprised of 15 microservices. so, all the leads and teams of microservices(including the platform team) had to go through one whole day of a working session to identify the service for which the data processing was failing because none of the services was consistently logging or sending the prospects ID or any other unique id. So, we started exploring some random ID which should be passed and tracked with each request/response with each communication within microservices and there we found the concept of Correlation ID(sometimes it is referred to as Transaction ID or Transit ID).

“Correlation ID is a unique identifier value that is attached to requests and messages that allow reference to a particular transaction or event chain.”

Here’s the following diagram which we followed in our architecture to track this unique id :

Correlation-id tracking flow

In this blog, we will be exploring how we can implement correlation ID in Java with MDC (Mapped Diagnostic Context) and PHP (Larvel).

Let's first explore how we can implement Correlation ID in Java with HTTPFilter and with AOP(Aspect-oriented programming).

Maven dependency :

<!-- logback dependency (MDC) --><dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>io.logz.logback</groupId>
<artifactId>logzio-logback-appender</artifactId>
<version>1.0.24</version>
</dependency>

Create a bean and with a static variable CORRELATION_ID_HEADER to set and get the to/from the request.

RequestCorrelation.java

public class RequestCorrelation {
public static final String CORRELATION_ID_HEADER = "correlation-Id";
private static final ThreadLocal<String> id = new ThreadLocal<>();
public static String getId() {
return id.get();
}
public static void setId(String correlationId) {
id.set(correlationId);
}
}

In the CorrelationHeaderFilter.java, we will capture the request in the doFilter() method and checks for the presence of the correlation-id. If it exist we will add it in MDC logs and if not, generate a new unique id for outgoing requests.

CorrelationHeaderFilter.java

package com.bills.fdr.file.readiness.filter;import java.io.IOException;
import java.util.UUID;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Order(1)
@Slf4j
public class CorrelationHeaderFilter implements Filter {
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException { final HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String currentCorrId = httpServletRequest.getHeader(RequestCorrelation.CORRELATION_ID_HEADER);try {
String uri = httpServletRequest.getRequestURI();
if (StringUtils.isEmpty(currentCorrId))
{
currentCorrId = UUID.randomUUID().toString();
log.info("No correlationId found in Header. Generated : " + currentCorrId);
}
else
{
log.info("Found correlationId in Header : " + currentCorrId);
}
RequestCorrelation.setId(currentCorrId);
MDC.put(RequestCorrelation.CORRELATION_ID_HEADER, currentCorrId);
filterChain.doFilter(httpServletRequest, servletResponse);
}finally
{
MDC.remove(RequestCorrelation.CORRELATION_ID_HEADER);
}
}
private boolean currentRequestIsAsyncDispatcher(HttpServletRequest httpServletRequest) {
return httpServletRequest.getDispatcherType().equals(DispatcherType.ASYNC);
}
}

Note : Here currentRequestIsAsyncDispatcher() is here to check for any asynchronous request in filter. If there is any asynch request, then we need to handle the request little differently.

For each outgoing call :

HttpHeaders httpHeaders = new HttpHeaders();httpHeaders.set(RequestCorrelation.CORRELATION_ID_HEADER,RequestCorrelation.getId());

We can also use AOP(Aspect oriented programming) to implement Correlation ID.So, Whenever there is any outgoing call to other service we can use @Before/@After aspects to log correlation ID with MDC.

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import java.util.UUID;@Aspect
@Configuration
public class CorelationIdAspect {private static final String CORRELATION_ID = "Correlation-Id";@Before(value = "execution(*com.example.restTemplateUtil.exchange(..))")
public void before(JoinPoint joinPoint) {
final String correlationId = generateUniqueCorrelationId();
MDC.put(CORRELATION_ID, correlationId);
}
@After(value =
"execution(* com.example.restTemplateUtil.exchange(..))")
public void afterReturning(JoinPoint joinPoint) {
MDC.remove(CORRELATION_ID);
}
private String generateUniqueCorrelationId() {
return UUID.randomUUID().toString();
}
}

The benefit of using the Filter or AOP for correlation id tracking is that, it completely separates logging/tracking from your business logic.You can also use interceptor (like HandlerInterceptorAdapter) to capture correlation id from request.

For logging purpose using MDC,please don’t forgot to configure logback-spring.xml file to print the correlation id in the logs as below :

<pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} %thread %X{correlationId} [%-5level] %class{0} - %msg%n</pattern>

Lets explore PHP (Larvel) implementation of Correlation ID:

Create and register a middleware class for correlation ID.

class Kernel extends HttpKernel{.......'api' => ['correlationId' => \App\Http\Middleware\CorrelationId::class....]........}

Create CorrelationID middleware :

class CorrelationId
{
public function handle(Request $request, Closure $next, string ...$authorizedRoles)
{
$rci = new RequestCorrelationId();
if (!$request) {
return;
}
$correlationId = $request->headers->get('correlation-Id');
if (empty($correlationId)) {
$correlationId = $this->generateCorrelationId($request);
}
$rci->setCorrelationId($correlationId);
Log::withContext([
'$correlation-id' => $correlationId
]);
return $next($request)->header('correlation-Id', $correlationId);
}
public function generateCorrelationId(Request $request)
{
return Str::uuid()->toString();
}
}
<?php
namespace App;
class RequestCorrelationId
{
public static $CORRELATION_ID;
public function __construct() {
}
public function getCorrelationId(){
return self::$CORRELATION_ID;
}
public function setCorrelationId($id)
{
self::$CORRELATION_ID = $id;
}
}

Why to generate and log another ID ?

A question will come in to your mind that we can simply track/log any existing unique identifier/id, why to generate and track a new unique id.
Yes you can use that, but there are two problems :one, sometimes existing unique ids are critical and it is not secure to expose them in logs.
Second, not all microservices of a system works on the same unique id.
So, it is better to generate and log a new unique id, which is not related to any business logic and whose sole purpose is to be passed in every request and to be logged with every log entry.

I hope this blog will help you in tracking and debugging the issues in your project with correlation id in future.

--

--

Khushboo patel
Vedity
Writer for

8+ years of experience in Java | Spring | Spring boot | JPA | Unit testing | Mockito | PHP | Agile