使用BTrace来定位方法中Catch住的异常

一.问题

今天在自己应用中引入了一个新的中间件,发布到线上后,发现一个接口一直获取不到数据,此时找不到日志,因为代码中的日志输出不按常理出牌,误引入了某一中间件自定义的Logger导致日志很难找到,此时要是在测试环境,debug一下就搞定,但是线上不能这么搞,如何去查看一下本次调用接口返回的数据到底是什么呢?

二.被调用到的代码

被调用到的代码

上面代码的getData方法被调用了,但是返回的DataResult中没有数据。

三.使用BTrace监控一下getData方法的返回值以及输入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
import com.sun.btrace.AnyType;

@BTrace
public class TraceMethodArgsAndReturn{

        @OnMethod(
        clazz="xxx.UdcDataSource",
        method="getData",
        location=@Location(Kind.RETURN)
        )
        public static void traceExecute(@Self Object dataSource, AnyType context, @Return Object result){
                println("Call UdcDataSource.getData");
                // 打印当前被BTrace拦截到的实例
                println(strcat("dataSource is:",str(dataSource)));
                // 打印getData方法的参数 xx.DefaultParamContext是context的类型
                println(strcat("fields are:",str(get(field("xx.DefaultParamContext","fields"),context))));
                println(strcat("context are:",str(get(field("xx.DefaultParamContext","context"),context))));
                // 打印getData的返回值 xx.DataResult是result的实际类型
                println(strcat("result is:",str(get(field("xx.DataResult","result"),result))));
        }
}

使用上面这段BTrace脚本,就可以跟踪getData方法的调用了,跟踪的结果是getData方法返回DataResult实例,但是DataResult实例中的Map实例大小为0,也就是说上面代码中ret大小为0。

仔细看看上面调用到的代码,如果返回结果是这样的话,只能说明getData方法中抛异常了。看看catch语句中的日志,竟然没有把exception也输出到log里面,那如何看这里面的异常呢?难道要再发布一次。。。。要是BTrace也能把这个异常跟踪到,那就好办了。

在BTrace脚本中不能出现自己定义的类型,要是自己定义的类类型,要么使用AnyType,要么使用Object在BTrace脚本中来指定。

三.使用BTrace来跟踪方法中catch住的异常

不清楚怎么搞,只能google一下,看了一些BTrace的文档,发现BTrace竟然能跟踪到方法中catch住的异常。于是写了下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;

@BTrace
public class TraceException {
   // 异常捕捉
   @OnMethod(clazz = "xxx.UdcDataSource", method = "getData", location = @Location(Kind.CATCH))
   public static void traceExecute(@ProbeClassName String pcn, @ProbeMethodName String pmn, Exception e) {
      print(pcn);
      print(".");
      print(pmn);
      print("(");
      println(") cacth Exception");
      println(e);
   }
}

使用Kind.CATCH就能捕获方法中catch住的异常了,至此异常发现,问题搞定。

五.BTrace使用

关于BTrace的使用很简单,可以参考http://jm-blog.aliapp.com/?p=509

六.需要注意的问题

我们知道BTrace是动态修改类的字节码的,动态增加一些字节码,那么这些动态增加的字节码在BTrace断开后,会被删除吗?也就是说被修改了的类能恢复到修改之前吗?答案是不能,在BTrace断开后,BTraceRuntime就会记录自己已经禁用了,把disabled设置为true,于是虽然被改写的字节码还保留着对它的调用,但BTraceRuntime看到disabled为true就直接返回了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * Enter method is called by every probed method just
 * before the probe actions start.
 */

public static boolean enter(BTraceRuntime current) {
	if (current.disabled) return false;
	return map.enter(current);
//        // check we have entered already or disabled
//        if (current.disabled || (tls.get() != null)) {
//            return false;
//        } else {
//            tls.set(current);
//            return true;
//        }
}

// 这个方法在BTrace退出的时候会被调用
private synchronized void exitImpl(int exitCode) {
	if (exitHandler != null) {

	try {
		exitHandler.invoke(null, exitCode);
	    } catch (Throwable ignored) {
 	    }
         }

	// 退出的时候把disabled设置成true
	disabled = true;
	// ...后面的代码省略

}

BTraceRuntime源码