想象一下这样的场景,如果生产上运行的应用没有输出关键日志,而我们又想在不重启应用的情况下,输出我们想要的日志,比如打印某个方法的执行耗时,那么可以怎么处理呢?
也许你会说,打印耗时还不简单,通过arthas的trace命令就可以看到,这个确实可以,例如下面的命令。
trace com.wangji92.arthas.plugin.demo.controller.CommonController traceE -n 5 --skipJDKMethod false
但是如果我们想改变方法的返回值,那么用trace命令就做不到了,另外,我们想把方法的执行耗时输出到日志一段时间,方便分析,trace命令同样做不到。那么arthas怎么做热更新的呢?需要用到以下几个步骤。
【步骤一】用jad反编译,并输出源码到本地
jad --source-only com.wangji92.arthas.plugin.demo.service.impl.ArthasTestServiceImpl > /root/arthas/ArthasTestServiceImpl.java
然后编辑 /root/arthas/ArthasTestServiceImpl.java,修改返回的返回值,增加记录执行耗时的日志输出。
public String doTraceE(String name) {
try {
long start = System.currentTimeMillis();
/*19*/ Thread.sleep(1000L);
/*21*/ if (StringUtils.isEmpty((Object)name)) {
name = System.currentTimeMillis() + "";
}
long end = System.currentTimeMillis();
log.info("doTraceE方法执行耗时:{}", end - start);
return Thread.currentThread().getName() + " - HELLO ARTHAS";
}
catch (InterruptedException e) {
/*27*/ log.error("InterruptedException", (Throwable)e);
/*29*/ return "";
}
}
【步骤二】用mc命令进行编译
在用mc编译之前,需要用sc命令查看com.wangji92.arthas.plugin.demo.service.impl.ArthasTestServiceImpl这个类用的是哪个类加载器,因为编译的时候需要用到,具体命令如下:
sc com.wangji92.arthas.plugin.demo.service.impl.ArthasTestServiceImpl
上面截图中的classLoaderHash对应的值31cefde0就是类加载器对应的hash码。
通过mc命令进行编译,命令如下:
mc -c 31cefde0 /root/arthas/ArthasTestServiceImpl.java -d /root/arthas/
没有任何报错的话说明编译成功。
这里说明一下为什么要指定类加载器31cefde0,因为ArthasTestServiceImpl这个类中用到了一些自定义的类,比如实现了ArthasTestService接口,如果用默认的类加载器的话,则会找不到该接口类的错误。
【步骤三】用redefine命令进行热加载
redefine -c 31cefde0 /root/arthas/com/wangji92/arthas/plugin/demo/service/impl/ArthasTestServiceImpl.class
没有任何报错的话说明编译成功。
【步骤四】测试效果
需要注意的是:
① 不允许新增加/删除 field/method
② 正在跑的函数,没有退出不能生效,如果调用ArthasTestServiceImpl的入口:com.wangji92.arthas.plugin.demo.controller.CommonController#traceE,如果你在traceE方法内加入监控执行耗时的日志输出的话,则不会生效。