根据日志级别从日志中隐藏异常堆栈跟踪

Hide the exception stacktrace from logs depending on log level

假设我们有 2 个项目:项目 A 和 B

项目 A

这个项目定义了一些常见的异常。没有 slf4j 依赖项。 异常示例

public abstract class SomeException extends RuntimeException {

    public SomeException (String msg) {
        super(msg);
    }

    // just added, being able to hide the stacktrace, but it contains an additional param
    public SomeException (String msg, boolean suppressStacktrace) {
        super(msg, null, suppressStacktrace, !suppressStacktrace);
    }

}

项目 B

这是主要项目。它包含 Project A 依赖项,并调用已定义的 SomeException 在很多地方。该项目包含 slf4j 依赖项,并包含我们指定日志记录级别的 slf4j 配置。我想在日志级别为 DEBUG 时显示堆栈跟踪。

挑战

我有很多throw new SomeException(String msg)的用法,我想隐藏项目A中的stacktrace,基于父项目项目 B 不更改异常签名,因为我必须在 100 个地方更改它。我无法在 if/else 语句中进行 2 次超级调用。所以最后的变化看起来像:

public SomeException (String msg) {
    // how to get the surpressStackTrace param from the parent project config?
    this(msg, surpressStackTrace);
}

如果这种方法不对,我还有什么其他的可能性可以在不更改调用的异常签名的情况下隐藏堆栈跟踪?

注意:这些异常是非阻塞的。即使发生异常,流程也不会中断。能不能用@ControllerAdvice,让异常处理后继续流程?

我也试过这个:

@ControllerAdvice
public class ControllerAdvisor {
    private static final Logger logger = LogManager.getLogger(ControllerAdvisor.class);

    @ExceptionHandler(SomeException .class)
    public void handleSomeException(SomeException ex) {
        logger.log(Level.INFO, ex.getMessage());

        if(Level.DEBUG.equals(logger.getLevel())){
            logger.log(Level.DEBUG, ex.getStackTrace());
        }
    }
}

但是,这会终止进一步逻辑的执行。异常处理后是否可以继续流程?

不确定我是否完全理解你的问题...

但这里有一个可能的解决方案:

  • 忽略 SomeException(你可以,因为它是一个 RuntimeException)
  • 保持正常执行流程。
  • 当异常被抛出时记录,当 log-level 是 DEBUG 时有堆栈跟踪,当 log-level 不是 DEBUG
  • 时没有

这可以通过 Aspect

  1. 确保你的类路径上有 spring-aop;对于 spring-boot,您可以包括 spring-boot-starter-aop 作为依赖项。

  2. 通过将 @EnableAspectJAutoProxy 添加到您的配置之一来启用 AOP 类。

  3. 写一个@Around-advise,拦截所有可能发生异常的方法。执行 try-catch,但在抛出 SomeException 时继续正常执行代码。

  4. 根据日志级别使用或不使用堆栈跟踪来记录消息。

这可能看起来像这样:

package com.example.demo.aop;

import com.example.demo.exception.SomeException;
import org.aspectj.lang.*;
import org.aspectj.lang.annotation.*;
import org.slf4j.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DemoAspect {

    private static final Logger logger = LoggerFactory.getLogger(DemoAspect.class);
    
    // all executed public method-calls in the package com.example.demo
    @Pointcut(value = "execution(public * com.example.demo..*(..))")
    private void pointcut(){
    }


    // intercept the defined pointcut
    @Around("pointcut()")
    public Object ignoreSomeException(ProceedingJoinPoint pjp) throws Throwable {
        Object result = null;
        try {
            result =  pjp.proceed(); // <-- try normal execution
        } catch (SomeException e) {
            log(e);
            result =  pjp.proceed(); // <-- continue normal execution
        } finally {
            return result;
        }
    }
    
    // Log the exception based on the loglevel
    private void log(Throwable e) {
        if(logger.isDebugEnabled()) {
            logger.error(e.getMessage(), e); // <-- log with stacktrace
        } else {
            logger.error(e.getMessage());    // <-- only log the message
        }
    }
}