Btrace
在github上对自己的介绍是:
BTrace is a safe, dynamic tracing tool for the Java platform.
Btrace
是一款利用了Java
动态织入技术来追踪已经部署在线上的应用信息状态的工具。
在线上因为应用已经被部署,一旦发生故障想排查应用里面的信息非常困难,这个时候停掉服务再去追加一些日志也不合适,而Btrace
则可以用脚本和应用的进程id获取用户自己希望得到的信息。
.______ .___________..______ ___ ______ _______ __
| _ \ | || _ \ / \ / || ____|| |
| |_) | `---| |----`| |_) | / ^ \ | ,----'| |__ | |
| _ < | | | / / /_\ \ | | | __| | |
| |_) | | | | |\ \----./ _____ \ | `----.| |____ |__|
|______/ |__| | _| `._____/__/ \__\ \______||_______|(__)
安装btrace
Btrace
的源码在github上:https://github.com/btraceio/btrace
截止这篇文章发布前,最新版本为1.3.9
.
下载之后,需要在环境变量中配置BTRACE_HOME
,将btrace
的安装目录赋值给BTRACE_HOME
:
BTRACE_HOME=export BTRACE_HOME="/usr/local/btrace/"
在命令行中输入btrace,能打印出以下内容,则说明安装成功:
➜ ~ btrace
Usage: btrace <options> <pid> <btrace source or .class file> <btrace arguments>
where possible options include:
--version Show the version
-v Run in verbose mode
-o <file> The path to store the probe output (will disable showing the output in console)
-u Run in unsafe mode
-d <path> Dump the instrumented classes to the specified path
-pd <path> The search path for the probe XML descriptors
-classpath <path> Specify where to find user class files and annotation processors
-cp <path> Specify where to find user class files and annotation processors
-I <path> Specify where to find include files
-p <port> Specify port to which the btrace agent listens for clients
-statsd <host[:port]> Specify the statsd server, if any
举个栗子
假设我们有这样的一个web入口:
package demo.spring.web.action;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author whthomas
* @date 2017/5/28
*/
@RestController
@RequestMapping("/")
public class MainController {
@RequestMapping("/{name}")
public String hello(@PathVariable("name") String name) {
return "hello " + name;
}
}
以上这段代码在线上运行之后,假设在不停服务的情况下来获取这个接口中的参数,就可以利用Btrace
来截获信息。
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class Sample{
@OnMethod(
clazz="demo.spring.web.action.MainController",
method="hello",
location= @Location(Kind.RETURN)
)
public static void func(@ProbeClassName String pcn,
@Duration long duration){
// get the param from the method
println("我抓到你了" + pcn + ",duration:" + duration);
}
}
运行脚本时,按照格式执行脚本:
btrace [Application's PID] Script.java
就像这样,当MainController
的hello
方法被调用到之后,脚本里面的内容就会被打印出来。
➜ btrace 29338 Sample.java
我抓到你了demo.spring.web.action.MainController,duration:12354
喜极而泣。
Btrace的具体配置
所有的Btrace
脚本都不能忘了把@BTrace
注解加到类的头部。然后透过以下这些配置,我们可以构建非常丰富的脚本来获取线上应用的信息。
@OnMethod
刚刚的Btrace
脚本中的func()
使用了一个OnMethod
注解。
在这个注解中可以定义这几个参数:
- clazz:目标对象所在的类的类型
- method:目前方法的方法名
- type:
- location:脚本执行的时间点
clazz不仅仅能指定特定目标,还可以使用正则表达式,拦截一系列的函数。
在location
参数中它可以通过@Location
配置脚本被执行的时间点,比如:
- @Location(Kind.RETURN): 在函数返回时执行
- @Location(Kind.ENTER): 在函数进入时执行
- @Location(value = Kind.LINE, line = 10): 函数被执行到第10行时触发
- @Location(value = Kind.CALL, clazz = "/./", method = "/./", where = Where.AFTER): 查询当前被拦截函数中其他函数的调用情况
在Kind
类中其实还有很多执行点,比如Kind.Error, Kind.Throw和 Kind.Catch异常抛出(Throw),异常被捕获(Catch),异常没被捕获被抛出函数之外(Error),主要用于对某些异常情况的跟踪。详见: https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/annotations/Kind.java
@OnTimer
这个注解,被用于执行一些定时任务。它只接受一个参数:
- interval:执行的时间间隔
比如要求某个方法两秒被执行一次:
@OnTimer(2000)
public static void print() {
// print the counter
println("component count = " + count);
}
被@OnTimer
注解的函数和被@OnMethod
注解的函数相结合,可以定期打印出某些函数的执行状态。
@OnExit
被@OnExit
注解的函数会在Btrace
脚本停止的时候触发,而非被监控的进程停止。
@OnExit
public static void onexit(int code) {
println("BTrace program exits!");
}
除了这种方法上的注解,在上面的例子中还能看到参数上可以使用的注解。
@ProbeClassName
获取被拦截的方法所属类的类名,因为有时候,@OnMethod
会利用正则表达式拦截到一组方法。
@ProbeMethodName
获取被拦截方法的方法名,原因同上。
@Duration
被@Duration
注解的参数,会被注入函数执行的时间。如果要使用@Duration
,被标记的参数只能是long
类型,而且函数上需要被标记成location=@Location(Kind.RETURN)
。
@Return
被@Return
注解的参数,会被注入函数执行的返回值。跟@Duration
一样,如果要使用@Return
注解函数也需要被标记成location=@Location(Kind.RETURN)
。
@Self
被@Self
注解的参数表示被拦截的方法实例。可以通过被@Self
注解的参数获取方法中的入参(param)
JDK原有类跟非JDK类获取参数的方式还有一些区别,非JDK类获取参数需要先用类加载器(classLoader)
将类加载出来。
// JDK类
Field fdFiled = field("java.io.FileInputStream", "fd");
// 非JDK类
Field customField = field(classForName("demo.spring.web.MyObject", contextClassLoader()), "customField");
@OnMethod(
clazz="demo.spring.web.action.MainController",
method="hello",
location= @Location(Kind.RETURN)
)
public static void func(@Self Object obj,
@ProbeClassName String pcn,
@Duration long duration){
// get the param from the method
println("拿到参数name:" + field("java.lang.String", "name"));
println("我抓到你了" + pcn + ", duration:" + duration);
}
@TargetInstance 和 @TargetMethodOrField
这两个注解需要放到一起才好理解,假设我们有如下两个类。
class A {
public void methodA(){
// statement
}
}
class B {
A a = new A();
public void methodB(){
// statement
a.methodA();
}
}
在Btrace
中,我们构建这样的脚本:
@BTrace
public class Sample{
@OnMethod(
clazz="B",
method="methodB",
location= @Location(
value = Kind.CALL,
clazz = "/.*/",
method = "/.*/",
where = Where.AFTER
)
)
public static void func(@Self self,
@TargetInstance targetInstance,
@TargetMethodOrField targetMethod,
@Duration long duration){
// get the param from the method
}
}
-
targetInstance
表示在methodB()
方法中其他引用类对象实例。 -
targetMethod
表示在methodB()
方法中其他对象实例执行的方法。
值得注意的是,这两个注解都只能在这个配置下使用:
location=@Location([Kind.CALL|Kind.FIELD_GET|Kind.FIELD_SET)
当然生产环境中不建议查询一个方法下所有的调用方法,这样JVM的大概会被拖崩溃掉。
网友评论