7

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();
Number945
  • 4,631
  • 8
  • 45
  • 83
  • [distributed tracing](https://www.baeldung.com/tracing-services-with-zipkin). get the logs based on trace id from centralized logging solutions – Barath Mar 16 '19 at 16:45

5 Answers5

6

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.

Vivek
  • 895
  • 11
  • 23
4

To summarize a straight-forward approach it is not much more than adding Spring Boot's Zipkin and Sleuth starters into your pom.xmls 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 SpanIds and TraceIds 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!

mle
  • 2,466
  • 1
  • 19
  • 25
1

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
      }
Anil Konduru
  • 878
  • 10
  • 18
1

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);
Bhaskara Arani
  • 1,556
  • 1
  • 26
  • 44
1

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


       }
Mithat Konuk
  • 447
  • 5
  • 19