How to globally handle errors thrown from WebFilter in Spring WebFlux? - spring

How to intercept and handle errors globally in WebFlux when they are being thrown from WebFilter chain?
It is clear how to handle errors thrown from controllers: #ControllerAdvice and #ExceptionHandler help great.
This approach does not work when an exception is thrown from WebFilter components.
In the following configuration GET /first and GET /second responses intentionally induce exceptions thrown. Although #ExceptionHandler methods handleFirst, handleSecond are similar, the handleSecond is never called. I suppose that is because MyWebFilter does not let a ServerWebExchange go to the stage where GlobalErrorHandlers methods could be applied.
Response for GET /first:
HTTP 500 "hello first" // expected
HTTP 500 "hello first" // actual
Response for GET /second:
HTTP 404 "hello second" // expected
HTTP 500 {"path": "/second", "status": 500, "error": "Internal Server Error" } // actual
#RestController
class MyController {
#GetMapping("/first")
String first(){
throw new FirstException("hello first");
}
}
#Component
class MyWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange swe, WebFilterChain wfc) {
var path = swe.getRequest().getURI().getPath();
if (path.contains("second")){
throw new SecondException("hello second")
}
}
}
#ControllerAdvice
class GlobalErrorHandlers {
#ExceptionHandler(FirstException::class)
ResponseEntity<String> handleFirst(FirstException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.message)
}
#ExceptionHandler(SecondException::class)
ResponseEntity<String> handleSecond(SecondException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.message)
}
}

Three steps are required to get full control over all exceptions thrown from application endpoints handling code:
Implement org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler
Annotate with #ControllerAdvice (or just #Component)
Set #Priority less than 1 to let the custom handler run before the default one (WebFluxResponseStatusExceptionHandler)
The tricky part is where we get an instance implementing
ServerResponse.Context for passing to
ServerResponse.writeTo(exchange, context). I did not find the final
answer, and comments are welcome. In the internal Spring code they always create a new instance of context for each writeTo invocation,
although in all cases (I've manged to find) the context instance is immutable.
That is why I ended up using the same ResponseContextInstance for all responses.
At the moment no problems detected with this approach.
#ControllerAdvice
#Priority(0) /* should go before WebFluxResponseStatusExceptionHandler */
class CustomWebExceptionHandler : ErrorWebExceptionHandler {
private val log = logger(CustomWebExceptionHandler::class)
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
log.error("handled ${ex.javaClass.simpleName}", ex)
val sr = when (ex) {
is FirstException -> handleFirst(ex)
is SecondException -> handleSecond(ex)
else -> defaultException(ex)
}
return sr.flatMap { it.writeTo(exchange, ResponseContextInstance) }.then()
}
private fun handleFirst(ex: FirstException): Mono<ServerResponse> {
return ServerResponse
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.bodyValue("first")
}
private fun handleSecond(ex: SecondException): Mono<ServerResponse> {
return ServerResponse.status(HttpStatus.BAD_REQUEST).bodyValue("second")
}
private object ResponseContextInstance : ServerResponse.Context {
val strategies: HandlerStrategies = HandlerStrategies.withDefaults()
override fun messageWriters(): List<HttpMessageWriter<*>> {
return strategies.messageWriters()
}
override fun viewResolvers(): List<ViewResolver> {
return strategies.viewResolvers()
}
}
}

Related

Hystrix fallback method returns null

I implemented feign client and hystrix to my spring boot microservice application.
I first tried to test to communicate users service to albums service with feign client,
so I threw an exception at albums service to check if users service Error Decoder can catch the exception and then make the fallback method triggered.
It worked, but the cause is always null only at the first time, and after that I can see the error message that I wanted to see.
Can anyone tell me if something is wrong or not.
This is my code.
Users Service Feign Client
#FeignClient(name = "albums-ws", fallbackFactory = AlbumsFallbackFactory.class)
public interface AlbumServiceClient {
#GetMapping(path = "users/{userId}/albums")
List<AlbumDetailResponse> getAlbums(#PathVariable("userId") String userId);
}
Fallback Factory
#Component
public class AlbumsFallbackFactory implements FallbackFactory<AlbumServiceClient> {
#Override
public AlbumServiceClient create(Throwable cause) {
return new AlbumServiceClientFallback(cause);
}
}
public class AlbumServiceClientFallback implements AlbumServiceClient {
private final Throwable cause;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public AlbumServiceClientFallback(Throwable cause) {
this.cause = cause;
}
#Override
public List<AlbumDetailResponse> getAlbums(String userId) {
logger.error("An exception took place: " + cause.getMessage());
return new ArrayList<>();
}
}
Feign Error Decoder
#Component
public class FeignErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
switch(response.status()) {
case 400:
break;
case 404:
if(methodKey.contains("getAlbums")) {
return new ResponseStatusException(HttpStatus.valueOf(response.status()), response.reason());
}
break;
default:
return new Exception(response.reason());
}
return null;
}
}
First fallback triggered
2020-08-02 12:42:27.836 ERROR 24772 --- [ HystrixTimer-1] c.a.p.a.u.P.f.AlbumServiceClientFallback : An exception took place: null
After
2020-08-02 12:43:07.672 DEBUG 24772 --- [rix-albums-ws-2] c.a.p.a.u.P.feign.AlbumServiceClient : [AlbumServiceClient#getAlbums] User not found with id: f5b313e2-411f-4fc3-95e7-9aa5c43c286c
Hystrix has class org.springframework.cloud.netflix.feign.HystrixTargeter. There is a comment in targetWithFallbackFactory method:
We take a sample fallback from the fallback factory to check if it
returns a fallback that is compatible with the annotated feign
interface.
and code after:
Object exampleFallback = fallbackFactory.create(new RuntimeException());
It is why you don't have cause in exception.

Spring Webflux WebExceptionHandler not being Triggered by OptimisticLockingFailure

I have a GlobalExceptionHandler to catch all exceptions thrown and return a correct return code.
Everything works except when an OptimisticLockingFailureException happens and I have no clue why this is.
GlobalExceptionHandler:
#Component
#Order(-2)
class GlobalExceptionHandler : WebExceptionHandler {
override fun handle(exchange: ServerWebExchange, throwable: Throwable): Mono<Void> = handleException(throwable)
.flatMap { it.writeTo(exchange, HandlerStrategiesResponseContext(HandlerStrategies.withDefaults())) }
.flatMap { Mono.empty<Void>() }
private fun handleException(ex: Throwable): Mono<ServerResponse> = when (ex) {
is EntityNotFoundException -> notFound(ex.message)
is InvalidRequestException,
is ReferenceKeyNotKnown,
is InvalidContractTypeException -> badRequest(ex.message)
is DuplicateKeyException,
is TransactionException,
is OptimisticLockingFailureException -> conflict(ex.message)
else -> errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex.message)
}
private class HandlerStrategiesResponseContext(val strategies: HandlerStrategies) : ServerResponse.Context {
override fun viewResolvers(): MutableList<ViewResolver> = strategies.viewResolvers()
override fun messageWriters(): MutableList<HttpMessageWriter<*>> = strategies.messageWriters()
}
}
the notFound, badRequest and errorResponse functions return a Mono with the correct statuscode and message.
Anyone that can help me out and point me in the right direction on why this isn't being triggered by the OptimistickLockingException?
Thx!
So the reason the exceptionhandler wasn't triggered was that we still had a .onErrorContinue step in our handler which logged the exception and nothing else. After moving the logging into the GlobalExceptionHandler and removing the onErrorContinue the GlobalExceptionHandler was working as expected

Is there a way to record response times of feign client

#FeignClient(...)
public interface SomeClient {
#RequestMapping(value = "/someUrl", method = POST, consumes = "application/json")
ResponseEntity<String> createItem(...);
}
Is there a way to find the response times for createItem api call?
We are using spring boot, actuator, prometheus.
We have straight forward as well as a customized way for logging the feign clients request and response (including the response time). We have to inject the feign.Logger.Level bean, that's it.
THE DEFAULT/ STRAIGHT FORWARD WAY
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
there are BASIC,FULL,HEADERS,NONE(default) logging levels are available for more details
The above bean injection will give you the logging of feign request and response in the below format:
REQUEST:
refer
log(configKey, "---> %s %s HTTP/1.1", request.httpMethod().name(), request.url());
ex:2019-09-26 12:50:12.163 [DEBUG] [http-nio-4200-exec-5] [com.sample.FeignClient:72] [FeignClient#getUser] ---> END HTTP (0-byte body)
where the configkey means FeignClientClassName#FeignClientCallingMethodName ex: ApiClient#apiMethod.
RESPONSE
refer
log(configKey, "<--- HTTP/1.1 %s%s (%sms)", status, reason, elapsedTime);
ex:2019-09-26 12:50:12.163 [DEBUG] [http-nio-4200-exec-5] [com.sample.FeignClient:72] [FeignClient#getUser] <--- HTTP/1.1 200 OK (341ms)
the elapsedTime is what the response time taken for the API call.
NOTE: If you prefer the default way of the feign client logging then we have to consider the underlying application logging level as well because the feign.Slf4jLogger class logging with the feign request and response details with the DEBUG level (refer). If the underlying logging level above DEBUG then you may need to specify the explicit logger for the feign logging package/class otherwise it will not work.
THE CUSTOMIZED WAY
If you prefer logging with your customized format then you can extend the feign.Logger class and customize your logging. For a typical example if I want to log the header details of request and response in a single line as a list(by default Logger.Level.HEADERS prints the header in multiple lines):
package com.test.logging.feign;
import feign.Logger;
import feign.Request;
import feign.Response;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import static feign.Logger.Level.HEADERS;
#Slf4j
public class customFeignLogger extends Logger {
#Override
protected void logRequest(String configKey, Level logLevel, Request request) {
if (logLevel.ordinal() >= HEADERS.ordinal()) {
super.logRequest(configKey, logLevel, request);
} else {
int bodyLength = 0;
if (request.requestBody().asBytes() != null) {
bodyLength = request.requestBody().asBytes().length;
}
log(configKey, "---> %s %s HTTP/1.1 (%s-byte body) %s", request.httpMethod().name(), request.url(), bodyLength, request.headers());
}
}
#Override
protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime)
throws IOException {
if (logLevel.ordinal() >= HEADERS.ordinal()) {
super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
} else {
int status = response.status();
Request request = response.request();
log(configKey, "<--- %s %s HTTP/1.1 %s (%sms) %s", request.httpMethod().name(), request.url(), status, elapsedTime, response.headers());
}
return response;
}
#Override
protected void log(String configKey, String format, Object... args) {
log.debug(format(configKey, format, args));
}
protected String format(String configKey, String format, Object... args) {
return String.format(methodTag(configKey) + format, args);
}
}
also we have to inject the customFeignLogger class bean
#Bean
public customFeignLogger customFeignLogging() {
return new customFeignLogger();
}
If you are building FeignClient by yourself then you can build it with the customized logger:
Feign.builder().logger(new customFeignLogger()).logLevel(Level.BASIC).target(SomeFeignClient.class,"http://localhost:8080");
Add the following annotation to your project.
package com.example.annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface DebugTracking {
#Aspect
#Component
public static class DebugTrackingAspect {
#Around("#annotation(com.example.annotation.DebugTracking)")
public Object trackExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start(joinPoint.toShortString());
Exception exceptionThrown = null;
try {
// Execute the joint point as usual
return joinPoint.proceed();
} catch (Exception ex) {
exceptionThrown = ex;
throw ex;
} finally {
stopWatch.stop();
System.out.println(String.format("%s took %dms.", stopWatch.getLastTaskName(), stopWatch.getLastTaskTimeMillis()));
if (exceptionThrown != null) {
System.out.println(String.format("Exception thrown: %s", exceptionThrown.getMessage()));
exceptionThrown.printStackTrace();
}
}
}
}
}
Then annotate the methods you want to track in your #FeignClient with #DebugTracking.
I'm using the following (with Spring and Lombok) :
#Configuration // from Spring
#Slf4j // from Lombok
public class MyFeignConfiguration {
#Bean // from Spring
public MyFeignClient myFeignClient() {
return Feign.builder()
.logger(new Logger() {
#Override
protected void log(String configKey, String format, Object... args) {
LOG.info( String.format(methodTag(configKey) + format, args)); // LOG is the Lombok Slf4j object
}
})
.logLevel(Logger.Level.BASIC) // see https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#_feign_logging
.target(MyFeignClient.class,"http://localhost:8080");
}
}
correct way doing this is using custom logger as pointed above. Using #Aspect is wrong. With that you create additional wrapper around the service. Feign already records this metric. Get that metric from feign.

How to use a gRPC interceptor to attach/update logging MDC in a Spring-Boot app

Problem
I have a Spring-Boot application in which I am also starting a gRPC server/service. Both the servlet and gRPC code send requests to a common object to process the request. When the request comes in I want to update the logging to display a unique 'ID' so I can track the request through the system.
On the Spring side I have setup a 'Filter' which updates the logging MDC to add some data to the log request (see this example). this works fine
On the gRPC side I have created an 'ServerInterceptor' and added it to the service, while the interceptor gets called the code to update the MDC does not stick, so when a request comes through the gRPC service I do not get the ID printed in the log. I realize this has to do with the fact that I'm intercepting the call in one thread and it's being dispatched by gRPC in another, what I can't seem to figure out is how to either intercept the call in the thread doing the work or add the MDC information so it is properly propagated to the thread doing the work.
What I've tried
I have done a lot of searches and was quite surprised to not find this asked/answered, I can only assume my query skills are lacking :(
I'm fairly new to gRPC and this is the first Interceptor I'm writing. I've tried adding the interceptor several different ways (via ServerInterceptors.intercept, BindableService instance.intercept).
I've looked at LogNet's Spring Boot gRPC Starter, but I'm not sure this would solve the issue.
Here is the code I have added in my interceptor class
#Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<ReqT, RespT> call, final Metadata headers, final ServerCallHandler<ReqT, RespT> next) {
try {
final String mdcData = String.format("[requestID=%s]",
UUID.randomUUID().toString());
MDC.put(MDC_DATA_KEY, mdcData);
return next.startCall(call, headers);
} finally {
MDC.clear();
}
}
Expected Result
When a request comes in via the RESTful API I see log output like this
2019-04-09 10:19:16.331 [requestID=380e28db-c8da-4e35-a097-4b8c90c006f4] INFO 87100 --- [nio-8080-exec-1] c.c.es.xxx: processing request step 1
2019-04-09 10:19:16.800 [requestID=380e28db-c8da-4e35-a097-4b8c90c006f4] INFO 87100 --- [nio-8080-exec-1] c.c.es.xxx: processing request step 2
2019-04-09 10:19:16.803 [requestID=380e28db-c8da-4e35-a097-4b8c90c006f4] INFO 87100 --- [nio-8080-exec-1] c.c.es.xxx: Processing request step 3
...
I'm hoping to get similar output when the request comes through the gRPC service.
Thanks
Since no one replied, I kept trying and came up with the following solution for my interceptCall function. I'm not 100% sure why this works, but it works for my use case.
private class LogInterceptor implements ServerInterceptor {
#Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<ReqT, RespT> call,
final Metadata headers,
final ServerCallHandler<ReqT, RespT> next) {
Context context = Context.current();
final String requestId = UUID.randomUUID().toString();
return Contexts.interceptCall(context, call, headers, new ServerCallHandler<ReqT, RespT>() {
#Override
public ServerCall.Listener<ReqT> startCall(ServerCall<ReqT, RespT> call, Metadata headers) {
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(next.startCall(call, headers)) {
/**
* The actual service call happens during onHalfClose().
*/
#Override
public void onHalfClose() {
try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.put("requestID",
UUID.randomUUID().toString())) {
super.onHalfClose();
}
}
};
}
});
}
}
In my application.properties I added the following (which I already had)
logging.pattern.level=[%X] %-5level
The '%X' tells the logging system to print all of the CloseableThreadContext key/values.
Hopefully this may help someone else.
MDC stores data in ThreadLocal variable and you are right about - "I realize this has to do with the fact that I'm intercepting the call in one thread and it's being dispatched by gRPC in another". Check #Eric Anderson answer about the right way to use ThradLocal in the post -
https://stackoverflow.com/a/56842315/2478531
Here is a working example -
public class GrpcMDCInterceptor implements ServerInterceptor {
private static final String MDC_DATA_KEY = "Key";
#Override
public <R, S> ServerCall.Listener<R> interceptCall(
ServerCall<R, S> serverCall,
Metadata metadata,
ServerCallHandler<R, S> next
) {
log.info("Setting user context, metadata {}", metadata);
final String mdcData = String.format("[requestID=%s]", UUID.randomUUID().toString());
MDC.put(MDC_DATA_KEY, mdcData);
try {
return new WrappingListener<>(next.startCall(serverCall, metadata), mdcData);
} finally {
MDC.clear();
}
}
private static class WrappingListener<R>
extends ForwardingServerCallListener.SimpleForwardingServerCallListener<R> {
private final String mdcData;
public WrappingListener(ServerCall.Listener<R> delegate, String mdcData) {
super(delegate);
this.mdcData = mdcData;
}
#Override
public void onMessage(R message) {
MDC.put(MDC_DATA_KEY, mdcData);
try {
super.onMessage(message);
} finally {
MDC.clear();
}
}
#Override
public void onHalfClose() {
MDC.put(MDC_DATA_KEY, mdcData);
try {
super.onHalfClose();
} finally {
MDC.clear();
}
}
#Override
public void onCancel() {
MDC.put(MDC_DATA_KEY, mdcData);
try {
super.onCancel();
} finally {
MDC.clear();
}
}
#Override
public void onComplete() {
MDC.put(MDC_DATA_KEY, mdcData);
try {
super.onComplete();
} finally {
MDC.clear();
}
}
#Override
public void onReady() {
MDC.put(MDC_DATA_KEY, mdcData);
try {
super.onReady();
} finally {
MDC.clear();
}
}
}
}

Spring Boot Exception(Error) Handling for RESTful Services

I have the following RESTful Services method :
#PostMapping("/ajouterNewField")
public String ajouterField(#Valid #ModelAttribute("field") Fields field, Model model) throws IOException {
fieldDao.save(field);
// SOME CODE
return displayListeChamps( model);
}
The method is working fine and my question is how to handle any error (database not connected ...) or every issue that can happen durring the execution of this RESTful Services method.
You can use #ControllerAdvice
Refer to the code below
#ControllerAdvice
public String NyExceptionHandlerAdvice {
private final Logger logger = ...;
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler({MyRunTimeException.class})
public void handleMyRunTimeException(Exception e) {
logger.error("Exception : ", e);
}
return MY_ERROR_STRING;
}
Best Practice is:
You can have your code throw RunTimeExceptions and handle all of them together or separately in handler methods similar to handleMyRunTimeException above.
You can decide what status code your request should return upon exception.
Basically you'll have to a sort of exception handler for any kind of exception your method might throw:
public class FooController{
// ...
#ExceptionHandler({ CustomException1.class, CustomException2.class })
public void handleException() {
//
}
}
Here's a nice article about that: https://www.baeldung.com/exception-handling-for-rest-with-spring

Resources