Assign a unique id to every request in a spring-based web application
You can also try using MDC class of Log4j. The MDC is managed on a per thread basis. If you are using a ServletRequestListner then you can set the unique Id in the requestInitialized.
import org.apache.log4j.MDC;
import java.util.UUID;
public class TestRequestListener implements ServletRequestListener {
protected static final Logger LOGGER = LoggerFactory.getLogger(TestRequestListener.class);
public void requestInitialized(ServletRequestEvent arg0) {
LOGGER.debug("++++++++++++ REQUEST INITIALIZED +++++++++++++++++");
MDC.put("RequestId", UUID.randomUUID());
}
public void requestDestroyed(ServletRequestEvent arg0) {
LOGGER.debug("-------------REQUEST DESTROYED ------------");
MDC.clear();
}
}
Now anywhere in the code if you do a log either debug, warn or error. Whatever you had put in the MDC will be printed out. You need to configure you log4j.properties. Notice the %X{RequestId}. This referes to the key name which is inserted in the requestInitialized() above.
log4j.appender.A1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSSS} %p %C %X{RequestId} - %m%n
I also found this link to be helpful -> What is the difference between Log4j's NDC and MDC facilities?
You can also use the "Fish Tagging" in Log4j 2. It is the same idea like MDC and NDC (thread-basis) as described in https://logging.apache.org/log4j/2.x/manual/thread-context.html
Here you can use either the Thread Context Stack or the Thread Context Map. An example for the Map looks like this:
//put a unique id to the map
ThreadContext.put("id", UUID.randomUUID().toString()
//clear map
ThreadContext.clearMap();
And for the pattern in log4j2.xml you can also use the %X{KEY} tag.
To put a new id to the map (for example for every incoming request) you can do that in an ServletRequestListener implementation how Sharadr described it.
If you don't mind using spring 4.1.3 or later, you can wrap your request into a custom subclass of ContentCachingRequestWrapper class.
public class MyHTTPServletRequestWrapper extends ContentCachingRequestWrapper {
private UUID uuid;
public MyHTTPServletRequestWrapper (HttpServletRequest request) {
super(request);
uuid = UUID.randomUUID();
}
public UUID getUUID() {
return uuid;
}
}
In your spring controller, add the request to the method's param, and cast it to your custom wrapper:
@RequestMapping(value="/get", method = RequestMethod.GET, produces="application/json")
public @ResponseBody String find(@RequestParam(value = "id") String id, HttpServletRequest request) {
MyHTTPServletRequestWrapper wrappedRequest = (WGHTTPServletRequestWrapper)request;
System.out.println(wrappedRequest.getUUID());
...
}
You will need to user filter though, to connect the dots:
public class RequestLoggingFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest requestToCache = new MyHTTPServletRequestWrapper((HttpServletRequest) request);
System.out.println(((WGHTTPServletRequestWrapper)requestToCache).getUUID());
chain.doFilter(requestToCache, response);
}
}
You will find that the printlin from RequestLoggingFilter.doFilter() and from controllers getId() will produce the same UUID.
You will just need to play around to use the UUID in appropriate places, but at least you have the value in your controller where is the start of your business logic.
You have three different problems to solve:
- Generate an unique id for each request
- Store the id and access it everywhere in the code
- Log the id automatically
I would suggest this approaches
Use a Servlet filter or a ServletRequestListener (as suggested by M. Deinum) or a Spring Handler Interceptor to intercept the request in a general way, there you can create a unique id, maybe with an UUID
You can save the id as an attribute of the request, in this case the id would propagate just in the controller layer, not in the services. So you can solve the problem using a ThreadLocal variable or asking Spring to do the magic with the RequestContextHolder: the RequestContextHolder will allow you to access the request of that specific thread, and the request attributes as well, in the service layer. The RequestContextHolder use ThreadLocal variable to store the request. You can access the request in this way:
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); // Extract the request HttpServletRequest request = attr.getRequest();
There is an interesting article (2018 alternative), if you are using log4j, on the customization of the pattern layout of the logger. However, youncan simply create a proxy of your logging system interface and append manually the id to every logged string.