Btrace是什么?简单来说就是我们线上进行代码调试的一款利器,它可以动态的修改线上代码,从而达到既保留现场又能定位问题的效果,一句话总结就是:Btrace可以动态的向目标应用程序的字节码注入追踪代码
下面来讲解以下Btrace如何使用
Btrace有两种运行脚本的方式:
1)在JVisualVM中添加BTrace插件,添加classpath
2)使用命令行btrace <pid> <trace_script>
然后后面的例子当中两种方式我都会进行演示如何使用这个工具
后面的例子大概会以下面这三个方面进行Btrace的讲解:
1)拦截方法
1.1)普通方法 @OnMethod(clazz="",method="")
1.2)构造函数 @OnMethod(clazz="",method="")
1.3)拦截同名函数,用参数区分
2)拦截时机
2.1)Kind.ENTRY:入口,默认值
2.2)Kind.RETURN:返回
2.3)Kind.THROW:异常
2.4)Kind.Line:行
3)拦截this,参数,返回值
3.1)this:@Self
3.2)入参:可以使用AnyType,也可以用真实类型,同名的用真实的
3.3)返回:@Return
首先我们在Idea当中创建一个SpringBoot的项目,然后做一个简单的demo进行访问,细节不再多说,因为比较简单,下面是demo代码:
@RestController
@RequestMapping("/btrace")
public class Ch4Controller {
@RequestMapping("/method1")
public String arg1(@RequestParam("name")String name) {
return "hello,"+name;
}
}
然后就是我们的主角上场,首先得从gitHub上进行下载,这是下载地址:
https://github.com/btraceio/btrace
下载完之后需要简单的两步:
1)进行解压,解压到自己保存工程的路劲下面
2)配置环境变量,和配置jdk环境变量方式一摸一样,我们给变量名叫:BTRACE_HOME
然后加入刚才解压好的路径,最后修改一下path环境变量加入 :%BTRACE_HOME%\bin;即可,最后cmd下输入btrace,显示如下图即表示成功
1.1)拦截普通方法
import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintArgSimple {
@OnMethod(
clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
method="method1",
location=@Location(Kind.ENTRY)
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
BTraceUtils.printArray(args);
BTraceUtils.println(pcn+","+pmn);
BTraceUtils.println();
}
}
第一种运行脚本的方式:【在JVisualVM中添加BTrace插件】
找到我们的jdk安装路径,找到bin,双击【jvisualvm.exe】即可运行,进去之后我们需要安装一下插件,具体安装细节这里不再多说,因为这个不是本章的重点。
然后直接在我们运行的java进程上右击【Trace application】便可进入编写我们的运行脚本了,直接粘贴上面代码即可。最后点击start即可运行
第二种运行脚本的方式:cmd下直接切换到我们存放脚本的地方,因为脚本类似java,但是他又是独立存在的,所以需要将其详细的的路径粘贴过来,然后输入脚本运行命令:
btrace pid PrintArgSimple.java(脚本名称)
基本流程都是如此,下面测试过程一样,直接贴测试代码
先是运行的脚本代码:
1.2)拦截构造器
import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintConstructor {
@OnMethod(
clazz="com.imooc.monitor_tuning.chapter2.User",
method="<init>"
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
BTraceUtils.println(pcn+","+pmn);
BTraceUtils.printArray(args);
BTraceUtils.println();
}
}
1.3)拦截同名函数,用参数区分
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintSame {
@OnMethod(
clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
method="same"
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, String name) {
BTraceUtils.println(pcn+","+pmn + "," + name);
BTraceUtils.println();
}
}
2.1)Kind.ENTRY:入口,默认值
看第一个脚本即可
2.2)Kind.RETURN:返回
import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
import com.sun.btrace.annotations.Return;
@BTrace
public class PrintReturn {
@OnMethod(
clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
method="method1",
location=@Location(Kind.RETURN)
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Return AnyType result) {
BTraceUtils.println(pcn+","+pmn + "," + result);
BTraceUtils.println();
}
}
2.3)Kind.THROW:异常
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Self;
import com.sun.btrace.annotations.TLS;
@BTrace
public class PrintOnThrow {
// store current exception in a thread local
// variable (@TLS annotation). Note that we can't
// store it in a global variable!
@TLS
static Throwable currentException;
// introduce probe into every constructor of java.lang.Throwable
// class and store "this" in the thread local variable.
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow(@Self Throwable self) {//new Throwable()
currentException = self;
}
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow1(@Self Throwable self, String s) {//new Throwable(String msg)
currentException = self;
}
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow1(@Self Throwable self, String s, Throwable cause) {//new Throwable(String msg, Throwable cause)
currentException = self;
}
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow2(@Self Throwable self, Throwable cause) {//new Throwable(Throwable cause)
currentException = self;
}
// when any constructor of java.lang.Throwable returns
// print the currentException's stack trace.
@OnMethod(
clazz="java.lang.Throwable",
method="<init>",
location=@Location(Kind.RETURN)
)
public static void onthrowreturn() {
if (currentException != null) {
BTraceUtils.Threads.jstack(currentException);
BTraceUtils.println("=====================");
currentException = null;
}
}
}
2.4)Kind.Line:行
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintLine {
@OnMethod(
clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
method="exception",
location=@Location(value=Kind.LINE, line=35)
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) {
BTraceUtils.println(pcn+","+pmn + "," +line);
BTraceUtils.println();
}
}
3)拦截this,参数,返回值
上面测试脚本基本都已包含,现在若是需要测试一个自己定义的对象,不如一个user对象,如何获取他的属性等,也就是
获取复杂类型,btrace是通过反射,类名+属性名的方式获得的,下面我们看脚本的编写:
import java.lang.reflect.Field;
import com.imooc.monitor_tuning.chapter2.User;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintArgComplex {
@OnMethod(
clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
method="arg2",
location=@Location(Kind.ENTRY)
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, User user) {
//print all fields
BTraceUtils.printFields(user);
//print one field
Field filed2 = BTraceUtils.field("com.imooc.monitor_tuning.chapter2.User", "name");
BTraceUtils.println(BTraceUtils.get(filed2, user));
BTraceUtils.println(pcn+","+pmn);
BTraceUtils.println();
}
}
这里需要注意运行脚本的时候可能会识别不了user,那么我们的脚本命令就需要稍微改一下:
还是先切换到相应脚本的路径下:然后执行
btrace 12112(进程id) -cp <classpath>(我们编译之后的class路径) PrintArgComplex.java
最后是我们测试每一个脚本的代码:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.imooc.monitor_tuning.chapter2.User;
@RestController
@RequestMapping("/btrace")
public class Ch4Controller {
@RequestMapping("/method1")
public String arg1(@RequestParam("name")String name) {
return "hello,"+name;
}
@RequestMapping("/method2")
public User arg2(User user) {
return user;
}
@RequestMapping("/constructor")
public User constructor(User user) {
return user;
}
@RequestMapping("/same1")
public String same(@RequestParam("name")String name) {
return "hello,"+name;
}
@RequestMapping("/same2")
public String same(@RequestParam("name")String name,@RequestParam("id")int id) {
return "hello,"+name+","+id;
}
@RequestMapping("/exception")
public String exception() {
try {
System.out.println("start...");
System.out.println(1/0);
System.out.println("end...");
}catch(Exception e) {
//
}
return "success";
}
}
执行脚本之后的效果图省略...
感兴趣的小伙伴可以直接本地测试一下,熟悉之后就可以运用到我们的实际生产环境当中了,还有需要注意的有两点:
1)上面演示的代码默认只能本地运行,要想测试远程的感兴趣的小伙伴得单独改写代码
2)生产环境下可以使用,但是被修改的字节码不会被还原
好了,Btarce的使用简单说到这里,相深入了解的可以去github上进行更详细的额查看