Регистрировать исключения контроллера Spring Boot без изменения тела ответа об ошибке или кода состояния - PullRequest
0 голосов
/ 28 апреля 2020

Благодаря DefaultErrorAttributes Spring Boot по умолчанию возвращает тело ответа для исключений, которое отвечает моим потребностям, из коробки:

{
  "timestamp": 1588022957431,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.bind.MethodArgumentNotValidException",
  "errors": [
    {
      "codes": [
        "NotEmpty.myResource.label",
        "NotEmpty.label",
        "NotEmpty.java.lang.String",
        "NotEmpty"
      ],
      "arguments": [
        {
          "codes": [
            "myResource.label",
            "label"
          ],
          "arguments": null,
          "defaultMessage": "label",
          "code": "label"
        }
      ],
      "defaultMessage": "must not be empty",
      "objectName": "myResource",
      "field": "label",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotEmpty"
    }
  ],
  "message": "Validation failed for object='myResource'. Error count: 1"
}

Кроме того, между встроенным обработка исключений, предоставляемая DefaultHandlerExceptionResolver, и моя собственная настраиваемая обработка ошибок, у меня есть исключения, возвращающие именно те коды ответов, которые я хочу предоставить для моего REST API.

Что я хотел бы сделать это журнал исключений, выданных для целей отладки. Если исключение приводит к ответу 4xx, я хотел бы зарегистрировать его как уровень отладки, так как это проблема с запросом клиента, а не то, что мне, как правило, требуется для входа в систему (но это может быть полезно при спецификация отладки c выдает или проверяет правильность кода). Если это приводит к исключению 5xx, я хотел бы зарегистрировать его на уровне предупреждения, поскольку это указывает на неожиданную проблему с сервером, о которой я, вероятно, хотел бы сообщить.

Я не вижу хорошего механизм добавления в эту запись при сохранении как тела ответа Spring Boot по умолчанию, так и моих настроенных отображений кода состояния. Как я могу сделать это условное ведение журнала на основе кода состояния при сохранении моего текущего тела ответа и кодов состояния? Более того, есть ли способ сделать это, который также будет работать с несколькими имеющимися у меня конечными точками, которые используют свои собственные настроенные @ExceptionHandler методы для возврата другого тела ответа?

Ответы [ 2 ]

0 голосов
/ 30 апреля 2020

Я решил это с помощью javax.servlet.Filter, который извлекает выброшенное исключение из ErrorAttributes:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.filter.OrderedFilter;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;

@Slf4j
@Component
public class ExceptionLoggingFilter extends OncePerRequestFilter implements OrderedFilter {
    @Autowired
    private ErrorAttributes errorAttributes;

    @Override
    public int getOrder() {
        return OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        filterChain.doFilter(request, response);

        logError(request, response);
    }

    private void logError(HttpServletRequest request, HttpServletResponse response) {
        Throwable error = errorAttributes.getError(new ServletWebRequest(request));
        if (error == null) {
            return;
        }

        int statusCode = response.getStatus();
        String reasonPhrase = Optional.ofNullable(HttpStatus.resolve(statusCode))
                .map(HttpStatus::getReasonPhrase).orElse("<nonstandard status code>");

        String uriString = request.getRequestURI()
                + Optional.ofNullable(request.getQueryString()).map(q -> "?" + q).orElse("");

        HttpStatus.Series series = HttpStatus.Series.resolve(statusCode);
        if (HttpStatus.Series.SERVER_ERROR.equals(series)) {
            log.warn("{} {} response for {}", statusCode, reasonPhrase, uriString, error);
        } else if (HttpStatus.Series.CLIENT_ERROR.equals(series)){
            log.debug("{} {} response for {}", statusCode, reasonPhrase, uriString, error);
        }
    }
}
0 голосов
/ 28 апреля 2020

Одно из предложенных мной решений, которое обрабатывает случай DefaultErrorAttributes, но не пользовательское @ExceptionHandler, - это переопределение встроенного контроллера ошибок Spring Boot (как определено ErrorMvcAutoConfiguration.basicErrorController ( ) ) чтобы зарегистрировать исключения перед возвратом результата:

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;

@Slf4j
public class LoggingErrorController extends BasicErrorController {
    private final ErrorAttributes errorAttributes;

    public LoggingErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties,
                                  List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorProperties, errorViewResolvers);
        this.errorAttributes = errorAttributes;
    }

    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        logError(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        return super.errorHtml(request, response);
    }

    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        logError(request, isIncludeStackTrace(request, MediaType.ALL));
        return super.error(request);
    }

    private void logError(HttpServletRequest request, boolean includeStackTrace) {
        Throwable error = errorAttributes.getError(new ServletWebRequest(request));
        Map<String, Object> attributesMap = getErrorAttributes(request, includeStackTrace);

        Object path = attributesMap.getOrDefault("path", "<unknown>");

        HttpStatus status = getStatus(request);
        if (status.is4xxClientError()) {
            log.debug("{} {} error for path {}", status.value(), status.getReasonPhrase(), path, error);
        } else {
            log.warn("{} {} error for path {}", status.value(), status.getReasonPhrase(), path, error);
        }
    }
}

Я чувствую, что это немного странно и очень зависит от текущей реализации Spring Boot, но в остальном кажется, что это работает и выглядит довольно solid.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...