Search code examples
javaspring-bootaws-java-sdkaws-cloudwatch-log-insights

Get controller name and java service path in controller advice


Hi All i have created a global exception handler in my spring boot app and writing the exception occurred in AWS cloudwatch below code working fine i am able to write the exception in cloudwatch but the challenge is i am unable to get the Restcontroller name and service path from where the the particular exception happened.

Sample java service

@GetMapping(value = "DynamoDb/deleteTable")
public String deleteTable(@RequestParam String TableName) throws InterruptedException {
     Table table = dynamoDB.getTable(TableName);
        try {
            table.delete();
            table.waitForDelete();
        } catch (Exception e) {
            throw e;
        }
        return "Success";
    }

When ever exception occurred it control transferred to controlleradvice global exception handler

Here is my code

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient;
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsRequest;
import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsResponse;
import software.amazon.awssdk.services.cloudwatchlogs.model.InputLogEvent;
import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsRequest;

import java.util.Arrays;

@ControllerAdvice
public class ExceptionControllerAdvice {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> exceptionHandler(Exception ex) {
        ErrorResponse error = new ErrorResponse();
        error.setErrorCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
        error.setMessage(ex.getMessage());
        error.setController(ex.getMessage());
        error.setService(ex.getMessage());
        error.setTimestamp(System.currentTimeMillis());
        PutLogEvents(error);
        return new ResponseEntity<ErrorResponse>(error, HttpStatus.OK);
    }

    public static void PutLogEvents(ErrorResponse Er)
    {
        String regionId = "us-east-1";
        String logGroupName = "xxxxxxx";
        String logStreamName = "xxxxxxx";

        CloudWatchLogsClient logsClient = CloudWatchLogsClient.builder().region(Region.of(regionId)).build();

        // A sequence token is required to put a log event in an existing stream.
        // Look up the stream to find its sequence token.
        String sequenceToken = getNextSequenceToken(logsClient, logGroupName, logStreamName);

        // Build a JSON log using the EmbeddedMetricFormat.

        String message = "[{" +
                "  \"Timestamp\": " + Er.getTimestamp()  + "," +
                "  \"ErrorCode\": " + Er.getErrorCode()  + "," +
                "  \"ControllerName\": " + Er.getErrorCode()  + "," +
                "  \"ServiceName\": " + Er.getErrorCode()  + "," +
                "  \"ErrorMsg\": " + Er.getErrorCode()   + "" +
                "}]";
        InputLogEvent inputLogEvent = InputLogEvent.builder()
                .message(message)
                .timestamp(Er.getTimestamp())
                .build();

        // Specify the request parameters.
        PutLogEventsRequest putLogEventsRequest = PutLogEventsRequest.builder()
                .logEvents(Arrays.asList(inputLogEvent))
                .logGroupName(logGroupName)
                .logStreamName(logStreamName)
                // Sequence token is required so that the log can be written to the
                // latest location in the stream.
                .sequenceToken(sequenceToken)
                .build();

        logsClient.putLogEvents(putLogEventsRequest);
    }

    private static String getNextSequenceToken(CloudWatchLogsClient logsClient, String logGroupName, String logStreamName) {
        DescribeLogStreamsRequest logStreamRequest = DescribeLogStreamsRequest.builder()
                .logGroupName(logGroupName)
                .logStreamNamePrefix(logStreamName)
                .build();

        DescribeLogStreamsResponse describeLogStreamsResponse = logsClient.describeLogStreams(logStreamRequest);

        // Assume that a single stream is returned since a specific stream name was
        // specified in the previous request.
        return describeLogStreamsResponse.logStreams().get(0).uploadSequenceToken();
    }

}

Errorresponse.class

public class ErrorResponse {
    private int errorCode;
    private String message;
    private String Controller;
    private String Service;
    private String ProjectName;
    private long Timestamp;

    public ErrorResponse(int errorCode, String message, String controller, String service, String projectName, long timestamp) {
        this.errorCode = errorCode;
        this.message = message;
        Controller = controller;
        Service = service;
        ProjectName = projectName;
        Timestamp = timestamp;
    }

    public ErrorResponse() {

    }

    @Override
    public String toString() {
        return "ErrorResponse{" +
                "errorCode=" + errorCode +
                ", message='" + message + '\'' +
                ", Controller='" + Controller + '\'' +
                ", Service='" + Service + '\'' +
                ", ProjectName='" + ProjectName + '\'' +
                ", Timestamp=" + Timestamp +
                '}';
    }

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getController() {
        return Controller;
    }

    public void setController(String controller) {
        Controller = controller;
    }

    public String getService() {
        return Service;
    }

    public void setService(String service) {
        Service = service;
    }

    public String getProjectName() {
        return ProjectName;
    }

    public void setProjectName(String projectName) {
        ProjectName = projectName;
    }

    public long getTimestamp() {
        return Timestamp;
    }

    public void setTimestamp(long timestamp) {
        Timestamp = timestamp;
    }
}


Could any one please help me how can i get the Restcontroller name and service path in Global exception handler?


Solution

  • Hi All Thanks to all by using below code i am able to get the result as suggested by client. Hope this may help some one. Thanks

    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.ObjectWriter;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.core.MethodParameter;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.method.HandlerMethod;
    import software.amazon.awssdk.regions.Region;
    import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient;
    import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsRequest;
    import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsResponse;
    import software.amazon.awssdk.services.cloudwatchlogs.model.InputLogEvent;
    import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsRequest;
    
    import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.AnnotatedType;
    import java.lang.reflect.Method;
    import java.text.SimpleDateFormat;
    import java.util.Arrays;
    
    @ControllerAdvice
    public class ExceptionControllerAdvice {
    
        private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss");
    
        @Value("${application.name}")
        private String applicationName;
    
        @Value("${aws.logGroupName}")
        private String logGroupName;
    
        @Value("${aws.logStreamName}")
        private String logStreamName;
    
    
        @ExceptionHandler(Exception.class)
        public ResponseEntity<ErrorResponse> exceptionHandler(Exception ex, HandlerMethod handlerMethod, HttpServletRequest request) throws JsonProcessingException {
            Class ControllerName = handlerMethod.getMethod().getDeclaringClass();
            String MethodName = handlerMethod.getMethod().getName();
            ErrorResponse error = new ErrorResponse();
    
            error.setErrorCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
            error.setErrorMessage(ex.getMessage());
            error.setControllerName(ControllerName.toString());
            error.setServiceName(MethodName.toString());
            error.setTimeStamp(sdf.format(System.currentTimeMillis()));
            error.setProjectName(applicationName);
            error.setServicePath(request.getRequestURL().toString());
            PutLogEvents(error);
    
            return new ResponseEntity<ErrorResponse>(error, HttpStatus.OK);
        }
    
        public void PutLogEvents(ErrorResponse Er) throws JsonProcessingException {
            String regionId = "xxxxx";
            String logGroupName = "xxxxx";
            String logStreamName = "xxxxx";
    
            CloudWatchLogsClient logsClient = CloudWatchLogsClient.builder().region(Region.of(regionId)).build();
            // A sequence token is required to put a log event in an existing stream.
            // Look up the stream to find its sequence token.
            String sequenceToken = getNextSequenceToken(logsClient, logGroupName, logStreamName);
    
            // Build a JSON log using the EmbeddedMetricFormat.
    
            ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
            String json = ow.writeValueAsString(Er);
    
            String message =json;
            InputLogEvent inputLogEvent = InputLogEvent.builder()
                    .message(message)
                    .timestamp(System.currentTimeMillis())
                    .build();
    
            // Specify the request parameters.
            PutLogEventsRequest putLogEventsRequest = PutLogEventsRequest.builder()
                    .logEvents(Arrays.asList(inputLogEvent))
                    .logGroupName(logGroupName)
                    .logStreamName(logStreamName)
                    // Sequence token is required so that the log can be written to the
                    // latest location in the stream.
                    .sequenceToken(sequenceToken)
                    .build();
    
            logsClient.putLogEvents(putLogEventsRequest);
        }
    
        private static String getNextSequenceToken(CloudWatchLogsClient logsClient, String logGroupName, String logStreamName) {
            DescribeLogStreamsRequest logStreamRequest = DescribeLogStreamsRequest.builder()
                    .logGroupName(logGroupName)
                    .logStreamNamePrefix(logStreamName)
                    .build();
    
            DescribeLogStreamsResponse describeLogStreamsResponse = logsClient.describeLogStreams(logStreamRequest);
    
            // Assume that a single stream is returned since a specific stream name was
            // specified in the previous request.
            return describeLogStreamsResponse.logStreams().get(0).uploadSequenceToken();
        }
    
    }
    

    Result should be like this

    {
        "errorMessage": "Table already exists: ProductFgh (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ResourceInUseException; Request ID: 6S14VS0E6ESUMG55DL937IC42JVV4KQNSO5AEMVJF66Q9ASUAAJG)",
        "timeStamp": "2020-Jan-22 11:53:58",
        "errorCode": 500,
        "projectName": "DynamoDB",
        "servicePath": "http://localhost:8090/DynamoDb/createTable",
        "controllerName": "class com.example.DynamoDB.DynamoDBController",
        "serviceName": "createExampleTable"
    }
    
    

    As of now i have achieved this through above code if any better approach is available let me know. Thanks to all