在 Java 中,将有用的状态信息传递给异常的好方法是什么?

2022-09-02 12:25:03

我注意到最初对我的问题有些困惑。我不是在问如何配置记录器,也不是在问如何正确使用记录器,而是如何捕获在低于异常消息中当前日志记录级别的日志记录级别记录的所有信息。

我注意到Java中有两种模式用于记录信息,当发生异常时,这些信息可能对开发人员有用。

以下模式似乎很常见。基本上,您只需根据需要将记录器日志信息内联,以便在发生异常时,您就拥有了日志跟踪。

try {
    String myValue = someObject.getValue();
    logger.debug("Value: {}", myValue);
    doSomething(myValue);
}
catch (BadThingsHappenException bthe) {
    // consider this a RuntimeException wrapper class
    throw new UnhandledException(bthe);
}

上述方法的缺点是,如果您的用户需要相对安静的日志,并且需要高级别的可靠性,以至于他们无法“在调试模式下重试”,则异常消息本身包含的数据不足,无法对开发人员有用。

下一个模式是我见过的一个试图缓解这个问题但看起来很丑陋的模式:

String myValue = null;
try {
    myValue = someObject.getValue();
    doSomething(myValue);
}
catch (BadThingsHappenException bthe) {
    String pattern = "An error occurred when setting value. [value={}]";
    // note that the format method below doesn't barf on nulls
    String detail = MessageFormatter.format(pattern, myValue);
    // consider this a RuntimeException wrapper class
    throw new UnhandledException(detail, bthe);
}

上面的模式似乎在某种程度上解决了这个问题,但是,我不确定我是否喜欢在try块的范围之外声明这么多变量。特别是,当我必须处理非常复杂的状态时。

我见过的唯一另一种方法是使用Map来存储键值对,然后将这些键值对转储到异常消息中。我不确定我是否喜欢这种方法,因为它似乎会产生代码膨胀。

有没有一些Java巫毒教,我错过了?如何处理异常状态信息?


答案 1

我们倾向于使用一些特殊的构造函数、一些常量和资源捆绑包来创建我们最重要的特定于应用程序的运行时异常类。

示例代码段:

 public class MyException extends RuntimeException
 {
    private static final long serialVersionUID = 5224152764776895846L;

    private static final ResourceBundle MESSAGES;
    static
    {
        MESSAGES = ResourceBundle.getBundle("....MyExceptionMessages");
    }

    public static final String NO_CODE = "unknown";
    public static final String PROBLEMCODEONE = "problemCodeOne";
    public static final String PROBLEMCODETWO = "problemCodeTwo";
    // ... some more self-descriptive problem code constants

    private String errorCode = NO_CODE;
    private Object[] parameters = null;

    // Define some constructors

    public MyException(String errorCode)
    {
        super();
        this.errorCode = errorCode;
    }

    public MyException(String errorCode, Object[] parameters)   
    {
        this.errorCode = errorCode;
        this.parameters = parameters;
    }

    public MyException(String errorCode, Throwable cause)
    {
        super(cause);
        this.errorCode = errorCode;
    }

    public MyException(String errorCode, Object[] parameters, Throwable cause)
    {
        super(cause);
        this.errorCode = errorCode;
        this.parameters = parameters;
    }   

    @Override
    public String getLocalizedMessage()
    {
        if (NO_CODE.equals(errorCode))
        {
            return super.getLocalizedMessage();
        }

        String msg = MESSAGES.getString(errorCode); 
        if(parameters == null)
        {
            return msg;
        }
        return MessageFormat.format(msg, parameters);
    }
 }

在属性文件中,我们指定异常描述,例如:

 problemCodeOne=Simple exception message
 problemCodeTwo=Parameterized exception message for {0} value

使用此方法

  • 我们可以使用相当可读和可理解的抛出子句(throw new MyException(MyException.PROBLEMCODETWO, new Object[] {parameter}, bthe))
  • 异常消息是“集中的”,可以轻松维护和“国际化”

编辑:更改为以利亚建议的。getMessagegetLocalizedMessage

编辑2:忘了提一下:这种方法不支持 Locale“动态”更改,但它是有意的(如果需要,可以实现它)。


答案 2

另一个好的日志记录API是SLF4J。它可以配置为拦截Log4J,Java Util Logging和Jakarta Commons Logging的日志API。它还可以配置为使用各种日志记录实现,包括Log4J,Logback,Java Util Logging以及一个或两个其他实现。这给了它巨大的灵活性。它是由Log4J的作者开发的,作为其继任者。

与此问题相关的是,SLF4J API 具有将字符串值表达式连接到日志消息中的机制。以下调用是等效的,但是如果您不输出调试级别的消息,则第二个调用的处理速度大约快 30 倍,因为避免了串联:

logger.debug("The new entry is " + entry + ".");
logger.debug("The new entry is {}.", entry);

还有两个参数版本:

logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);

对于两个以上的对象,您可以像这样传入一个对象数组:

logger.debug("Value {} was inserted between {} and {}.", 
             new Object[] {newVal, below, above});

这是一个很好的简洁格式,可以消除混乱。

示例来源来自 SLF4J 常见问题解答

编辑:下面是对示例的可能重构:

try {
    doSomething(someObject.getValue());
}
catch (BadThingsHappenException bthe) {
  throw new UnhandledException(
    MessageFormatter.format("An error occurred when setting value. [value={}]", 
                              someObject.getValue()), 
    bthe);
}

或者,如果此模式出现在多个位置,则可以编写一组捕获共性的静态方法,如下所示:

try {
    doSomething(someObject.getValue());
}
catch (BadThingsHappenException bthe) {
    throwFormattedException(logger, bthe,
                            "An error occurred when setting value. [value={}]", 
                            someObject.getValue()));
}

当然,该方法还会将格式化的消息放在记录器上。


推荐