如何使用调试日志信息动态生成堆栈帧
How to dynamically generate a stack frame with debug log information
为了更好的调试,我经常想要:
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()
如上所示的调试堆栈帧将动态生成,就像 java.lang.reflect.Proxy
一样,只是我想完全控制最终出现在代理上的整个完全限定方法名称.
在调用站点,我会做一些愚蠢而简单的事情:
public void wrappingMethod() {
run("com.example.debug.version_3_8_0.debug_info_something.Hah.method()", () -> {
World.foo();
});
}
如您所见,wrappingMethod()
是一个真正的方法,最终出现在堆栈跟踪中,Hah.method()
是一个动态生成的方法,而 World.foo()
又是一个真正的方法.
是的,我知道这会污染已经很深的堆栈跟踪。别担心。我有我的理由。
是否有(简单的)方法可以做到这一点或与上述类似的方法?
不需要代码生成来解决这个问题:
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;
}
}
使用代码生成,您可以添加一个带有名称的方法,在方法中重新定义调用站点,展开框架并调用生成的方法,但这会增加很多工作量,而且永远不会同样稳定。
这种策略在测试框架中是一种相当常见的方法,we do it a lot in Mockito 其他实用程序(如 JRebel)也这样做是为了通过重写异常堆栈帧来隐藏它们的魔力。
当使用 Java 9 时,使用 Stack Walker API 进行此类操作会更有效率。
为了更好的调试,我经常想要:
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()
如上所示的调试堆栈帧将动态生成,就像 java.lang.reflect.Proxy
一样,只是我想完全控制最终出现在代理上的整个完全限定方法名称.
在调用站点,我会做一些愚蠢而简单的事情:
public void wrappingMethod() {
run("com.example.debug.version_3_8_0.debug_info_something.Hah.method()", () -> {
World.foo();
});
}
如您所见,wrappingMethod()
是一个真正的方法,最终出现在堆栈跟踪中,Hah.method()
是一个动态生成的方法,而 World.foo()
又是一个真正的方法.
是的,我知道这会污染已经很深的堆栈跟踪。别担心。我有我的理由。
是否有(简单的)方法可以做到这一点或与上述类似的方法?
不需要代码生成来解决这个问题:
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;
}
}
使用代码生成,您可以添加一个带有名称的方法,在方法中重新定义调用站点,展开框架并调用生成的方法,但这会增加很多工作量,而且永远不会同样稳定。
这种策略在测试框架中是一种相当常见的方法,we do it a lot in Mockito 其他实用程序(如 JRebel)也这样做是为了通过重写异常堆栈帧来隐藏它们的魔力。
当使用 Java 9 时,使用 Stack Walker API 进行此类操作会更有效率。