Logging elapsed time of execution in SpringBoot rest API - spring

It could be a simple solution but I am unable to get it done.
I need to log the overall execution time for my request in SpringBoot Rest API.
Request always enters to MainController always and can exit from two places-
Main Restcontroller same method or
ExceptionHandlerController handler method
I have created one custom annotation and injecting it to both main Controller and ExceptionController methods and getting elapsed time for individual methods.
So here is the problem. I need to add these individual times to calculate the total time which I don't want.
Is there any other way to log this information easily.
Aspect class:
#Aspect
#Component
public class PointsAspect {
private final static Logger logger = LoggerFactory.getLogger(PointsAspect.class);
#Around("#annotation(annotation)")
public Object logMethod(final ProceedingJoinPoint proceedingJoinPoint, final LogAround annotation)
throws Throwable {
final long start = System.currentTimeMillis();
Object obj;
try {
logger.debug("Starting...! Method Name - " +proceedingJoinPoint.getSignature().getName());
obj = proceedingJoinPoint.proceed();
} finally {
logger.debug("Exiting...! Method Name - " +proceedingJoinPoint.getSignature().getName() +"Execution Time in Milliseconds:> "+ String.valueOf(System.currentTimeMillis()-start));
}
return obj;
}
}
Marker Interface:
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface LogAround {
}
And this is how I am injecting it:
**ExceptionHandlerController.java**
#LogAround
#ExceptionHandler(HttpMessageNotReadableException.class)
public GenericFailureResponse missingRequestBodyException(HttpServletResponse response,
HttpServletRequest request, Exception ex) throws IOException {
GenericFailureResponse failureResponse = new GenericFailureResponse();
//TODO: exception logic
return failureResponse;
}
**MainController.java**
#LogAround
public String getTotalPoints(#RequestParam(required=true) long memberNo,
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
//TODO : some logic
return "something";
}

You can use a simple filter.
#Component
public class LogTimeFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
long startTime = System.currentTimeMillis();
chain.doFilter(request, response);
long duration = System.currentTimeMillis() - startTime;
System.out.println("Request take " + duration + " ms");
}
}

Related

How can i use multiple interceptors for specific request such as POST, Get and PUT?

I am using spring boot and cloud in the project. For logging, I am using Interceptors. Since I am new to interceptors I am having difficulty using multiple interceptors. Like can I use a specific interceptor for the specific task? For example, when I request a post, the POST interceptor is called, when I use GET the get interceptor is called. and how can I code for multiple interceptors too?
I never tried anything yet for that because I am not getting the logic
You can define all HTTP interceptors that you want, every interceptor should implement the logic of intercept an HTTP request.
#Slf4j
#Component
public class GetRequestInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getMethod().equals(HttpMethod.GET.name())) {
log.info("intercepting GET request {}", request.getRequestURI());
}
return true;
}
}
#Slf4j
#Component
public class PostRequestInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getMethod().equals(HttpMethod.POST.name())) {
log.info("intercepting POST request {}", request.getRequestURI());
}
return true;
}
}
And then you have to register them in spring.
#RequiredArgsConstructor
#Configuration
public class WebConfigurer implements WebMvcConfigurer {
private final GetRequestInterceptor getInterceptor;
private final PostRequestInterceptor postRequestInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getInterceptor);
registry.addInterceptor(postRequestInterceptor);
}
}
#earandap
Your comment worked Thanks a lot.
Here is my code:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
log.info("[START] [" + request.getMethod() + "] [ URL is: " + request.getRequestURL().toString()
+ " Body is: {}]");
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
long startTime = (long) request.getAttribute("startTime");
request.removeAttribute("startTime");
long endTime = System.currentTimeMillis();
log.info("[END] [" + request.getMethod() + "] [ URL is:" + request.getRequestURL().toString()
+ "] [Execution Time: {} miliseconds]", (endTime - startTime));
}

OncePerRequestFilter - handling exceptions annotated with #ResponseStatus

I'm looking for a way to log all my requests and responses in the database (1 record = 1 request + 1 response).
My use case in details:
Log record in database with request URL, params, IP, start date etc.
Update database record (when request finish) and save response,
exceptions, end date etc.
I'm trying to do with custom OncePerRequestFilter and it work's almost OK. But I have problem with handling exceptions annotated with annotation #ResponseStatus. This kind of exceptions (thrown in controllers) I can't catch in my custom doFilter method. Do you know any way to capture these exceptions in filter? Unless I should do this in some other way?
AuditFilter:
#Component
public class AuditFilter extends OncePerRequestFilter {
private Logger logger = Logger.getLogger(AuditFilter.class.getName());
private RequestAuditRepository repository;
AuditFilter(RequestAuditRepository repository) {
this.repository = repository;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
}
private void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain)
throws ServletException, IOException {
RequestAuditLog requestAuditLog = new RequestAuditLog();
String catchedExceptionMsg = null;
try {
beforeRequest(requestAuditLog, request);
filterChain.doFilter(request, response);
}
catch (Exception e) {
// Not called when exception with #ResponStatus annotation throwed
catchedExceptionMsg = e.getMessage();
throw e;
}
finally {
afterRequest(requestAuditLog, catchedExceptionMsg, request, response);
response.copyBodyToResponse();
}
}
...
}
BadRequestException:
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
I think the BadRequestException is handled even before your custom filter gets triggered and therefore you can't catch this exception in your filter.
What you could do is that you write your own ExceptionHandler additionally to your filter and log your stuff there.
#ControllerAdvice
public class MyExceptionHandler {
#ExceptionHandler(BadRequestException.class)
public void handleError(BadRequestException ex) {
// do your stuff here
}
}

Propagating correlation-id not working

I have the problem that the correlation-id is not propagated from my first to the my second microservice. I started to implement a servlet filter, a context and a context-holder as follows:
#Component
// Do not name bean "RequestContextFilter", otherwise filter will not work!
public class CallContextFilter implements Filter {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;CallContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(CallContext.CORRELATION_ID));
filterChain.doFilter(httpServletRequest, servletResponse);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
}
#Component
#Getter
#Setter
public class CallContext {
public static final String CORRELATION_ID = "correlation-id";
private String correlationId = new String();
}
public class CallContextHolder {
private static final ThreadLocal<CallContext> userContext = new ThreadLocal<>();
public static final CallContext getContext() {
CallContext context = userContext.get();
if (context == null) {
context = new CallContext();
userContext.set(context);
}
return userContext.get();
}
}
Then, I implemented a RestTemplate bean as follows:
#Bean
public RestTemplate getRestTemplate() {
RestTemplate template = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = template.getInterceptors();
interceptors.add(new CallContextInterceptor());
return template;
}
and the interceptor looks as follows:
public class CallContextInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
headers.add(CallContext.CORRELATION_ID, CallContextHolder.getContext().getCorrelationId());
return execution.execute(request, body);
}
}
When I call my endpoint, the servlet filter is executed and the correlation-id is stored in the CallContextHolder. So far, so good. However, the CallContextInterceptor seems to be called in an other thread and my ThreadLocal variable in the CallContextHolder is null. What I have to do to make this working?
#GetMapping("/ping")
public String ping() {
return pongRestTemplateClient.getPong();
}
Why not use Spring Cloud Sleuth and just let the libary do the work for you? http://cloud.spring.io/spring-cloud-sleuth/spring-cloud-sleuth.html
The problem was that I'm using Hysterix. Hystrix spawns a new thread to execute the code, completely unaware of the "outer" thread context. So, the executing thread losses access to the ThreadLocal dependant functionality when using Hysterix commands.
I found an answer to my problem here: https://github.com/jmnarloch/hystrix-context-spring-boot-starter

Make simple servlet filter work with #ControllerAdvice

I've a simple filter just to check if a request contains a special header with static key - no user auth - just to protect endpoints. The idea is to throw an AccessForbiddenException if the key does not match which then will be mapped to response with a class annotated with #ControllerAdvice. However I can't make it work. My #ExceptionHandler isn't called.
ClientKeyFilter
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Controller
import javax.servlet.*
import javax.servlet.http.HttpServletRequest
#Controller //I know that #Component might be here
public class ClientKeyFilter implements Filter {
#Value('${CLIENT_KEY}')
String clientKey
public void init(FilterConfig filterConfig) {}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
req = (HttpServletRequest) req
def reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey)) {
throw new AccessForbiddenException('Invalid API key')
}
chain.doFilter(req, res)
}
public void destroy() {}
}
AccessForbiddenException
public class AccessForbiddenException extends RuntimeException {
AccessForbiddenException(String message) {
super(message)
}
}
ExceptionController
#ControllerAdvice
class ExceptionController {
static final Logger logger = LoggerFactory.getLogger(ExceptionController)
#ExceptionHandler(AccessForbiddenException)
public ResponseEntity handleException(HttpServletRequest request, AccessForbiddenException e) {
logger.error('Caught exception.', e)
return new ResponseEntity<>(e.getMessage(), I_AM_A_TEAPOT)
}
}
Where I'm wrong? Can simple servlet filter work with spring-boot's exception mapping?
As specified by the java servlet specification Filters execute always before a Servlet is invoked. Now a #ControllerAdvice is only useful for controller which are executed inside the DispatcherServlet. So using a Filter and expecting a #ControllerAdvice or in this case the #ExceptionHandler, to be invoked isn't going to happen.
You need to either put the same logic in the filter (for writing a JSON response) or instead of a filter use a HandlerInterceptor which does this check. The easiest way is to extend the HandlerInterceptorAdapter and just override and implement the preHandle method and put the logic from the filter into that method.
public class ClientKeyInterceptor extends HandlerInterceptorAdapter {
#Value('${CLIENT_KEY}')
String clientKey
#Override
public boolean preHandle(ServletRequest req, ServletResponse res, Object handler) {
String reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey)) {
throw new AccessForbiddenException('Invalid API key')
}
return true;
}
}
You can't use #ControllerAdvice, because it gets called in case of an exception in some controller, but your ClientKeyFilter is not a #Controller.
You should replace the #Controller annotation with the #Component and just set response body and status like this:
#Component
public class ClientKeyFilter implements Filter {
#Value('${CLIENT_KEY}')
String clientKey
public void init(FilterConfig filterConfig) {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String reqClientKey = request.getHeader("Client-Key");
if (!clientKey.equals(reqClientKey)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid API key");
return;
}
chain.doFilter(req, res);
}
public void destroy() {
}
}
Servlet Filters in Java classes are used for the following purposes:
To check requests from client before they access resources at backend.
To check responses from server before sent back to the client.
Exception throw from Filter may not be catch by #ControllerAdvice because in may not reach DispatcherServlet. I am handling in my project as below:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
String token = null;
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && (bearerToken.contains("Bearer "))) {
if (bearerToken.startsWith("Bearer "))
token = bearerToken.substring(7, bearerToken.length());
try {
AuthenticationInfo authInfo = TokenHandler.validateToken(token);
logger.debug("Found id:{}", authInfo.getId());
authInfo.uri = request.getRequestURI();
AuthPersistenceBean persistentBean = new AuthPersistenceBean(authInfo);
SecurityContextHolder.getContext().setAuthentication(persistentBean);
logger.debug("Found id:'{}', added into SecurityContextHolder", authInfo.getId());
} catch (AuthenticationException authException) {
logger.error("User Unauthorized: Invalid token provided");
raiseException(request, response);
return;
} catch (Exception e) {
raiseException(request, response);
return;
}
// Wrapping the error response
private void raiseException(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ApiError apiError = new ApiError(HttpStatus.UNAUTHORIZED);
apiError.setMessage("User Unauthorized: Invalid token provided");
apiError.setPath(request.getRequestURI());
byte[] body = new ObjectMapper().writeValueAsBytes(apiError);
response.getOutputStream().write(body);
}
// ApiError class
public class ApiError {
// 4xx and 5xx
private HttpStatus status;
// holds a user-friendly message about the error.
private String message;
// holds a system message describing the error in more detail.
private String debugMessage;
// returns the part of this request's URL
private String path;
public ApiError(HttpStatus status) {
this();
this.status = status;
}
//setter and getters

how to get returned value of my controllers from HandlerInterceptor

I'm creating a log manager for my controllers that logs every action in it and returned values
My controllers are defined in this way:
#Controller
#RequestMapping(value="/ajax/user")
public class UserController extends AbstractController{
#RequestMapping(value="/signup")
public #ResponseBody ActionResponse signup(#Valid SignupModel sign) {
ActionResponse response=new ActionRespone();
response.setMessage("This is a test message");
return response;
}
}
and I defined a HandlerInterceptor to log output of each handler:
#Component
public class ControllerInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
LogManager log=new LogManager();
log.setMessage();//I need returned ActionResponse here
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
where I use log.setMessage(); I need my ActionResponse's message (This is a test message) which is returned from signup method
How can I do this?
An interceptor is not the right place to do what you want since it's not capable of getting the return value of the handler.
You can achieve what you wan't without changing any existing code using aspect oriented programming (AOP). For this to work in spring you'll need to include the jars for spring-aop and AspectJ.
Creating the aspect and advice
#Aspect
#Component
public class ActionResponseLoggerAspect {
private static final Logger logger = LoggerFactory.getLogger(ActionResponseLoggerAspect.class);
#AfterReturning(pointcut="execution(* your.package.UserController.*(..)))", returning="result")
public void afterReturning(JoinPoint joinPoint , Object result) {
if (result instanceof ActionResponse) {
ActionResponse m = (ActionResponse) result;
logger.info("ActionResponse returned with message [{}]", m.getMessage());
}
}
}
The afterReturning method will be executed every time a controller method returns.
Enabling #AspectJ Support
Enable AspectJ support by adding this to your XML configuration.
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
For more info see the spring docs.

Resources