What is a good way to pass useful state information to an exception in Java?

前端 未结 11 1867
说谎
说谎 2021-02-04 06:23

I noticed some confusion initially with my question. I\'m not asking about how to configure a logger nor how to use a logger properly, but rather how to capture all of the infor

相关标签:
11条回答
  • 2021-02-04 06:45

    Take a look at the MemoryHandler class from java.util.logging. It acts as a buffer between your log.$level() invocations and the actual output, and will pass it's buffer content into the output only if some condition is met.

    For example you could configure it to dump content only if it sees ERROR level message. Then you can safely output DEBUG level messages and no one will see them unless actual error occurs and then all messages are written to log file.

    I would guess there are similar implementations for other logging frameworks.

    EDIT: One possible issue with this approach is a performance lost on generating all the debug messages (see @djna comment). Because of this it could be a good idea to make the level of logging going into the buffer configurable - in production it should be INFO or higher, and only if you are actively hunting a problem down it could be turned down to DEBUG.

    0 讨论(0)
  • 2021-02-04 06:49

    You answered your own question. If you want to pass the state to the exception, you need to store your state somewhere.

    You have mentioned adding extra variables to do this, but didn't like all the extra variables. Someone else mentioned a MemoryHandler as a buffer (holds state) between the logger and the application.

    These are all the same idea. Create an object that will hold the state you want you show in your exception. Update that object as your code executes. If an error occurs pass that object into the exception.

    Exceptions already do this with StackTraceElements. Each thread keeps a list of the stack trace (method, file, line) which represents its 'state'. When the exception happens, it passes the stack trace to the exception.

    What you seem to be wanting, is a copy of all the local variables also.

    This would mean making a object to hold all your locals and using that object, instead of the locals directly. Then passing the object to the exception.

    0 讨论(0)
  • 2021-02-04 06:52

    One option that no one seems to have mentioned yet is to use a logger that logs to an in memory buffer, and only pushes the information into the actual log target under certain circumstances (e.g., an error level message is logged).

    If you're using the JDK 1.4 logging facilities, MemoryHandler does exactly this. I'm not sure if the logging system you're using does this, but I imagine you should be able to implement your own appender/handler/whatever that does something similar.

    Also, I just want to point out that in your original example, if your concern is variable scope, you could always define a block to reduce the scope of your variable:

    {
        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);
        }
    }
    
    0 讨论(0)
  • 2021-02-04 06:54

    Perhaps I'm missing something, but if the users really require a relatively quiet log file, why don't you just configure your debug logs to go to a separate spot?

    If that's insufficient, then capture a fixed amount of the debug logs in RAM. E.g., the last 500 entries. Then, when something ugly happens, dump the debug logs along with the problem report. You don't mention your logging framework, but this would be pretty easy to do in Log4J.

    Even better, assuming you have the user's permission, just send an automatic error report rather than logging. I recently helped some folks run down a hard-to-find bug and made the error reporting automatic. We got 50x the number of bug reports, making the problem pretty easy to find.

    0 讨论(0)
  • 2021-02-04 06:55

    Besides your example which declares local fields outside the try block in order to be accessible inside the catch block, one very simple way of handling this is to dump the state of the class out in the Exception using the class's overridden toString method. Granted, this is only useful in Classes that maintain state.

    try {
       setMyValue(someObject.getValue());
       doSomething(getMyValue());
    }
    catch (BadThingsHappenException bthe) {
       // consider this a RuntimeException wrapper class
      throw new UnhandledException(toString(), bthe);
    }
    

    Your toString() would need to be overridden:

    public String toString() {
       return super.toString() + "[myValue: " + getMyValue() +"]";
    }
    

    edit:

    another idea:

    You could maintain state in a ThreadLocal debug context. Suppose you create a class called MyDebugUtils which holds a ThreadLocal that contains a Map per Thread. You allow for static access to this ThreadLocal and maintenance methods (ie, to clear the context when your debugging is finished).

    The interface could be:

    public static void setValue(Object key, Object value)
    public static void clearContext()
    public static String getContextString() 
    

    and in our example:

    try {
       MyDebugUtils.setValue("someObeject.value", someObject.getValue());
       doSomething(someObject.getValue());
    } catch (BadThingsHappenException bthe) {
       // consider this a RuntimeException wrapper class
      throw new UnhandledException(MyDebugUtils.getContextString(), bthe);
    } finally {
      MyDebugUtils.clearContext(); 
    }
    

    There might be some issues that you would want to iron out, such as handling cases where your doSomething method also contains a try/catch/finally set that clears the debug context. This could be handled by allowing for finer granularity in the context Map than just the Thread in the process:

    public static void setValue(Object contextID, Object key, Object value)
    public static void clearContext(Object contextID)
    public static String getContextString(Object contextID)
    
    0 讨论(0)
  • 2021-02-04 06:56

    Another good logging API is SLF4J. It can be configured to also intercept log APIs for Log4J, Java Util Logging, and Jakarta Commons Logging. And it can also be configured to use various logging implementations, including Log4J, Logback, Java Util Logging, and one or two others. This gives it enormous flexibility. It was developed by the author of Log4J to be its successor.

    Of relevance to this question, the SLF4J API has a mechanism to concatenate string valued expressions into a log message. The following calls are equivalent, but the second is about 30x faster to process if you're not outputting debug level messages, since the concatenation is avoided:

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

    There's a two argument version too:

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

    And for more than two you can pass in an array of Object like this:

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

    This is a nice terse format that eliminates clutter.

    Example source is from the SLF4J FAQ.

    Edit: Here's a possible refactoring of your example:

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

    Or if this pattern occurs more than a few places you could write a set of static methods that capture the commonality, something like:

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

    and of course the method would also put the formatted message out on the logger for you.

    0 讨论(0)
提交回复
热议问题