在 Spring Boot 应用程序中防止自定义异常的堆栈跟踪日志记录
Posted
技术标签:
【中文标题】在 Spring Boot 应用程序中防止自定义异常的堆栈跟踪日志记录【英文标题】:Prevent stack trace logging for custom exception in Spring Boot application 【发布时间】:2015-09-23 21:07:38 【问题描述】: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 ...");
有更多的异常会导致在每次抛出之前放置日志语句,我认为这是一种不好的方法。我尝试从服务方法中删除所有日志语句并创建 @ControlledAdvice 我将在其中记录所有自定义异常并重新抛出它们,这样我仍然可以像以前一样获得漂亮的 json: p>
@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
s 不要扔,他们只是返回ResponseEntity
(状态为500等)而不是FWIW ...
【参考方案1】:
如果您不需要堆栈跟踪,您可以通过在异常类中覆盖 fillInStackTrace
来抑制堆栈跟踪。
public class DuplicateFoundException extends RuntimeException
@Override
public synchronized Throwable fillInStackTrace()
return this;
当您调用 e.printStackTrace()
时,不会打印任何堆栈跟踪。
另见this blog post。
【讨论】:
谢谢,堆栈跟踪消失了,但我仍然看到“2015-07-07 11:47:57.188 ERROR 41456 --- [nio-8080-exec-1] .mmaExceptionHandlerExceptionResolver : 无法调用@ExceptionHandler 方法:公共 org.springframework.web.servlet.ModelAndView"。有没有办法摆脱这种情况,所以我在日志中看不到自定义异常的任何错误?我接受了答案,因为堆栈跟踪已经消失,但现在我看到我的异常消息在日志中出现了两次(1. 日志语句和 2. 来自失败方法的异常消息) 我认为没有简单的方法可以做到这一点。也许您应该考虑不使用异常(即自定义状态代码),因为这种情况并非异常但可以预期。 ***.com/questions/3213094 我的用例与官方 spring 文档 (spring.io/blog/2013/11/01/exception-handling-in-spring-mvc) 中的示例基本相同,因此我认为它不值得期待。否则,我们也可以争论validationException 的观点。正如我在问题中所描述的那样,创建自定义返回对象是可能的,但如果可能的话,我试图找到更简单、更清洁的解决方案。 @RolandWeisleder 感谢您的回答,这对我很有帮助。【参考方案2】:解决方案是将异常处理留给 spring boot,这样自定义异常就不会被记录,而任何其他异常都会被默认记录。我从其余控制器中删除了 @ControllerAdvice 以及日志语句,并将日志语句添加到自定义异常构造函数。
public DuplicateFoundException(String message)
super(message);
LOGGER.warn(message);
我不确定这是否是最好的方法,但现在我只在一个地方有自定义异常日志记录,并且不必为每个异常重复日志语句或查看其堆栈跟踪或任何其他错误消息日志。
【讨论】:
【参考方案3】:我正在使用 Spring Boot 2+ 只需将此行添加到您的 application.properties:
server.error.include-stacktrace=never
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/web/ErrorProperties.IncludeStacktrace.html
【讨论】:
从 1.3.0 开始,默认为“从不”。检查release notes @shark1608 虽然发行说明确实这么说,但使用 Spring Boot 2.1.7 它默认设置为“始终” @Robber 请参阅下面的答案,这可以解释为什么它被设置为 ALWAYS【参考方案4】:警惕 Spring Boot DevTools。
尽管NEVER
是server.error.include-stacktrace
的默认值,但如果您包含Spring Boot DevTools,它会覆盖ALWAYS
。
如果您对更多细节感兴趣,请参阅 this commit,它已成为 Spring Boot 2.1.0+ 的一部分
【讨论】:
谢谢,感谢您的周到。总是喜欢了解情况的“为什么”部分。又一个不知道“弹簧靴魔法”的案例。 如果您更新application.yml
(或application.properties
)以指定NEVER
,Spring Boot DevTools 将尊重它(至少在 2.3 中)
嗯,请检查您是否也没有覆盖 DefaultErrorAttributes 并且没有使用硬编码的 includeStackTrace 参数调用 getErrorAttributes - 正如我所做的那样,呵呵!【参考方案5】:
调用重载的超级构造函数以避免在响应中显示堆栈跟踪。
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);
【讨论】:
以上是关于在 Spring Boot 应用程序中防止自定义异常的堆栈跟踪日志记录的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot 2 实战:如何自定义 Servlet Filter
如何防止 Spring Boot 守护程序/服务器应用程序立即关闭/关闭?
如何防止 Spring Boot 守护程序/服务器应用程序立即关闭/关闭?