当前位置: 首页 > 工具软件 > BTrace > 使用案例 >

BTrace快速入门

公冶京
2023-12-01

概述

btrace是sun的一款java动态诊断工具。可以在运行中的java类中动态的注入trace代码,这样我们就可以在不停机的情况下获取方法的入参,返回值,等信息,从而达到监控调试的目的。但是使用btrace可能导致jvm崩溃,所以才生产环境使用btrace必须要在本地验证脚本的正确性。

配置

  1. 安装:去https://github.com/btraceio/btrace下载btrace,配置环境变量后即可使用。
  2. 命令:btrace < options > < pid > < btrace-script >

快速入门

在开始之前,我们先编写一个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:这个类是一个BTrace脚本。
  • @OnMethod:拦截的目标类和方法。
  • @Location:拦截的时机,类似于AOP,在方法的执行前,执行后等时机进行拦截。
  • @ProbeClassName:拦截的类名。
  • @ProbeMethodName :拦截的方法名。
  • @Return:方法的返回值。
  • AnyType:形参,可以使用具体的形参类型。也可以使用AnyType表示通配。
@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中。

  • @TLS:用@TLS注解修饰,表示这是一个ThreadLocal变量。
  • @Slef:@Slef表示的是this关键字,在下文中,表示的就是Throwable对象的实例。
@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, ]
 类似资料: