Reputation: 15103
I need to log trace information using a Tracer
bean, which is request-scoped, in both my REST method and exception handler. The Tracer
bean is consumed in the REST method to log the trace information at the start of processing the request, and I want to use the same trace message in the exception handler when an exception occurs.
However, I'm facing difficulties accessing the request-scoped Tracer
bean in my exception handler.
I have tried both rest controller @ExceptionHhandler
annotation in the REST controller and @ControllerAdvice
, but neither seams to support access to a scope bean.
Here's my setup:
@RestController
public class MyController {
@Bean
@RequestScope
Tracer tracer(){
return new Tracer();
}
@GetMapping(path = "list")
public String list(Tracer trace){
return trace.getCorrelationId();
}
@GetMapping(path = "throw")
public String throwEx(Tracer trace,HttpServletRequest req){
throw new RuntimeException("Ops");
}
@ExceptionHandler({Exception.class})
ResponseEntity<String> defaultExceptionHandler(Tracer tracer, HttpServletRequest req, Exception ex) {
return ResponseEntity
.internalServerError()
.body("ERROR: " + tracer.getCorrelationId());
}
}
The tracer (for completeness):
@Getter
public class Tracer {
private final String requestId;
private final String correlationId;
public Tracer() {
requestId = null;
correlationId = UUID.randomUUID().toString();
}
public Tracer(String requestId, String correlationId) {
this.requestId = requestId;
this.correlationId = correlationId;
}
}
Here the test:
@SpringBootTest
@AutoConfigureMockMvc
class MyControllerTest {
@Autowired
protected MockMvc mockMvc;
@Test
void testExceptionHandler() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders.get("/throw")
)
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse()
.getContentAsString();
}
}
And here is the problem:
Could not resolve parameter [0] in org.springframework.http.ResponseEntity<java.lang.String> com.baeldung.scopes.MyController.defaultExceptionHandler(com.baeldung.scopes.Tracer,jakarta.servlet.http.HttpServletRequest,java.lang.Exception): No suitable resolver
It seems that I cannot directly inject the Tracer bean into the exception handler method.
I'm I missing something here? Is there another approach to do this problem?
Any help or suggestions would be greatly appreciated!
Upvotes: 0
Views: 97
Reputation: 1203
UPDATE: Most correct way is this
Maybe "dirty", but at least it works:
@Getter
public class YourException extends RuntimeException {
private final Tracer tracer;
//Your constructors
}
@GetMapping(path = "throw")
public String throwEx(Tracer trace,HttpServletRequest req){
throw new YourException("Ops", tracer); -- pass your tracer to the constructor
}
@ExceptionHandler({YourException.class})
ResponseEntity<String> defaultExceptionHandler(HttpServletRequest req, YourException ex) {
return ResponseEntity
.internalServerError()
.body("ERROR: " + ex.getTracer().getCorrelationId());
}
Other solution is ThreadLocal (spring web-mvc uses thread-per-request model)
public class TracerInfo {
private static final ThreadLocal<Tracer> TRACER = new ThreadLocal<>();
public static void set(Tracer tracer) {
TRACER.set(tracer);
}
public static Tracer get() {
return TRACER.get();
}
}
@GetMapping(path = "throw")
public String throwEx(Tracer trace,HttpServletRequest req) {
TracerInfo.set(tracer);
throw new YourException("Ops");
}
@ExceptionHandler({Exception.class})
ResponseEntity<String> defaultExceptionHandler(HttpServletRequest req, Exception ex) {
return ResponseEntity
.internalServerError()
.body("ERROR: " + TracerInfo.get().getCorrelationId());
}
Ps: fix compile error if any since I do not have IDE right now
Upvotes: 0
Reputation: 61
Another approach. Add annotations @RequestScope
and @Component
to Tracer
:
@Getter
@Component
@RequestScope
public class Tracer {
private final String requestId;
private final String correlationId;
public Tracer() {
requestId = null;
correlationId = UUID.randomUUID().toString();
}
public Tracer(String requestId, String correlationId) {
this.requestId = requestId;
this.correlationId = correlationId;
}
}
Inject it in your controller, and remove the trace
method parameters.
@RestController
@Log4j2
public class MyController {
private Tracer tracer; // use instance variable
public MyController(Tracer tracer) {
this.tracer = tracer;
}
@GetMapping(path = "list")
public String list() {
return tracer.getCorrelationId();
}
@GetMapping(path = "throw")
public String throwEx(HttpServletRequest req) {
log.info(tracer.getCorrelationId());
throw new RuntimeException("Ops");
}
@ExceptionHandler({Exception.class})
ResponseEntity<String> defaultExceptionHandler(HttpServletRequest req, Exception ex) {
return ResponseEntity
.internalServerError()
.body("ERROR: " + tracer.getCorrelationId()); // use the instance variable
}
}
Upvotes: 1