I'd like to generate unique traceId per request and pass it through all services. In Spring MVC it was fairly easy by using MDC context and putting traceId in header, but in reactive stack it isn't working at all because of ThreadLocal.
In general I'd like to log each request and response on every service I have with single traceId which can identify specific action in whole system.
I tried to create custom filter based on article: https://azizulhaq-ananto.medium.com/how-to-handle-logs-and-tracing-in-spring-webflux-and-microservices-a0b45adc4610 but it's seems to not working.
My current solution only log responses and traceId are losing after making request, so there is no on response.
Let's try imagine that there are two services: service1 and service2. Below I tried to sketch how it should work.
How should it work
client->service1- service1 should generate traceId and log requestservice1->service2- service2 should fetch traceId from request, then log requestservice1<-service2- after some calculation service2 should log response and return response to service1client<-service1- at the end service1 should log response (still with the same traceId) and return response to client
How it actually works
client->service1- nothing in logsservice1->service2- nothings in logsservice1<-service2- service2 is logging correctly and return response to service1client<-service1- service1 is logging response (but without traceId)
Here is my approach
@Component
public class TraceIdFilter implements WebFilter {
private static final Logger log = LoggerFactory.getLogger(TraceIdFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
Map<String, String> headers = exchange.getRequest().getHeaders().toSingleValueMap();
return Mono.fromCallable(() -> {
final long startTime = System.currentTimeMillis();
return new ServerWebExchangeDecorator(exchange) {
@Override
public ServerHttpRequest getRequest() {
return new RequestLoggingInterceptor(super.getRequest(), false);
}
@Override
public ServerHttpResponse getResponse() {
return new ResponseLoggingInterceptor(super.getResponse(), startTime, false);
}
};
}).contextWrite(context -> {
var traceId = "";
if (headers.containsKey("X-B3-TRACEID")) {
traceId = headers.get("X-B3-TRACEID");
MDC.put("X-B3-TraceId", traceId);
} else if (!exchange.getRequest().getURI().getPath().contains("/actuator")) {
traceId = UUID.randomUUID().toString();
MDC.put("X-B3-TraceId", traceId);
}
Context contextTmp = context.put("X-B3-TraceId", traceId);
exchange.getAttributes().put("X-B3-TraceId", traceId);
return contextTmp;
}).flatMap(chain::filter);
}
}
Github: https://github.com/Faelivrinx/kotlin-spring-boot
There is any existing solution do that?