首页 > 解决方案 > Logback 不会在 Spring Boot 的 @ExceptionHandler 中记录 ConstraintViolationException

问题描述

我正在对我的 Spring Boot Web 服务中的查询参数进行一些验证。在这种情况下,它是一个与正则表达式不匹配的参数[0-9]{3}。所以在服务方法中,有一个验证:

@Pattern(regexp="[0-9]{3}") @Valid @RequestParam(value = "AngivelseFrekvensForholdUnderkontoArtKode", required = false) String angivelseFrekvensForholdUnderkontoArtKode

angivelseFrekvensForholdUnderkontoArtKode只是查询参数的名称)

我正在开发一个日志管理器,它基本上只是使用 logback 和 slf4j 打印日志消息。

我的日志管理器类中有一个writeInternalError(exception)在被告知时可以很好地记录异常:

public void writeInternalError(Exception exception) {
    logger.error(exception.getClass().getName(), kv("LogType", exception), kv("LogMessage", exception));
}

除非ConstraintViolationException@ExceptionHandler我的@ControllerAdvice. 没有显示错误,并且输出了 Spring 日志而不是我预期的自定义日志。当我调试时,logger.error()似乎已执行并且没有显示错误。

我做了一个快速修复方法,我手动提取异常信息,但我想对所有异常使用相同的日志记录方法:

public void writeTracelog(Exception exception) {
    logger.error(exception.getClass().getName(), kv("LogType", "exception"), kv("ErrorMessage", exception.getMessage()), kv("StackTrace", exception.getStackTrace()));
}

我得到的预期和意外日志是:

// The Spring log message shown instead of my custom error message:
{
    "@timestamp": "2021-06-10T12:13:40.730+02:00",
    "@version": "1",
    "message": "Resolved [javax.validation.ConstraintViolationException: call29f0dab4A3094a30A1cdE29c01f28af8.angivelseFrekvensForholdUnderkontoArtKode: must match \"[0-9]{3}\"]",
    "logger_name": "org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver",
    "thread_name": "http-nio-8082-exec-1",
    "level": "WARN",
    "level_value": 30000
}

// How the log is supposed to look like
{
    "@timestamp": "2021-06-10T14:35:18.257+02:00",
    "@version": "1",
    "message": "javax.validation.ConstraintViolationException",
    "logger_name": "ClsLogManager",
    "thread_name": "http-nio-8082-exec-1",
    "level": "ERROR",
    "level_value": 40000,
    "LogType": "exception",
    "LogMessage": {
        "cause": null,
        "stackTrace": [...],
        "constraintViolations": null,
        "message": "call29f0dab4A3094a30A1cdE29c01f28af8.angivelseFrekvensForholdUnderkontoArtKode: must match \"[0-9]",
        "suppressed": [],
        "localizedMessage": "call29f0dab4A3094a30A1cdE29c01f28af8.angivelseFrekvensForholdUnderkontoArtKode: must match \"[0-9]"
    }
}

当我调用writeInternalError()任何其他异常时,日志会很好地输出。我尝试了不同的日志记录方式,以查看哪些有效,哪些无效,正如您在处理程序中看到的那样@ControllerAdvice

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ConstraintViolationException.class)
    protected ResponseEntity<Object> handleConflict(ConstraintViolationException ex, HttpServletRequest request) {
        ...
        // Get the invalid parameter from the ConstraintViolationException

        if (invalidParameter.equalsIgnoreCase("angivelseFrekvensForholdUnderkontoArtKode")) {
            errorMessage = setErrorMessage(request, "422.9", HttpStatus.UNPROCESSABLE_ENTITY.value(), invalidValue);
            clsLogManager.writeTracelog(ex); // Outputs customized unwanted log
            clsLogManager.writeInternalError(new ConstraintViolationException(null)); // Outputs exception in the format I want
            clsLogManager.writeInternalError(ex); // Outputs nothing
            responseEntity = writeToAuditlog(request, inputHeaders, errorMessage); // Outputs an info log as it supposed to

            return responseEntity; // Outputs the ExceptionHandlerExceptionResolver message after the return
        }
        // Do something else in case of another error
    }
}

看起来记录器无法处理异常,但它为什么不告诉我为什么,如果这是真的,为什么ExceptionHandlerExceptionResolver要这样做呢?

更新:

ExceptionHandlerExceptionResolver按照保护程序的建议进行了调查,发现日志来自AbstractHandlerExceptionResolver's logException()。我的自定义记录器类的方法在之前被调用logException(),但它仍然不打印任何东西。可能是因为它是一个ConstraintViolationException包含该字段的字段constraintViolations并且记录器不知道如何处理这个?

如果我不想要 Spring 日志,我想有一种setWarnLogCategory方法可以关闭。我只是不知道怎么做。logExceptionin的 javadocsAbstractHandlerExceptionResolver表明有一个属性,但我不知道如何设置它。

标签: javaspring-bootexceptionlogback

解决方案


更新:

问题在于该类中的方法subAppend(E event)OutputStreamAppender编码的父级ConsoleAppender和编码行: byte[] byteArray = this.encoder.encode(event); 编码器尝试序列化ConstraintViolationException异常并且杰克逊失败并出现错误:HV000116: getParameterIndex() may only be invoked for nodes of type ElementKind.PARAMETER.

在此处输入图像描述

并且由于编码的结果是空字节数组,以防出现异常,这就是为什么控制台中没有记录任何内容的原因。见下文:

在此处输入图像描述

我现在没有快速解决方法。

旧提案:

我建议doResolveHandlerMethodExceptionExceptionHandlerExceptionResolver类中调试方法,您会看到只有一个地方 Spring Boot 记录器可以记录具有警告级别的消息,并且该记录器仅在调用异常处理程序方法期间发生某些事情时才起作用(例如:不正确的类型处理程序方法中的参数等)。您将看到未调用处理程序方法的原因。 在此处输入图像描述

请注意类ConstraintViolationException可以位于两个不同包中的情况:

  • javax.validation.ConstraintViolationException
  • org.hibernate.exception.ConstraintViolationException

当然,在我们的例子中,我们应该使用ConstraintViolationExceptionfrom javax.validationpackage


推荐阅读