I have 2 services - Service 1
and Service 2
. Service 1
calls some Service 2
API through Spring Rest Template. Now Some exception happened in Service 2
. I need the whole stack trace of it in Service 1
. How to get it ?
Service 1 ---calls--> Service 2
Does the stack trace even get passed to Service 1
by Spring ?
You can say I am calling like this :
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity = new HttpEntity<String>(headers);
return restTemplate.exchange("http://localhost:8080/products", HttpMethod.GET, entity, String.class).getBody();
Using printStackTrace() method − It print the name of the exception, description and complete stack trace including the line where exception occurred. Using toString() method − It prints the name and description of the exception. Using getMessage() method − Mostly used. It prints the description of the exception.
Default Error Handling By default, the RestTemplate will throw one of these exceptions in the case of an HTTP error: HttpClientErrorException – in the case of HTTP status 4xx. HttpServerErrorException – in the case of HTTP status 5xx. UnknownHttpStatusCodeException – in the case of an unknown HTTP status.
Altogether, the most common way is to use @ExceptionHandler on methods of @ControllerAdvice classes so that the exception handling will be applied globally or to a subset of controllers. ControllerAdvice is an annotation introduced in Spring 3.2, and as the name suggests, is “Advice” for multiple controllers.
The stack trace contains all invocations from the start of a thread until the point it's generated. This is usually a position at which an exception takes place. When printed out, the generation point shows up first, and method invocations leading to that point are displayed underneath.
I need the whole stack trace of it in Service 1. How to get it ?
So there are ways to get it , in essence you have to implement.
You can get your relevant exception message/trace in JSON response
from Service 2
. That is , when there is any exception
at Service 2
end then , we can configure response to send relevant exception information.
In this post there are 3 answers explaining different ways to achieve, also this one. Now on :
Does the stack trace even get passed to Service 1 by Spring ?
Normally any unhandled/runtime exception
thrown when processing a web-request
causes the server to return an HTTP 500
response.
So the answer is spring does not transfer the stack trace to Service 1
rather respond with error HTTP 500
and the most likely message of your exception
.
However, any exception that you write yourself can be annotated with the @ResponseStatus
annotation (which supports all the HTTP status codes defined by the HTTP
specification).
When an annotated exception
is thrown from a controller method, and not handled elsewhere, it will automatically cause the appropriate HTTP response
to be returned with the specified status-code and with the message/trace written.
For example,
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Account") // 404
public class AddressNotFoundException extends RuntimeException {
// ...
}
And here is a controller method using it:
@RequestMapping(value="/account/{id}", method=GET)
public String showOrder(@PathVariable("id") long id, Model model) {
Account account = accountServices.findAccountById(id);
if (account == null) throw new AddressNotFoundException(id);
model.addAttribute(account);
return "accountDetail";
}
A familiar HTTP 404
response will be returned if the URL handled by this method includes an unknown account id.
Hope this helps.
To summarize a straight-forward approach it is not much more than adding Spring Boot's Zipkin
and Sleuth
starters into your pom.xml
s to enable log tracing in both directions between multiple apps…
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth</artifactId>
<version>${spring-cloud-sleuth.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
…and configure your log pattern in your application.properties
to reflect the SpanId
s and TraceId
s which Sleuth injected in your requests…
logging.pattern.level=[%X{X-B3-TraceId}/%X{X-B3-SpanId}] %-5p [%t] %C{2} - %m%n
You can also compare your own progress with a running example on Openzipkin's Github account.
Maybe you could give it a try and show us your experiences and progress with it!
1) Catch specific HttpServerErrorException and HttpClientErrorException before Exception, propagate exception object all the way up to service 1 or resource.
try {
//code
} catch (HttpServerErrorException | HttpClientErrorException ex) {
throw new SystemException("Http Exception", ex.getResponseBodyAsString(),
ex);
} catch (RuntimeException ex) {
throw new SystemException("RuntimeException", ex);
} catch (Exception ex) {
throw new SystemException("default exception block", ex);
}
2) Have Exception mapper like below for your system and business exceptions where all the error messages can be caught and logged.
public class SystemException extends Throwable implements
ExceptionMapper<Throwable> {
@Override
public Response toResponse(Throwable ex) {
//code to handle exception
}
Try to convert that exception in Service1 into string and attach that exception string to response with an error code, try to catch that exception in your Service2 By using the below code u can convert exception to string
import java.io.StringWriter;
import java.io.PrintWriter;
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
e.printStackTrace(printWriter);
String exceptionString = stringWriter.toString();
System.out.println(exceptionString);
In your Catch block of service1
return ResponseEntity.badRequest()
.body(exceptionString);
or
return new ResponseEntity<>(
exceptionString,
HttpStatus.BAD_REQUEST);
when service 2 get exception it will return http 500 so you cant see any detail on service 1 , so you have to intercept exception on @controlleradvice , create a controller advice and set runtime or any exception with specific http status and stack trace as error detail return it .
@ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler(value
= { Service2Exception.class})
protected ResponseEntity<Service2Error> handleService2Exception(
RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
Service2Error error = new Service2Error();
error.setStack(toStack(ex));
return new ResponseEntity<Server2Error>(error,HttpStatus.NOT_FOUND);// what you want
}
}
public static String toStack(Exception e) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
e.printStackTrace(printWriter);
String exceptionString = stringWriter.toString();
return exceptionString;
}
}
public class Service2Error {
private String errorStack;
private int errorCode;
// getter settter
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With