btrace已经修改了这个功能https://github.com/btraceio/btrace/issues/327
我之前自己改了之后,抽空修了一下,然后给提了个pull request,将脚本中是否包含原方法参数和是否包含异常参数分开处理,有参数就打印参数:
Type[] sources = Type.getArgumentTypes(getDescriptor());
useArgs = sources.length == 0 || om.getMethodParameter() == -1;
if (useArgs){
vr = validateArguments(om, actionArgTypes, new Type[]{THROWABLE_TYPE});
} else {
vr = validateArguments(om, actionArgTypes, Type.getArgumentTypes(getDescriptor()));
}
ArgumentProvider[] actionArgs;
Label l;
if (useArgs){
actionArgs = buildArgsWithoutParas(throwableIndex);
l = levelCheck(om, bcn.getClassName(true));
loadArguments(actionArgs);
} else {
actionArgs = loadArgsWithParas(throwableIndex);
l = levelCheck(om, bcn.getClassName(true));
loadArguments(vr, actionArgTypes, isStatic(), actionArgs);
}
private ArgumentProvider[] loadArgsWithParas(int throwableIndex){
ArgumentProvider[] actionArgs = new ArgumentProvider[5];
actionArgs[0] = constArg(throwableIndex, THROWABLE_TYPE);
actionArgs[1] = constArg(om.getClassNameParameter(), className.replace('/', '.'));
actionArgs[2] = constArg(om.getMethodParameter(), getName(om.isMethodFqn()));
actionArgs[3] = selfArg(om.getSelfParameter(), Type.getObjectType(className));
actionArgs[4] = new ArgumentProvider(asm, om.getDurationParameter()) {
@Override
public void doProvide() {
MethodTrackingExpander.DURATION.insert(mv);
}
};
return actionArgs;
}
private ArgumentProvider[] buildArgsWithoutParas(int throwableIndex){
ArgumentProvider[] actionArgs = new ArgumentProvider[5];
actionArgs[0] = localVarArg(vr.getArgIdx(0), THROWABLE_TYPE, throwableIndex);
actionArgs[1] = constArg(om.getClassNameParameter(), className.replace('/', '.'));
actionArgs[2] = constArg(om.getMethodParameter(), getName(om.isMethodFqn()));
actionArgs[3] = selfArg(om.getSelfParameter(), Type.getObjectType(className));
actionArgs[4] = new ArgumentProvider(asm, om.getDurationParameter()) {
@Override
public void doProvide() {
MethodTrackingExpander.DURATION.insert(mv);
}
};
return actionArgs;
}
然后作者给提了个修改建议,让加一个@Targetinstance,然后不对是否包含做区分,不过我一直没理解,他就自己改了,大体思路没变,就是在脚本中一定要有 @Targetinstance Throwable err这个参数,限制了必须有之后就不用在分开判断了,直接一起处理就好了,这样代码简洁了很多:
addExtraTypeInfo(om.getTargetInstanceParameter(), THROWABLE_TYPE);
vr = validateArguments(om, actionArgTypes, Type.getArgumentTypes(getDescriptor()));
ArgumentProvider[] actionArgs = new ArgumentProvider[5];
actionArgs[0] = localVarArg(om.getTargetInstanceParameter(), THROWABLE_TYPE, throwableIndex);
actionArgs[1] = constArg(om.getClassNameParameter(), className.replace('/', '.'));
actionArgs[2] = constArg(om.getMethodParameter(), getName(om.isMethodFqn()));
actionArgs[3] = selfArg(om.getSelfParameter(), Type.getObjectType(className));
actionArgs[4] = new ArgumentProvider(asm, om.getDurationParameter()) {
@Override
public void doProvide() {
MethodTrackingExpander.DURATION.insert(mv);
}
};
Label l = levelCheck(om, bcn.getClassName(true));
loadArguments(vr, actionArgTypes, isStatic(), actionArgs);
下面是脚本的写法:
@onmethod(clazz="/.*\.OnMethodTest/", method="args", location=@location(value=Kind.ERROR))
public static void args(@self Object self, @return long retVal, @duration long dur, String a, long b, String[] c, int[] d, @Targetinstance Throwable err) {
println("args");
} -----
以下是最开始的查问题的时候,为了快,临时修改代码的记录:
子公司线上的一个项目偶尔会报一个异常,问题出在一个Filter里,由于Filter是由web容器调用的,所以全都没有走自己的代码,也不容易加日志,由于搬的位置比较近,我就帮着看看,先是想用BTrace拦截到Filter的参数:
@OnMethod(clazz = "com.....filter.CipherFilter", method = "doFilter", location = @Location(Kind.RETURN))
public static void traceExecute(@ProbeClassName String name, @ProbeMethodName String method,
AnyType servletRequest, AnyType servletResponse, AnyType chain){
.......
}
@OnMethod(clazz = "com.....filter.CipherFilter", method = "doFilter", location = @Location(Kind.ERROR))
public static void traceErrorExecuteWithoutParas(@ProbeClassName String name,@ProbeMethodName String method){
......
}
@OnMethod(clazz = "com.....filter.CipherFilter", method = "doFilter", location = @Location(Kind.ERROR))
public static void traceErrorExecute(@ProbeClassName String name,@ProbeMethodName String method,
AnyType servletRequest, AnyType servletResponse, AnyType chain){
......
}
然而发现,不报错时,traceExecute方法可以拿到参数。报错的时候,只有traceErrorExecuteWithoutParas会执行,traceErrorExecute完全没有输出,这就比较尴尬了,这项目之前都没有接触过,这第三方框架也没用过,如果连什么参数造成的错误都不知道,那就没什么可以参照的信息了。于是我就想看看,为什么参数没有输出,就fork了btrace的源码。 在Instrumentor.java里找到了原因:
case ERROR:
// <editor-fold defaultstate="collapsed" desc="Error Instrumentor">
ErrorReturnInstrumentor eri = new ErrorReturnInstrumentor(cl, mv, mHelper, className, superName, access, name, desc) {
ValidationResult vr;
{
addExtraTypeInfo(om.getSelfParameter(), Type.getObjectType(className));
vr = validateArguments(om, actionArgTypes, new Type[]{THROWABLE_TYPE});
}
@Override
protected void onErrorReturn() {
......
ArgumentProvider[] actionArgs = new ArgumentProvider[5];
actionArgs[0] = localVarArg(vr.getArgIdx(0), THROWABLE_TYPE, throwableIndex);
.......
loadArguments(actionArgs);
源码中处理ERROR的情况时参数直接就忽略了,用了THROWABLE_TYPE(THROWABLE_TYPE = Throwable.class.getName()),或许BTrace提供了别的方法,或许我这种需求不多,总之,一时也没找到办法,就只好自己改了。Filter的doFilter方法没有返回值,我暂时就不处理返回值的情况,先解决问题,至于其他回头抽出空来再说了。 其实如果不考虑很多情况,只是要拿到参数很容易:
vr = validateArguments(om, actionArgTypes, new Type[]{THROWABLE_TYPE});
-->
vr = validateArguments(om, actionArgTypes, new Type[]{THROWABLE_TYPE});
ArgumentProvider[] actionArgs = new ArgumentProvider[5];
...
-->
if (vr.isAny()){
callWithoutArgs(throwableIndex);
} else {
callWithArgs();
}
private void callWithArgs(){
loadArguments(
vr, actionArgTypes, isStatic(),
constArg(om.getMethodParameter(), getName(om.isMethodFqn())),
constArg(om.getClassNameParameter(), className.replace("/", ".")),
// localVarArg(om.getReturnParameter(), probeRetType, retValIndex, boxReturnValue),
selfArg(om.getSelfParameter(), Type.getObjectType(className)),
new ArgumentProvider(asm, om.getDurationParameter()) {
@Override
public void doProvide() {
MethodTrackingExpander.DURATION.insert(mv);
}
}
);
}
private void callWithoutArgs(int throwableIndex){
ArgumentProvider[] actionArgs = new ArgumentProvider[5];
actionArgs[0] = localVarArg(vr.getArgIdx(0), THROWABLE_TYPE, throwableIndex);
actionArgs[1] = constArg(om.getClassNameParameter(), className.replace('/', '.'));
actionArgs[2] = constArg(om.getMethodParameter(), getName(om.isMethodFqn()));
actionArgs[3] = selfArg(om.getSelfParameter(), Type.getObjectType(className));
actionArgs[4] = new ArgumentProvider(asm, om.getDurationParameter()) {
@Override
public void doProvide() {
MethodTrackingExpander.DURATION.insert(mv);
}
};
Label l = levelCheck(om, bcn.getClassName(true));
loadArguments(actionArgs);
}
这样就可以拿到参数了:
ERROR class name ============com.....filter.CipherFilter
ERROR class method ============doFilter
ERROR time==1/3/18 2:35 PM
/.../.../....action
/.../.../....action
另外,从ServletRequest中拿到请求的url和参数之类的也稍有点啰嗦:
Object request = get(field("org.apache.catalina.connector.RequestFacade","request"), servletRequest);
Object coyoteRequest = get(field("org.apache.catalina.connector.Request","coyoteRequest"), request);
Object uri = get(field("org.apache.coyote.Request","uriMB"), coyoteRequest);
println(get(field("org.apache.tomcat.util.buf.MessageBytes","strValue"), uri));
Object path = get(field("org.apache.catalina.connector.Request","requestDispatcherPath"), request);
println(get(field("org.apache.tomcat.util.buf.MessageBytes","strValue"), path));
微信公众号: