Arthas 动态执行命令ognl
前面我们学习了监控相关的命令,包括minitor,watch,trace,stack等监控方法,这些已经很秀了,但是对于今天讲的ognl来说,还不够秀,ognl是非常实用的命令,可以动态的执行方法,今天我们专门花一篇文章来介绍ognl命令,它可以动态的修改加载的类文件,比如 日志级别,实现热更新
演示代码
本文采用SpringBoot 结构, 且 日志输出方式使用 logback,也就是Springboot默认的日志格式 之前采用了log4j2的方式,但是没有找到log的实现方式,没能成功的修改日志级别,所以今天演示,使用的是logback的默认日志形式
现在有一个测试类,TestController如下
package com.jzj.jvmtest.font;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
private static Logger log = LoggerFactory.getLogger(TestController.class);
private static Integer age = 10;
@GetMapping("/ping")
private String ping() {
log.info("sssss");
return "pong";
}
@GetMapping("/print")
private String getScore(Integer score) {
log.debug("debug ===========");
log.info("info ===========");
log.error("error ===========");
//输入分数超过 100, 就取100,否则返回真正的分数
if (score > 100) {
return "10";
}
return String.valueOf(score);
}
}
还有一个静态工具类,静态方法TestOgnl
package com.jzj.jvmtest.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestOgnl {
private static Logger logger = LoggerFactory.getLogger(TestOgnl.class);
private Integer aa = 11;
private static String bb = "22";
public static String print(String cc) {
logger.info("=============print cc:{}", cc);
return cc + "123";
}
public static void main(String[] args) {
System.out.println();
}
public static Logger getLogger() {
return logger;
}
public static void setLogger(Logger logger) {
TestOgnl.logger = logger;
}
public Integer getAa() {
return this.aa;
}
public void setAa(Integer aa) {
this.aa = aa;
}
public static String getBb() {
return bb;
}
public static void setBb(String bb) {
TestOgnl.bb = bb;
}
}
1. ognl 修改Logback 日志级别,热更新
下面我们来演示一下 ognl动态调整 logback的日志格式,上面我们已经看了TestController
执行 curl 127.0.0.1:8078/print,看下打印日志的级别
#执行结果 返回 99, 并且打印了日志信息
C:\Users\jzj>curl 127.0.0.1:8078/print?score=99
99
C:\Users\jzj>
# 打印日志 info级别
2023-05-09 22:46:10.540 INFO 17656 --- [nio-8078-exec-3] com.jzj.jvmtest.font.TestController : info ===========
2023-05-09 22:46:10.540 ERROR 17656 --- [nio-8078-exec-3] com.jzj.jvmtest.font.TestController : error ===========
打印日志格式,输出 info, error 级别的日志
现在我们使用 ognl来动态调整日志级别
- 先看下 TestController 类的 属性 log的 类信息
[arthas@17656]$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.jzj.jvmtest.font.TestController@log'
@Logger[
serialVersionUID=@Long[5454405123156820674],
FQCN=@String[ch.qos.logback.classic.Logger],
name=@String[com.jzj.jvmtest.font.TestController],
level=null,
effectiveLevelInt=@Integer[20000],
parent=@Logger[Logger[com.jzj.jvmtest.font]],
childrenList=null,
aai=null,
additive=@Boolean[true],
loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]],
]
[arthas@17656]$
- 看到TestConroller的log属性的 level级别 level=null 设置为null,且它使用的是logback的Logger信息
- 然后我们设置 log的日志输出级别 DEBUG
# 修改日志级别为 DEBUG,返回null
[arthas@17656]$
[arthas@17656]$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.jzj.jvmtest.font.TestController@log.setLevel(@ch.qos.logback.classic.Level@DEBUG)'
null
[arthas@17656]$
日志级别 到底修改成功了没, 现在的日志级别是什么? 下面我们来测试一下
- 执行 curl 127.0.0.1:8078/print,看下打印日志的级别
#执行结果 返回 99, 并且打印了日志信息
C:\Users\jzj>curl 127.0.0.1:8078/print?score=99
99
C:\Users\jzj>
# 打印日志 debug级别, 把debug,info,error debug以上的全都打印出来了,修改日志级别成功
2023-05-09 23:02:13.378 DEBUG 17656 --- [nio-8078-exec-7] com.jzj.jvmtest.font.TestController : debug ===========
2023-05-09 23:02:13.378 INFO 17656 --- [nio-8078-exec-7] com.jzj.jvmtest.font.TestController : info ===========
2023-05-09 23:02:13.378 ERROR 17656 --- [nio-8078-exec-7] com.jzj.jvmtest.font.TestController : error ===========
修改日志级别成功,现在是debug级别
2.ognl 调用静态方法简单参数
我们使用ognl可以调用类的静态方法, 输入参数,就相当于我们在jvm中,执行了该方法,会根据实际入参参数,输出结果
下面我们调用下 TestOgnl的静态方法 print ,输入参数 hello world
[arthas@18012]$ ognl '@com.jzj.jvmtest.util.TestOgnl@print("hello world")'
@String[hello world123]
我们执行的代码就是
- TestOgnl的静态方法 print
- 输入参数 hello world
- 输出参数 hello world123
- 代码逻辑就是在输入参数上拼接123,作为返回值,逻辑正确
3.ognl 调用表达式,返回List
我们可以使用表达式 给变量赋值比如我现在有两个变量 value1 和 value2 我们使用表达式 获取系统变量,来给 value1 和value2 赋值,并返回List信息
[arthas@17656]$ ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
@ArrayList[
@String[C:\Program Files\Java\jdk-17],
@String[Java(TM) SE Runtime Environment],
]
可以看到执行结果 List 中两个元素
- String 类型的 C:\Program Files\Java\jdk-17
- String 类型的 Java(TM) SE Runtime Environment
4.ognl 返回复杂对象 -x number 解析多少层
有时候我们返回的对象是一个List 列表, 列表中的还是一个对象,对象还会嵌套对象,我们该如何展示这些信息
ognl -x 1 表示的就是堆返回结果 执行1次深度的解析,下面我们看下这个例子 UserInfo 结构如下
package com.jzj.jvmtest.entity;
import cn.hutool.core.util.RandomUtil;
import com.google.common.collect.Lists;
import lombok.Data;
import java.util.List;
@Data
public class UserInfo {
private String name;
private List<Address> addressList;
public static UserInfo getInstance() {
UserInfo user = new UserInfo();
user.setName(RandomUtil.randomString(50));
Address a1 = new Address("hubei", "wuhan", "hanyang");
Address a2 = new Address("guangdong", "guangzhou", "shekou");
user.setAddressList(Lists.newArrayList(a1, a2));
return user;
}
}
内嵌的Address 地址信息如下
package com.jzj.jvmtest.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
private String province;
private String city;
private String hometown;
}
TestOgnl 类的 getUser方法如下
public static List<UserInfo> getUser() {
UserInfo u1 = UserInfo.getInstance();
u1.setName(name1);
UserInfo u2 = UserInfo.getInstance();
u2.setName(name2);
return Lists.newArrayList(u1, u2);
}
现在我们来用 ognl来执行下代码
ognl '@com.jzj.jvmtest.util.TestOgnl@getUser()' -x 1
我们可以看到 返回对象是List, -x 1/2/3 分别是返回对象的深度
# 返回List -x 1 解析1层深度
[arthas@10112]$ ognl '@com.jzj.jvmtest.util.TestOgnl@getUser()' -x 1
@ArrayList[
@UserInfo[UserInfo(name=t9ujdmxryt2idic0y6uqw5f7cb1zmwbeokfyptfvrqnkbh9258, addressList=[Address(province=hubei, city=wuhan, hometown=hanyang), Address(province=guangdong, city=guangzhou, hometown=shekou)])],
@UserInfo[UserInfo(name=mxlatnys6ydak4dfjf84iswnu6qxtacbkg7e3kudrsfkkqtud5, addressList=[Address(province=hubei, city=wuhan, hometown=hanyang), Address(province=guangdong, city=guangzhou, hometown=shekou)])],
]
[arthas@10112]$
# 返回List -x 2 解析2层深度
[arthas@10112]$ ognl '@com.jzj.jvmtest.util.TestOgnl@getUser()' -x 2
@ArrayList[
@UserInfo[
name=@String[nsz3xmh1br28auam546wkbe2cc7i2b58mqsjj9lnjsv7f5pfzi],
addressList=@ArrayList[isEmpty=false;size=2],
],
@UserInfo[
name=@String[na8eilayunkk49fpb9htgyvyvzljtx0ujcqmzm26y1tfevo9ph],
addressList=@ArrayList[isEmpty=false;size=2],
],
]
# 返回List -x 3 解析3层深度
[arthas@10112]$
[arthas@10112]$ ognl '@com.jzj.jvmtest.util.TestOgnl@getUser()' -x 3
@ArrayList[
@UserInfo[
name=@String[hi5pzu5wq7ucf2y3obmamzpijj2979a770j50gtckccgc21qy5],
addressList=@ArrayList[
@Address[Address(province=hubei, city=wuhan, hometown=hanyang)],
@Address[Address(province=guangdong, city=guangzhou, hometown=shekou)],
],
],
@UserInfo[
name=@String[reku5stt3jkc9gjgwrf4ja40weji9hvwyqqmde1lrf7ziwfyue],
addressList=@ArrayList[
@Address[Address(province=hubei, city=wuhan, hometown=hanyang)],
@Address[Address(province=guangdong, city=guangzhou, hometown=shekou)],
],
],
]
[arthas@10112]$
本文专门用一篇文章 介绍了ognl,其实ognl还有很多用法,比如针对输入类型参数是复杂类型如何处理,连续调用的方法A的出参是B方法的入参如何处理,我们要在实际项目中多使用它,才能记住它的语法,另外针对ognl动态执行的能力,专门用了日志logger日志级别的修改 来说明ognl的强大功能