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

Eli*_*jah 15 java error-handling logging exception

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

我已经注意到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);
}
Run Code Online (Sandbox Code Playgroud)

上述方法的缺点是,如果你的用户需要相对安静的日志,需要点高度的可靠性,他们就不能"再次在调试模式下尝试",异常消息包含足够的数据本身是对开发者有用.

我看到的下一个模式试图缓解这个问题,但看起来很难看:

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

是否有一些Java丢失的Java vodoo?你如何处理你的异常状态信息?

Csa*_*a_H 9

我们倾向于使用一些特殊的构造函数,一些常量和一个ResourceBundle创建我们最重要的特定于应用程序的运行时异常类.

示例代码段:

 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);
    }
 }
Run Code Online (Sandbox Code Playgroud)

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

 problemCodeOne=Simple exception message
 problemCodeTwo=Parameterized exception message for {0} value
Run Code Online (Sandbox Code Playgroud)

使用这种方法

  • 我们可以使用非常易读和易懂的throw子句(throw new MyException(MyException.PROBLEMCODETWO, new Object[] {parameter}, bthe))
  • 异常消息是"集中的",可以轻松维护和"国际化"

编辑:改为getMessagegetLocalizedMessage利亚建议.

编辑2:忘记提及:这种方法不支持"动态"更改区域设置,但它是有意的(如果需要,可以实现).

  • 我不认为应该将例外文本国际化.GUI上显示的错误消息不同,但异常应尽可能简单.如果无法读取ResourceBundle会发生什么? (7认同)

Wil*_*tri 6

也许我错过了一些东西,但如果用户真的需要一个相对安静的日志文件,你为什么不配置你的调试日志去一个单独的位置呢?

如果这还不够,那么在RAM中捕获一定量的调试日志.例如,最后500个条目.然后,当发生丑陋的事情时,将调试日志与问题报告一起转储.你没有提到你的日志框架,但在Log4J中这很容易做到.

更好的是,假设您拥有用户的权限,只需发送自动错误报告而不是记录.我最近帮助一些人发现了一个难以发现的错误并自动报告错误.我们获得了错误报告数量的50倍,这使问题很容易找到.


Jim*_*ans 5

另一个好的日志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);
Run Code Online (Sandbox Code Playgroud)

还有两个参数版本:

logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);
Run Code Online (Sandbox Code Playgroud)

对于两个以上,您可以传递一个Object数组,如下所示:

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

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

示例源来自SLF4J FAQ.

编辑:这是您的示例可能的重构:

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

或者,如果此模式出现在多个地方,您可以编写一组捕获共性的静态方法,如:

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

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