btrace是sun的一款java动态诊断工具。可以在运行中的java类中动态的注入trace代码,这样我们就可以在不停机的情况下获取方法的入参,返回值,等信息,从而达到监控调试的目的。但是使用btrace可能导致jvm崩溃,所以才生产环境使用btrace必须要在本地验证脚本的正确性。
在开始之前,我们先编写一个Controller和一个User类,方便后面进行脚本的测试。
@Data
@AllArgsConstructor
public class User {
private String username;
private String password;
}
@RestController
public class BTraceController {
@RequestMapping("/args")
public String args(String arg1, String arg2) {
return arg1 + " " + arg2;
}
@RequestMapping("/constructor")
public User constructor() {
return new User("admin", "123456");
}
@RequestMapping("/referenceArgs")
public String referenceArgs(User user) {
return "success";
}
@RequestMapping("/throwable")
public void throwable() {
try {
int a = 1 / 0;
} catch (Throwable throwable) {
//nothing to do.
}
}
@RequestMapping("/line")
public String line() {
String ret = "Hello World"; //line = 37
return ret;
}
}
1、 拦截方法参数及返回值
如下文的脚本所示,拦截BTraceController.args的参数和返回值。
@BTrace
public class ArgsTracker {
@OnMethod(clazz = "com.zs.study.btrace.controller.BTraceController",
method = "args", location = @Location(Kind.ENTRY))
public static void traceArgs(@ProbeClassName String pcn, @ProbeMethodName String pmn,
AnyType[] anyTypes, @Return String result) {
BTraceUtils.print(pcn + "." + pmn);
BTraceUtils.printArray(anyTypes);
BTraceUtils.print(result);
}
}
访问http://127.0.0.1:8080/args?arg1=hello&arg2=world,可以看到btrace已经将入参和返回值输出了。
[root@izbp1chtb8a3vd2mzvuawlz btrace]# btrace 1799 ArgsTracker.java
com.zs.study.btrace.controller.BTraceController.args
[hello, world, ]
hello world
2、拦截构造方法
如下文的脚本所示,拦截User类的构造方法
@BTrace
public class ConstructorTracer {
@OnMethod(clazz = "com.zs.study.btrace.model.User", method = "<init>")
public static void traceConstructor(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] anyTypes) {
BTraceUtils.println(pcn + "." + pmn);
BTraceUtils.printArray(anyTypes);
BTraceUtils.println();
}
}
访问http://127.0.0.1:8080/constructor,可以看到btrace已经将User的构造方法输出了。
[root@izbp1chtb8a3vd2mzvuawlz btrace]# btrace 2364 ConstructorTracer.java
com.zs.study.btrace.model.User.<init>
[admin, 123456, ]
3、拦截异常
拦截异常与拦截构造方法类似,只需要拦截Throwable的构造方法即可。
下面的代码表示了,当拦截到Throwable的构造方法时,将其对象保存到一个ThreadLocal中。
@BTrace
public class ThrowableTracer {
@TLS
static Throwable currentThrowable;
@OnMethod(clazz = "java.lang.Throwable", method = "<init>")
public static void traceThrowable(@Self Throwable self) {
//new Throwable()
currentThrowable = self;
}
@OnMethod(clazz = "java.lang.Throwable", method = "<init>")
public static void traceThrowable(@Self Throwable self, String message) {
//new Throwable(String message)
currentThrowable = self;
}
@OnMethod(clazz = "java.lang.Throwable", method = "<init>")
public static void traceThrowable(@Self Throwable self, Throwable cause) {
//new Throwable(Throwable cause)
currentThrowable = self;
}
@OnMethod(clazz = "java.lang.Throwable", method = "<init>")
public static void traceThrowable(@Self Throwable self, String message, Throwable cause) {
//new Throwable(String message, Throwable cause)
currentThrowable = self;
}
@OnMethod(clazz = "java.lang.Throwable", method = "<init>", location = @Location(Kind.RETURN))
public static void print() {
if (currentThrowable != null) {
BTraceUtils.Threads.jstack(currentThrowable);
currentThrowable = null;
}
BTraceUtils.println();
}
}
访问http://127.0.0.1:8080/throwable,可以看到btrace已经异常堆栈信息输出了。
[root@izbp1chtb8a3vd2mzvuawlz btrace]# btrace 2364 ThrowableTracer.java
java.lang.ArithmeticException: / by zero
com.zs.study.btrace.controller.BTraceController.throwable(BTraceController.java:29)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
4、拦截行号
如下文的脚本所示,拦截的是BTraceController中的第33行代码,通过这个脚本,我们可以知道某一行代码是否被执行了。当line = -1时,会拦截所有行。
@BTrace
public class LineTracer {
@OnMethod(clazz = "com.zs.study.btrace.controller.BTraceController",
location = @Location(value = Kind.LINE, line = 37))
public static void traceLine(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) {
BTraceUtils.println(pcn + "." + pmn + "," + line);
BTraceUtils.println();
}
}
在BTraceController这个类中,第37行代码是String ret = “Hello World”。 访问http://127.0.0.1:8080/line,可以看到第37行代码执行了。
[root@izbp1chtb8a3vd2mzvuawlz btrace]# btrace 2636 LineTracer.java
com.zs.study.btrace.controller.BTraceController.line,37
5、拦截引用类型
拦截非rt.jar包下的引用类型需要引用classpath,如拦截BTraceController.referenceArgs方法,执行脚本时需要指定User类的classpath。
btrace脚本
@BTrace
public class ReferenceArgsTracer {
@OnMethod(clazz = "com.zs.study.btrace.controller.BTraceController",
method = "referenceArgs", location = @Location(Kind.ENTRY))
public static void traceReferenceArgs(@ProbeClassName String pcn, @ProbeMethodName String pmn,User user) {
BTraceUtils.println(pcn + "." + pmn);
BTraceUtils.printFields(user);
BTraceUtils.println();
}
}
访问http://127.0.0.1:8080/referenceArgs?username=admin&password=123456,可以看到btrace已经将User的字段值输出了。
[root@izbp1chtb8a3vd2mzvuawlz btrace]# btrace -cp "/usr/workspace/btrace/target/classes" 2832 ReferenceArgsTracer.java
com.zs.study.btrace.controller.BTraceController.referenceArgs
{username=admin, password=123456http://127.0.0.1:8080/referenceArgs?username=admin,123456, }
6、正则表达式拦截
btrace是可以支持正则表达式的,如下文所示,拦截controller中的所有方法。
@BTrace
public class RegexTracer {
@OnMethod(clazz = "/com.zs.study.btrace.controller.*/",
method = "/.*/", location = @Location(Kind.RETURN))
public static void traceArgs(@ProbeClassName String pcn, @ProbeMethodName String pmn,
AnyType[] anyTypes, @Return String result) {
BTraceUtils.println(pcn + "." + pmn);
BTraceUtils.printArray(anyTypes);
BTraceUtils.println(result);
BTraceUtils.println();
}
}
这里我们同样测试BTraceController.args方法。访问http://127.0.0.1:8080/args?arg1=hello&arg2=world,可以看到btrace已经将入参和返回值输出了。
[root@izbp1chtb8a3vd2mzvuawlz btrace]# btrace 3231 RegexTracer.java
com.zs.study.btrace.controller.BTraceController.args
[hello, world, ]