btrace可以在线上运行的程序,不重启的情况下动态改变类
下载btrace, 网上说需要什么gradle,其实不需要
btrace使用有很多限制,比如不允许创建对象,不允许抛出异常等等,所以一般只能使用BTraceUtils工具类的方法
先构建一个普通的web项目A,里面有一个接口,启动这个项目
@RestController
@RequestMapping("/user")
public class UserController {
@ResponseBody
@RequestMapping("/get")
public UserResp getUserById(@RequestBody UserReq userReq) throws InterruptedException {
System.out.println("enter getUserById");
UserResp resp=new UserResp(userReq.getId(),"aaa");
return resp;
}
}
这个接口没有打印log参数之类的,或者返回值是什么也不知道,如果现在出现问题,不好定位,生产环境一般不允许远程调试,
我们使用btrace添加一些log观察问题
新建另外一个项目B
导入btrace jar包
<dependency>
<groupId>com.sun.tools.btrace</groupId>
<artifactId>btrace-agent</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.sun.tools.btrace</groupId>
<artifactId>btrace-boot</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.sun.tools.btrace</groupId>
<artifactId>btrace-client</artifactId>
<version>1.2.3</version>
</dependency>
新建一个类,用于打印出A项目的参数
package com;
import com.sun.btrace.AnyType;
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
/**
* 设置了unsafe=true,可以使用一些除了BTraceUtils里面的其他方法,比如String.valueOf() ,当然同时需要在启动的时候添加-u参数
*/
@BTrace(unsafe = true)
public class TestOne {
/**
* 默认@OnMethod方法的 location的value为 Kind.ENTRY,就是说作用在方法进入的时候
* @param self @Self注解的对象,可以获得this引用,如果是static方法,selef为null
* @param params 因为getUserById默认只有一个对象参数,所以params跟在self后面即可,
* 如果参数是多个,一直往下写即可,会自动识别注入,有多个就写成param1,param2,param3的形式
*/
@OnMethod(clazz = "com.example.demo.controller.UserController",method = "getUserById")
public static void pre(@Self Object self,AnyType params){
println("self:"+self);
printFields(params);
println("==============");
}
/**
*
* @param obj @Return注解的参数用于接收方法的返回值
* @param params 方法参数
* @param time @Duration用在Kind.Return上,用在Kind.ENTRY会报错
*/
@OnMethod(clazz = "com.example.demo.controller.UserController",method = "getUserById",location = @Location(value = Kind.RETURN))
public static void getReturn(@Return Object obj, AnyType params,@Duration long time){
printFields(obj);
printFields(params);
println("time:"+time);
println("=============="); //最后打印一下,否则上面的time将因为缓存显示不出来,
}
}
找到A项目的pid, 使用jps即可
并不需要打包整个B项目,直接copy TestOne.java 到btrace的bin目录
可以先使用 btracec TestOne.java编译一下看,保证java文件可以正常编译(这里不保证最后一定能成功给A项目添加注释,只是保证此java文件正常)
最后使用,我是在windows操作系统,所以使用.bat脚本
btrace.bat -o brace.log 5196 TestOne.java
brace动态添加的代码,比如上面的print,不会输出到A项目的log文件,如果不指定,默认是在调用btrace命令的控制台,
这里指定 -o btrace.log, 日志会输出到A项目的根目录, 5196是A项目的PID,TestOne.java是注入的脚本
btrace的 OnMethod可以拦截不同类型的方法,
@OnMethod( clazz="+java.lang.ClassLoader", method="defineClass",location=@Location(Kind.RETURN) )
拦截继承le ClassLoader的子类的defineClass方法, 也可以写成 +xxx.xxx.interfaceClass, 拦截某些接口的实现类
@OnMethod( clazz="/java\\.io\\..*Input.*/",method="/read.*/" ) 正则表达式写法
@OnMethod(clazz="@javax.jws.WebService", method="@javax.jws.WebMethod" ) 拦截打了@WebService直接的类的@WebMethod
遇到的坑:有时候报的错误根本不会在运行btrace脚本的时候正常显示,调试期间,第一可以添加-v参数,这个参数意义也不太大,
如果是脚本的问题,还是不会显示,比如碰到一个问题,@Duration不能作用在Kind.Entry上,这种错误,运行btrace看不出来,我是先到java bin/的jvisualvm中装了btrace插件,在里面贴上脚本,才显示的错误,但是如果直接使用jvisualvm执行,也会报错,所以jvisualvm我只用来检查脚本正确性
btrace还有一些其他注解,比如@OnTime @OnError,可以定时执行某个方法,或者在某个方法出错回调,使用起来不难,
此处有一个问题,print中想打印请求参数对象的某个属性报错,虽然btrace提供了 相应方法, 比如使用getLong(field,obj)报错了