Improved logging and exception handling

This commit is contained in:
Maksim Tereshin 2024-12-16 10:22:36 +01:00
parent b0f329c102
commit 7da06bd659
No known key found for this signature in database
3 changed files with 220 additions and 136 deletions

View file

@ -20,6 +20,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
@ -39,98 +40,58 @@ public class ApiController {
private ValidationService validationService;
@PostMapping("/block")
public ResponseEntity<?> block(@RequestBody List<String> ids) {
try {
validationService.validateAll(ids);
public ResponseEntity<?> block(@RequestBody List<String> ids) throws SQLException, FileNotFoundException {
validationService.validateAll(ids);
logger.debug("Starting block process for ids: {}", ids);
apiService.process(ConfigType.BLOCK, ids);
logger.debug("Finished block process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Блокировка\" завершена успешно.");
} catch (ValidationException e) {
logger.error("Validation failed for IDs: {}", ids, e);
return ResponseEntity.badRequest().body(Map.of(
"message", "Validation error occurred",
"details", e.getValidationDetails()
));
} catch (Exception e) {
logger.error("Unexpected error during processing: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred.");
}
logger.debug("Starting block process for ids: {}", ids);
apiService.process(ConfigType.BLOCK, ids);
logger.debug("Finished block process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Блокировка\" завершена успешно.");
}
@PostMapping("/unblock")
public ResponseEntity<?> unblock(@RequestBody List<String> ids) {
try {
validationService.validateAll(ids);
public ResponseEntity<?> unblock(@RequestBody List<String> ids) throws SQLException, FileNotFoundException {
validationService.validateAll(ids);
logger.debug("Starting unblock process for ids: {}", ids);
apiService.process(ConfigType.UNBLOCK, ids);
logger.debug("Finished unblock process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Разблокировка\" завершена успешно.");
} catch (ValidationException e) {
logger.error("Validation failed for IDs: {}", ids, e);
return ResponseEntity.badRequest().body(Map.of(
"message", "Validation error occurred",
"details", e.getValidationDetails()
));
} catch (Exception e) {
logger.error("Unexpected error during processing: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred.");
}
logger.debug("Starting unblock process for ids: {}", ids);
apiService.process(ConfigType.UNBLOCK, ids);
logger.debug("Finished unblock process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Разблокировка\" завершена успешно.");
}
@PostMapping("/removeFromSystem")
public ResponseEntity<?> removeFromSystem(@RequestBody List<String> ids) {
try {
validationService.validateAll(ids);
public ResponseEntity<?> removeFromSystem(@RequestBody List<String> ids) throws SQLException, FileNotFoundException {
validationService.validateAll(ids);
logger.debug("Starting removeFromSystem process for ids: {}", ids);
apiService.process(ConfigType.REMOVE_FROM_SYSTEM, ids);
logger.debug("Finished removeFromSystem process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Удаление данных по гражданину\" завершена успешно.");
} catch (ValidationException e) {
logger.error("Validation failed for IDs: {}", ids, e);
return ResponseEntity.badRequest().body(Map.of(
"message", "Validation error occurred",
"details", e.getValidationDetails()
));
} catch (Exception e) {
logger.error("Unexpected error during processing: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred.");
}
logger.debug("Starting removeFromSystem process for ids: {}", ids);
apiService.process(ConfigType.REMOVE_FROM_SYSTEM, ids);
logger.debug("Finished removeFromSystem process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Удаление данных по гражданину\" завершена успешно.");
}
@PostMapping("/removeFromCallList")
public ResponseEntity<?> removeFromCallList(@RequestBody List<String> ids) {
try {
validationService.validateAll(ids);
public ResponseEntity<?> removeFromCallList(@RequestBody List<String> ids) throws SQLException, FileNotFoundException {
validationService.validateAll(ids);
logger.debug("Starting removeFromCallList process for ids: {}", ids);
apiService.process(ConfigType.REMOVE_FROM_CALL_LIST, ids);
logger.debug("Finished removeFromCallList process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Удаление из списков на вызов\" завершена успешно.");
} catch (ValidationException e) {
logger.error("Validation failed for IDs: {}", ids, e);
return ResponseEntity.badRequest().body(Map.of(
"message", "Validation error occurred",
"details", e.getValidationDetails()
));
} catch (Exception e) {
logger.error("Unexpected error during processing: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error occurred.");
}
logger.debug("Starting removeFromCallList process for ids: {}", ids);
apiService.process(ConfigType.REMOVE_FROM_CALL_LIST, ids);
logger.debug("Finished removeFromCallList process for ids: {}", ids);
return ResponseEntity.ok("Операция \"Удаление из списков на вызов\" завершена успешно.");
}
@PostMapping("/downloadCSV")
public ResponseEntity<Resource> downloadCSV(@RequestBody DownloadCSVRequest request)
throws IOException {
public ResponseEntity<Resource> downloadCSV(@RequestBody DownloadCSVRequest request) throws IOException {
logger.debug("Starting downloadCSV process for request: {}", request.getType());
if (request.getStartDate() != null && request.getEndDate() != null) {
if (request.getStartDate().isAfter(request.getEndDate())) {
throw new IllegalArgumentException("Start date must be before end date");
}
}
validateRequestDates(request);
File csvFile = apiService.download(ConfigType.DOWNLOAD_CSV, request);
InputStreamResource resource = new InputStreamResource(new FileInputStream(csvFile));
@ -144,11 +105,22 @@ public class ApiController {
.body(resource);
}
private void validateRequestDates(DownloadCSVRequest request) {
if (request.getStartDate() != null && request.getEndDate() != null) {
if (request.getStartDate().isAfter(request.getEndDate())) {
throw new IllegalArgumentException("Start date must be before end date");
}
}
}
@GetMapping("/listDownloadTypes")
public ResponseEntity<?> listDownloadTypes()
throws FileNotFoundException {
public ResponseEntity<List<String>> listDownloadTypes() throws FileNotFoundException {
logger.debug("Fetching list of download types...");
List<String> downloadCSVTypes = apiService.getDownloadTypes(ConfigType.DOWNLOAD_CSV);
logger.debug("Successfully retrieved download types");
return ResponseEntity.ok(downloadCSVTypes);
}
}

View file

@ -0,0 +1,106 @@
package org.micord.exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.naming.ServiceUnavailableException;
import java.io.FileNotFoundException;
import java.nio.file.AccessDeniedException;
import java.sql.SQLException;
import java.util.Map;
import java.util.NoSuchElementException;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(SQLException.class)
public ResponseEntity<?> handleSQLException(SQLException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of(
"message", "Database error occurred",
"details", e.getMessage()
));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleGeneralException(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of(
"message", "Unexpected error occurred",
"details", e.getMessage()
));
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<?> handleValidationException(ValidationException e) {
return ResponseEntity.badRequest().body(Map.of(
"message", "Validation error occurred",
"details", e.getValidationDetails()
));
}
@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<?> handleIllegalStateException(IllegalStateException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of(
"message", "Operation cannot be performed due to an invalid state",
"details", e.getMessage()
));
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<?> handleAccessDeniedException(AccessDeniedException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Map.of(
"message", "Access denied",
"details", e.getMessage()
));
}
@ExceptionHandler(ServiceUnavailableException.class)
public ResponseEntity<?> handleServiceUnavailableException(ServiceUnavailableException e) {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(Map.of(
"message", "Service is temporarily unavailable",
"details", e.getMessage()
));
}
@ExceptionHandler(FileNotFoundException.class)
public ResponseEntity<?> handleFileNotFoundException(FileNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of(
"message", "File not found",
"details", e.getMessage()
));
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<?> handleIllegalArgumentException(IllegalArgumentException e) {
return ResponseEntity.badRequest().body(Map.of(
"message", "Invalid input provided",
"details", e.getMessage()
));
}
@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<?> handleNoSuchElementException(NoSuchElementException e) {
logger.error("Resource not found: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of(
"message", "Requested resource not found",
"details", e.getMessage()
));
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<?> handleRuntimeException(RuntimeException e) {
logger.error("Unexpected error occurred: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of(
"message", "Internal server error",
"details", e.getMessage()
));
}
}

View file

@ -16,7 +16,13 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
@ -26,7 +32,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
@ -42,24 +47,9 @@ public class RequestService {
public void processS3Requests(List<S3Request> s3Requests, List<String> ids) {
logger.debug("Starting processing of S3 requests");
if (s3Requests != null && !s3Requests.isEmpty()) {
List<CompletableFuture<Void>> requestFutures = s3Requests.stream()
.map(request -> CompletableFuture.runAsync(() -> {
try {
processS3Request(request, ids);
} catch (Exception e) {
throw new RuntimeException("Error processing S3 request: " + request, e);
}
}))
.toList();
CompletableFuture.allOf(requestFutures.toArray(new CompletableFuture[0]))
.whenComplete((result, ex) -> {
if (ex != null) {
logger.error("Error processing S3 requests", ex);
throw new RuntimeException("Error processing S3 requests", ex);
}
});
for (S3Request request : s3Requests) {
processS3Request(request, ids);
}
}
@ -82,61 +72,75 @@ public class RequestService {
}
long endExecTime = System.currentTimeMillis();
logger.debug("Paths fetched in {} ms", endExecTime - startExecTime);
}
catch (SQLException e) {
logger.error("Failed to execute query for RequestArgument", e);
throw new RuntimeException("Error executing database query: " + argument.getRequestArgumentURL(), e);
} catch (SQLException e) {
logger.error("Failed to execute query for RequestArgument: {}", argument.getRequestArgumentURL(), e);
throw new RuntimeException("Database query error for argument: " + argument.getRequestArgumentURL(), e);
}
}
}
if (files.isEmpty()) {
logger.warn("No files found for S3 request");
throw new RuntimeException("No files found for S3 request");
throw new NoSuchElementException("No files found for S3 request");
}
files.forEach(file -> {
HttpRequest httpRequest;
logger.debug("Starting building HTTP request for S3 request");
long startExecTime = System.currentTimeMillis();
if (file == null || file.isBlank()) {
logger.warn("Skipping invalid file path: {}", file);
throw new RuntimeException("Invalid file path");
}
for (String file : files) {
try {
httpRequest = S3HttpConnection.buildHttpRequest(request, file);
processFileForS3Request(request, file);
} catch (RuntimeException e) {
logger.error("Error processing file: {}", file, e);
throw e; // Rethrow to propagate for exception handling
}
catch (Exception e) {
throw new RuntimeException(e);
}
long endExecTime = System.currentTimeMillis();
logger.debug("HTTP request built in {} ms", endExecTime - startExecTime);
}
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenAccept(response -> {
if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT
|| response.statusCode() == HttpURLConnection.HTTP_OK) {
logger.info("Successfully deleted object {}", file);
}
else {
logger.error("Failed to delete object {}. Response code: {}", file,
response.statusCode()
);
}
})
.exceptionally(ex -> {
logger.error("Failed to delete object {}", file, ex);
return null;
});
});
}
catch (Exception e) {
} catch (Exception e) {
logger.error("Failed to process S3 request: {}", request, e);
throw e; // Rethrow exception to propagate to the handler
}
}
private void processFileForS3Request(S3Request request, String file) {
if (file == null || file.isBlank()) {
logger.warn("Skipping invalid file path: {}", file);
throw new RuntimeException("Invalid file path");
}
try {
logger.debug("Starting building HTTP request for file: {}", file);
long startExecTime = System.currentTimeMillis();
HttpRequest httpRequest = S3HttpConnection.buildHttpRequest(request, file);
long endExecTime = System.currentTimeMillis();
logger.debug("HTTP request built in {} ms for file: {}", endExecTime - startExecTime, file);
HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT || response.statusCode() == HttpURLConnection.HTTP_OK) {
logger.info("Successfully deleted object {}", file);
} else {
handleErrorResponse(response, file);
}
} catch (Exception e) {
logger.error("Error sending HTTP request for file: {}", file, e);
throw new RuntimeException("HTTP request error for file: " + file, e);
}
}
private void handleErrorResponse(HttpResponse<String> response, String file) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputSource is = new InputSource(new StringReader(response.body()));
Document doc = builder.parse(is);
Element root = doc.getDocumentElement();
String message = root.getElementsByTagName("Message").item(0).getTextContent();
logger.error("Failed to delete object {}. Response code: {}. Error message: {}", file, response.statusCode(), message);
throw new RuntimeException("Failed to delete object: " + file);
} catch (Exception e) {
logger.error("Failed to parse error response for file {}", file, e);
throw new RuntimeException("Error parsing HTTP response: " + response.body(), e);
}
}
@Transactional
public void processSqlAndAqlRequests(Requests config, List<String> ids) {
logger.debug("Starting transactional processing of requests");
@ -151,7 +155,9 @@ public class RequestService {
processAqlRequests(request, ids);
}
}
processS3Requests(config.getS3Requests(), ids);
if (config.getS3Requests() != null && !config.getS3Requests().isEmpty()) {
processS3Requests(config.getS3Requests(), ids);
}
}
private void processSqlRequests(SqlRequest request, List<String> ids) {