Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically generate a stack frame with debug log information

For better debugging, I would often like to have:

Exception 
  at com.example.blah.Something.method()
  at com.example.blah.Xyz.otherMethod()
  at com.example.hello.World.foo()
  at com.example.debug.version_3_8_0.debug_info_something.Hah.method() // synthetic method
  at com.example.x.A.wrappingMethod()

The debug stack frame as shown above would be dynamically generated, just like a java.lang.reflect.Proxy, except that I'd like to be in full control of the entire fully qualified method name that ends up on the proxy.

At the call site, I would do something silly and simple as this:

public void wrappingMethod() {
    run("com.example.debug.version_3_8_0.debug_info_something.Hah.method()", () -> {
        World.foo();
    });
}

As you can see, the wrappingMethod() is a real method that ends up on the stack trace, Hah.method() is a dynamically generated method, whereas World.foo() is again a real method.

Yes, I know this pollutes the already deep deep stack traces. Don't worry about it. I have my reasons.

Is there a (simple) way to do this or something similar as the above?

like image 730
Lukas Eder Avatar asked Sep 26 '16 21:09

Lukas Eder


1 Answers

No need for code generation to solve this problem:

static void run(String name, Runnable runnable) {
  try {
    runnable.run();
  } catch (Throwable throwable) {
    StackTraceElement[] stackTraceElements = throwable.getStackTrace();
    StackTraceElement[] currentStackTrace = new Throwable().getStackTrace();
    if (stackTraceElements != null && currentStackTrace != null) { // if disabled
      int currentStackSize = currentStackStrace.length;
      int currentFrame = stackTraceElements.length - currentStackSize - 1;
      int methodIndex = name.lastIndexOf('.');
      int argumentIndex = name.indexOf('(');
      stackTraceElements[currentFrame] = new StackTraceElement(
          name.substring(0, methodIndex),
          name.substring(methodIndex + 1, argumentIndex),
          null, // file name is optional
          -1); // line number is optional
      throwable.setStackTrace(stackTraceElements);
    }
    throw throwable;
  }
}

With code generation, you could add a method with the name, redefine the call site within the method, unwind the frame and call the generated method but this would be much more work and would never be equally stable.

This strategy is a rather common approach in testing frameworks, we do it a lot in Mockito and also other utilities like JRebel do it to hide their magic by rewriting exception stack frames.

When Java 9 is used, it would be more efficient to do such manipulations using the Stack Walker API.

like image 189
Rafael Winterhalter Avatar answered Sep 28 '22 10:09

Rafael Winterhalter