在Spring Boot应用程序中防止自定义异常的堆栈跟踪日志记录。

42
在Spring Boot(MVC)中是否有一种方法可以记录自定义异常并抛出,而不会在日志文件中显示其堆栈跟踪?但对于任何其他异常仍然可以看到堆栈跟踪。
长说明:我正在使用Spring Boot创建一个简单的rest服务。我喜欢默认情况下没有堆栈跟踪,并且使用基本的异常细节(状态、错误、消息)创建JSON响应来处理自定义异常。
问题是它也不创建任何日志条目,因此我必须手动执行此操作:
自定义异常
@ResponseStatus(value = HttpStatus.CONFLICT)
public class DuplicateFoundException extends RuntimeException {
    public DuplicateFoundException(String message) {
        super(message);
    }
}

在 @RestController 中的服务方法中抛出异常

if (!voteDao.findByItemAndUser(item, voteDto.getUserId()).isEmpty()) {
    log.warn("... already voted ...");   //TODO: don't do this for every throw
    throw new DuplicateFoundException("... already voted ...");
}

有更多的异常会导致在每个抛出之前放置日志语句,我认为这是一种不好的方法。我尝试从服务方法中删除所有日志记录,并创建@ControllerAdvice,在其中记录所有自定义异常并重新抛出它们,以便仍然获得像以前一样漂亮的JSON:

@ControllerAdvice
public class RestExceptionHandler {
    private static final Logger log = Logger.getLogger(RestExceptionHandler.class);

    @ExceptionHandler
    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            log.warn(e.getMessage());
        } else {
            log.error("...");
        }
        throw e;
    }
}

现在问题是我不仅看到了日志条目,还看到了自定义异常的堆栈跟踪,并且找不到如何防止这种情况的方法。我认为这个问题是由于再次抛出异常引起的。可能的解决方法是创建一个自定义异常类,然后返回该类,但我不喜欢这个想法,因为异常编组似乎很好用。有什么提示吗?

所以它到达了log.warn行,但同时也记录了整个异常?我可能错了,但我之前见过不会抛出异常的@ExceptionHandler,它们只是返回一个ResponseEntity(带有状态500等)。 - rogerdpack
6个回答

54

从1.3.0版本开始,“Never”是默认设置。请查看发布说明 - shark1608
1
@shark1608 尽管发布说明中是这样说的,但使用Spring Boot 2.1.7时默认情况下设置为“Always”。 - Robber
2
@Robber 请看下面我的回答,可能会解释为什么它被设置为ALWAYS。 - muttonUp

47

请注意Spring Boot DevTools。

虽然server.error.include-stacktrace的默认设置为NEVER,但如果包含Spring Boot DevTools,则会覆盖为ALWAYS

如果您想了解更多详细信息,请参见此提交,该提交成为Spring Boot 2.1.0+的一部分。 输入图像说明


1
谢谢,感谢您的彻底。我总是喜欢了解情况的“为什么”部分。这又是一个不知道“spring-boot魔法”的案例。 - BitfulByte
如果您更新application.yml(或application.properties)以指定NEVER,Spring Boot DevTools将尊重它(至少在2.3版本中)。 - delitescere
嗯,请检查您是否也覆盖了DefaultErrorAttributes并且没有使用硬编码的includeStackTrace参数调用getErrorAttributes方法——就像我之前所做的那样,呃! - hello_earth

25

如果您不需要堆栈跟踪,可以通过在异常类中覆盖fillInStackTrace方法来禁用堆栈跟踪。

public class DuplicateFoundException extends RuntimeException {
    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }
}

当您调用e.printStackTrace()时,不会打印堆栈跟踪。

另请参见此博客文章


谢谢,堆栈跟踪已经消失了,但我仍然看到“2015-07-07 11:47:57.188 ERROR 41456 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Failed to invoke @ExceptionHandler method: public org.springframework.web.servlet.ModelAndView”。有没有办法也摆脱这个,这样我就不会在自定义异常的日志中看到任何错误了吗?我接受了答案,因为堆栈跟踪已经消失了,但现在我发现我的异常消息在日志中出现了两次(1.日志语句和2.失败方法的异常消息)。 - rhorvath
我认为没有简单的方法来解决这个问题。也许你应该考虑不使用异常(即自定义状态码),因为这种情况并不是异常情况,而是可以预料的。https://dev59.com/CHA75IYBdhLWcg3wlqOh - Roland Weisleder
我的用例基本上与官方Spring文档中的示例相同(https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc),因此我不会称其为可预期。否则,我们也可以就ValidationException进行争论。如我在问题中所述,创建自定义返回对象是可能的,但如果可能的话,我正在尝试找到更简单和更清晰的解决方案。 - rhorvath
1
@RolandWeisleder 谢谢您的回答,真的帮了我很大的忙。 - Ashu Phaugat

1
调用重载的超类构造函数以避免在响应中显示堆栈跟踪。
public class ProductCustomException extends Exception{
    
    private static final long serialVersionUID = -291211739734090347L;

    public ProductCustomException(String message) {
        super(message,null,false,false);
    }

}

异常类中的超级加载构造函数

 protected Exception(String message, Throwable cause,
                        boolean enableSuppression,
                        boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
    
}

0
解决方案是将异常处理留给Spring Boot,这样自定义异常就不会被记录,而其他异常则会默认记录。我删除了@ControllerAdvice以及来自rest控制器的日志语句,并在自定义异常构造函数中添加了日志语句。
public DuplicateFoundException(String message) {
    super(message);
    LOGGER.warn(message);
}

我不确定这是否是最佳方法,但现在我只在一个地方拥有自定义异常日志记录,而不必为每个异常重复记录语句或在日志中查看其堆栈跟踪或任何其他错误消息。


0

值得一提的是,对于像我这样的最新版本的Springboot(例如3.0.4),即使您在pom.xml中有Devtools,并且假设默认情况下您的服务器错误堆栈跟踪设置为server.error.include-stacktrace=never,当您将应用程序构建为JAR文件时,由于使用了自定义异常类,因此不会显示堆栈跟踪。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接