Hello 大家好, 我是阿粉,不知道你有没有遇到过这种场景,线上服务跑了一段时间过后偶尔会出现问题,光靠代码和数据分析找不到原因,而且这种情况也不是很常见所以对应的代码也没有加日志输出,如果说重新加上日志进行发布的话,就会破坏现场只能再等一段时间了,或者有的时候想看下接口的参数,从而判断接口参数有没有问题。
这个时候就在想有没有一个好的方法,可以不用重新修改源代码也不用发布升级就可以增加一些日志看到运行状态和入口参数呢?答案当然是肯定的,下面我们就来看一下神器 BTrace
!
我们模拟一个场景,这个场景就是线上有个服务目前出现问题了,在某些请求触发的时候就会报错,我们现在就想看看报错的时候方法接口的入参的详细信息是什么。
先写一个 demo
代码,内部循环调用一个方法,具体如下,这段代码通过一个循环来模拟一直调用某个方法,这个方法print
的入参是一个 person
包装类,方法的内部处理逻辑这里就忽略了,并且每 3 秒执行一次。现在我们的需求很简单,就是想知道每次运行的时候参数 person
的每个属性值都是什么,话句话说也就是 age
和 name
的值是多少,当然我们也不能修改源代码增加打印和重新发布。
package com.javajeek.demo;
public class BtraceTest {
public void print(Person person) {
System.out.println("--------处理Person ing------");
try {
//...其他逻辑
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void test(Person person) {
while (true) {
this.print(person);
}
}
public static void main(String[] args) {
BtraceTest btraceTest = new BtraceTest();
Person person = new Person(18, "ziyou");
btraceTest.test(person);
}
static class Person {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
}
}
运行结果没什么好说的,就是每隔 3 秒输出一句话,如下。
乍一看大家可能都有点懵,觉得很难实现,这玩意又不是在本地 debug
,还能打断点不成。这个时候就需要上我们的神器了,虽然不是本地打断点调试,但是输出一下参数的属性值以及一些 JVM
的状态还是可以实现的。
在提供解决方案之前,我们先看下什么是 BTrace
,BTrace
是sun
公司推出的一款 Java
动态、安全追踪(监控)工具,可以在不用重启JVM
的情况下监控系统运行情况,方便的获取程序运行时的数据信息,如方法参数、返回值、全局变量和堆栈信息。简单来说就是 BTrace
是一个工具,可以帮助我们实时获取运行时的一些数据情况。
首先我们先下载压缩包 wget https://github.com/btraceio/btrace/releases/download/v2.2.0/btrace-v2.2.0-bin.tar.gz
下载最新的版本压缩包,下载完成过后解压一下,这里阿粉是在 macOS
上面演示的,如果是 Windows
的小伙伴可以直接到 GitHub
上面下载然后手动解压也是一样的。GitHub
地址 https://github.com/btraceio/btrace/releases/tag/v2.2.0
。
wget https://github.com/btraceio/btrace/releases/download/v2.2.0/btrace-v2.2.0-bin.tar.gz
tar xzvf btrace-v2.2.0-bin.tar.gz
解压完了过后,我们可以看到目录结构如下,其中 samples
目录里面有很示例代码感兴趣的小伙伴可以去看看:
在使用 BTrace
的时候我们需要编写一个Java
脚本,在这个脚本里面表达我们要处理的事情,如果想知道 JVM
的运行情况怎么样,某个类的某个方法的返回值是什么,方法入参是什么等等任何想知道的信息。这里贴一下我们案例里面的解决方法的脚本代码,具体如下:
import org.openjdk.btrace.core.annotations.*;
import org.openjdk.btrace.core.types.*;
import static org.openjdk.btrace.core.BTraceUtils.*;
@BTrace
public class AllMethodsTest {
@OnMethod(
clazz = "com.lazada.marketing.search.ads.diamond.BtraceTest",
method = "print",
location = @Location(Kind.ENTRY)
)
public static void m(Object request) {
printFields(request);
}
}
可以看到核心的代码就那么几行,跟我们写 Java
代码是非常相似的,也是导入包,写一个类,再定义方法,同时加上了一些自己的注解。通过注解的内容我们可以要处理哪个类以及哪个类的方法。关于 BTrace
有哪些注解,以及每个注解是怎么用的具体可以参考这篇文章 https://ningyu1.github.io/site/post/39-btrace/ 很详细,阿粉觉得很不错,提到有类的注解,方法的注解,以及参数相关的注解。
简单说下上面的这段代码中我们使用到的几个注解,@BTrace
,OnMethod
,@Location
代表的含义:
@BTrace
:表示这个类是一个 BTrace
程序,BTrace
编译器会强制查找该注解,BTrace
代理也会检查这个是否有该注解。如果没有,则提示错误,并且不会执行。
OnMethod
:该注解可用来指定目标类,目标方法,以及目标方法里的“位置”。其中 clazz
和 method
分别表示要执行的类和方法,可以用正则来表示。
@Location
:表示方法中特定的位置。取值可以参考上面的文章中的表格。
有了上面的运行程序以及 BTrace
的示例代码我们就可以来满足我们的要求了,首先我们的 demo
代码是在运行中的,我们通过jps
命令查询到对应的 pid
,操作如下,对应的 pid
是84287
。
查到了 pid
过后我们就可以执行 BTrace
程序了,通过命令./bin/btrace 84287 samples/AllMethodsTest.java
,执行的结果如下图所示。可以看到入参的详细信息都被输出出来了,至此我们的要求已经达到了。小伙伴们是不是很开心,很激动,很兴奋呢?这个使用只是 BTrace
强大功能的冰山一角,还有很多更好用的示例都在 samples
目录下,感兴趣的小伙伴可以去研究研究。
上面的一个示例,阿粉只是很浅显的给大家介绍了一个很简单但是很常用的使用方式,更多强大的使用大家可以自己研究一下,很多小伙伴看到这个功能以为肯定会有一个疑惑,那就是 BTrace
是怎么实现这个功能的,具体的实现阿粉给大家分享一篇美团的技术博客文章,里面介绍的比较详细也比较好理解,大家可以去看一下https://tech.meituan.com/2019/02/28/java-dynamic-trace.html,BTrace
虽然好用,但是使用起来相对还是比较麻烦的,更好用的一个工具相信大家都知道那就是阿里开源的 Arthas
,大家可以去学习一下。