Enhance `returnedObj` expression to support Array, List and Map (#84)

* Enhance `returnedObj` expression to support Array, List and Map for apm-customize-enhance-plugin and `@Tag`

* update doc & add UnitTest for @Tag annotation

* update e2e test accordingly
This commit is contained in:
Daniel Qian 2021-12-15 15:00:22 +08:00 committed by GitHub
parent bc90371daa
commit ff5de395a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 243 additions and 80 deletions

View File

@ -49,31 +49,7 @@ public class CustomizeExpression {
public static Map<String, Object> evaluationReturnContext(Object ret) {
Map<String, Object> context = new HashMap<>();
context.put("returnedObj", ret.toString());
if (ret instanceof List) {
List retList = (List) ret;
int retLength = retList.size();
for (int i = 0; i < retLength; i++) {
context.put(String.valueOf(i), retList.get(i));
}
} else if (ret.getClass().isArray()) {
int length = Array.getLength(ret);
for (int i = 0; i < length; i++) {
context.put(String.valueOf(i), Array.get(ret, i));
}
} else if (ret instanceof Map) {
context.putAll((Map) ret);
} else {
Field[] fields = ret.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
context.put(field.getName(), field.get(ret));
} catch (Exception e) {
LOGGER.debug("evaluationReturnContext error, ret is {}, exception is {}", ret, e.getMessage());
}
}
}
context.put("returnedObj", ret);
return context;
}
@ -88,20 +64,6 @@ public class CustomizeExpression {
return "null";
}
public static String parseReturnExpression(String expression, Map<String, Object> context) {
try {
String[] es = expression.split("\\.");
if (es.length == 1) {
return String.valueOf(context.get(es[0]));
}
Object o = context.get(es[1]);
return o == null ? "null" : String.valueOf(parse(es, o, 1));
} catch (Exception e) {
LOGGER.debug("parse expression error, expression is {}, exception is {}", expression, e.getMessage());
}
return "null";
}
private static Object parse(String[] expressions, Object o, int i) {
int next = i + 1;
if (next == expressions.length) {

View File

@ -48,6 +48,38 @@ public class CustomizeExpressionTest {
Assert.assertTrue("ext_v_2".equals(CustomizeExpression.parseExpression("arg[5].user.ext.['ext_k_2']", context)));
}
@Test
public void testReturnExpression() {
Object[] allArguments = init();
Map<String, Object> evalCtx1 = CustomizeExpression.evaluationReturnContext(allArguments);
Assert.assertTrue("String_test".equals(CustomizeExpression.parseExpression("returnedObj.[0]", evalCtx1)));
Assert.assertTrue("1024".equals(CustomizeExpression.parseExpression("returnedObj.[1]", evalCtx1)));
Assert.assertTrue("v2_1".equals(CustomizeExpression.parseExpression("returnedObj.[2].['k2_1']", evalCtx1)));
Assert.assertTrue("test1".equals(CustomizeExpression.parseExpression("returnedObj.[3].[1]", evalCtx1)));
Assert.assertTrue("null".equals(CustomizeExpression.parseExpression("returnedObj.[3].[100]", evalCtx1)));
Assert.assertTrue("100".equals(CustomizeExpression.parseExpression("returnedObj.[4].id", evalCtx1)));
Assert.assertTrue("sw".equals(CustomizeExpression.parseExpression("returnedObj.[4].getName()", evalCtx1)));
Assert.assertTrue("ext_v_1".equals(CustomizeExpression.parseExpression("returnedObj.[4].ext.['ext_k_1']", evalCtx1)));
Assert.assertTrue("uuid".equals(CustomizeExpression.parseExpression("returnedObj.[5].uuid", evalCtx1)));
Assert.assertTrue("c".equals(CustomizeExpression.parseExpression("returnedObj.[5].orderIds.[0]", evalCtx1)));
Assert.assertTrue("2".equals(CustomizeExpression.parseExpression("returnedObj.[5].ids.[2]", evalCtx1)));
Assert.assertTrue("3".equals(CustomizeExpression.parseExpression("returnedObj.[5].ids.[1]", evalCtx1)));
Assert.assertTrue("open_id".equals(CustomizeExpression.parseExpression("returnedObj.[5].openId", evalCtx1)));
Assert.assertTrue("ext_v_2".equals(CustomizeExpression.parseExpression("returnedObj.[5].user.ext.['ext_k_2']", evalCtx1)));
Map<String, Object> evalCtx2 = CustomizeExpression.evaluationReturnContext("Simple text");
Assert.assertTrue("Simple text".equals(CustomizeExpression.parseExpression("returnedObj", evalCtx2)));
Map<String, Object> evalCtx3 = CustomizeExpression.evaluationReturnContext(newOrder());
Assert.assertTrue("uuid".equals(CustomizeExpression.parseExpression("returnedObj.uuid", evalCtx3)));
Assert.assertTrue("c".equals(CustomizeExpression.parseExpression("returnedObj.orderIds.[0]", evalCtx3)));
Assert.assertTrue("2".equals(CustomizeExpression.parseExpression("returnedObj.ids.[2]", evalCtx3)));
Assert.assertTrue("3".equals(CustomizeExpression.parseExpression("returnedObj.ids.[1]", evalCtx3)));
Assert.assertTrue("open_id".equals(CustomizeExpression.parseExpression("returnedObj.openId", evalCtx3)));
Assert.assertTrue("ext_v_2".equals(CustomizeExpression.parseExpression("returnedObj.user.ext.['ext_k_2']", evalCtx3)));
}
private static Object[] init() {
Object[] allArguments = new Object[6];
allArguments[0] = "String_test";
@ -76,6 +108,19 @@ public class CustomizeExpressionTest {
return allArguments;
}
private static Order newOrder() {
Map m2 = new HashMap();
m2.put("ext_k_2", "ext_v_2");
User user2 = new User(101, "sw0", m2);
List l1 = new ArrayList();
l1.add("c");
return new Order(999, "uuid", l1, user2, "open_id", new Object[] {
0,
3,
"2"
});
}
static class Order {
public Order(int id, String uuid, List orderIds, User user, String openId, Object[] ids) {
this.id = id;

View File

@ -40,13 +40,13 @@ public class BaseTagAnnotationInterceptor {
if (tags != null && tags.value().length > 0) {
for (final Tag tag : tags.value()) {
if (!TagUtil.isReturnTag(tag.value())) {
TagUtil.tagParamsSpan(activeSpan, context, tag);
TagUtil.tagSpan(activeSpan, context, tag);
}
}
}
final Tag tag = method.getAnnotation(Tag.class);
if (tag != null && !TagUtil.isReturnTag(tag.value())) {
TagUtil.tagParamsSpan(activeSpan, context, tag);
TagUtil.tagSpan(activeSpan, context, tag);
}
}
@ -60,13 +60,13 @@ public class BaseTagAnnotationInterceptor {
if (tags != null && tags.value().length > 0) {
for (final Tag tag : tags.value()) {
if (TagUtil.isReturnTag(tag.value())) {
TagUtil.tagReturnSpanSpan(localSpan, context, tag);
TagUtil.tagSpan(localSpan, context, tag);
}
}
}
final Tag tag = method.getAnnotation(Tag.class);
if (tag != null && TagUtil.isReturnTag(tag.value())) {
TagUtil.tagReturnSpanSpan(localSpan, context, tag);
TagUtil.tagSpan(localSpan, context, tag);
}
}

View File

@ -47,13 +47,13 @@ public class BaseTraceAnnotationInterceptor {
if (tags != null && tags.value().length > 0) {
for (final Tag tag : tags.value()) {
if (!TagUtil.isReturnTag(tag.value())) {
TagUtil.tagParamsSpan(localSpan, context, tag);
TagUtil.tagSpan(localSpan, context, tag);
}
}
}
final Tag tag = method.getAnnotation(Tag.class);
if (tag != null && !TagUtil.isReturnTag(tag.value())) {
TagUtil.tagParamsSpan(localSpan, context, tag);
TagUtil.tagSpan(localSpan, context, tag);
}
}
@ -68,13 +68,13 @@ public class BaseTraceAnnotationInterceptor {
if (tags != null && tags.value().length > 0) {
for (final Tag tag : tags.value()) {
if (TagUtil.isReturnTag(tag.value())) {
TagUtil.tagReturnSpanSpan(localSpan, context, tag);
TagUtil.tagSpan(localSpan, context, tag);
}
}
}
final Tag tag = method.getAnnotation(Tag.class);
if (tag != null && TagUtil.isReturnTag(tag.value())) {
TagUtil.tagReturnSpanSpan(localSpan, context, tag);
TagUtil.tagSpan(localSpan, context, tag);
}
} finally {
ContextManager.stopSpan();

View File

@ -26,16 +26,11 @@ import org.apache.skywalking.apm.agent.core.util.CustomizeExpression;
import org.apache.skywalking.apm.toolkit.trace.Tag;
public class TagUtil {
public static void tagParamsSpan(final AbstractSpan span, final Map<String, Object> context,
public static void tagSpan(final AbstractSpan span, final Map<String, Object> context,
final Tag tag) {
new StringTag(tag.key()).set(span, CustomizeExpression.parseExpression(tag.value(), context));
}
public static void tagReturnSpanSpan(final AbstractSpan span, final Map<String, Object> context,
final Tag tag) {
new StringTag(tag.key()).set(span, CustomizeExpression.parseReturnExpression(tag.value(), context));
}
public static Boolean isReturnTag(String expression) {
String[] es = expression.split("\\.");
return "returnedObj".equals(es[0]);

View File

@ -158,14 +158,15 @@ public class TagAnnotationTest {
AbstractTracingSpan tracingSpan = spans.get(0);
assertThat(tracingSpan.getOperationName(), is("testMethod"));
SpanAssert.assertLogSize(tracingSpan, 0);
SpanAssert.assertTagSize(tracingSpan, 2);
SpanAssert.assertTagSize(tracingSpan, 3);
List<TagValuePair> tags = SpanHelper.getTags(tracingSpan);
assertThat(tags.get(0).getKey().key(), is("username"));
assertThat(tags.get(0).getValue(), is("wangwu"));
assertThat(tags.get(1).getKey().key(), is("info"));
assertThat(tags.get(1).getValue(), is("username=wangwu,age=18"));
assertThat(tags.get(2).getKey().key(), is("info2"));
assertThat(tags.get(2).getValue(), is("username=wangwu,age=18"));
}
@Test
@ -182,13 +183,15 @@ public class TagAnnotationTest {
AbstractTracingSpan tracingSpan = spans.get(0);
assertThat(tracingSpan.getOperationName(), is("testMethod"));
SpanAssert.assertLogSize(tracingSpan, 0);
SpanAssert.assertTagSize(tracingSpan, 2);
SpanAssert.assertTagSize(tracingSpan, 3);
List<TagValuePair> tags = SpanHelper.getTags(tracingSpan);
assertThat(tags.get(0).getKey().key(), is("username"));
assertThat(tags.get(0).getValue(), is("wangwu"));
assertThat(tags.get(1).getKey().key(), is("info"));
assertThat(tags.get(1).getValue(), is("username=wangwu,age=18"));
assertThat(tags.get(2).getKey().key(), is("info2"));
assertThat(tags.get(2).getValue(), is("username=wangwu,age=18"));
}
@Test
@ -208,13 +211,15 @@ public class TagAnnotationTest {
AbstractTracingSpan tracingSpan = spans.get(0);
assertThat(tracingSpan.getOperationName(), is("testMethod"));
SpanAssert.assertLogSize(tracingSpan, 0);
SpanAssert.assertTagSize(tracingSpan, 2);
SpanAssert.assertTagSize(tracingSpan, 3);
List<TagValuePair> tags = SpanHelper.getTags(tracingSpan);
assertThat(tags.get(0).getKey().key(), is("username"));
assertThat(tags.get(0).getValue(), is("wangwu"));
assertThat(tags.get(1).getKey().key(), is("info"));
assertThat(tags.get(1).getValue(), is("username=wangwu,age=18"));
assertThat(tags.get(2).getKey().key(), is("info2"));
assertThat(tags.get(2).getValue(), is("username=wangwu,age=18"));
}
@Test
@ -296,17 +301,29 @@ public class TagAnnotationTest {
return new User(username, age);
}
@Tags({@Tag(key = "username", value = "arg[0]"), @Tag(key = "info", value = "returnedObj.0.info")})
@Tags({
@Tag(key = "username", value = "arg[0]"),
@Tag(key = "info", value = "returnedObj.0.info"),
@Tag(key = "info2", value = "returnedObj.[0].info"),
})
public List<User> testMethodWithReturnList(String username, Integer age) {
return Arrays.asList(new User(username, age));
}
@Tags({@Tag(key = "username", value = "arg[0]"), @Tag(key = "info", value = "returnedObj.0.info")})
@Tags({
@Tag(key = "username", value = "arg[0]"),
@Tag(key = "info", value = "returnedObj.0.info"),
@Tag(key = "info2", value = "returnedObj.[0].info"),
})
public User[] testMethodWithReturnArray(String username, Integer age) {
return new User[]{new User(username, age)};
}
@Tags({@Tag(key = "username", value = "arg[0]"), @Tag(key = "info", value = "returnedObj.user.info")})
@Tags({
@Tag(key = "username", value = "arg[0]"),
@Tag(key = "info", value = "returnedObj.user.info"),
@Tag(key = "info2", value = "returnedObj.['user'].info")
})
public Map<String, User> testMethodWithReturnMap(String username, Integer age) {
Map<String, User> userMap = new HashMap<>();
userMap.put("user", new User(username, age));

View File

@ -81,8 +81,8 @@ class BaseInterceptorMethods {
operationNameSuffix.append(CustomizeExpression.parseExpression(expression, evalContext));
}
}
evalAndPopulate(evalContext, tags, spanTags);
evalAndPopulate(evalContext, logs, spanLogs);
evalAndPopulate(evalContext, false, tags, spanTags);
evalAndPopulate(evalContext, false, logs, spanLogs);
operationName = operationNameSuffix.insert(0, operationName).toString();
AbstractSpan localSpan = ContextManager.createLocalSpan(operationName);
@ -121,8 +121,8 @@ class BaseInterceptorMethods {
try {
Map<String, Object> evalContext = CustomizeExpression.evaluationReturnContext(ret);
evalReturnAndPopulate(evalContext, tags, spanTags);
evalReturnAndPopulate(evalContext, logs, spanLogs);
evalAndPopulate(evalContext, true, tags, spanTags);
evalAndPopulate(evalContext, true, logs, spanLogs);
tagSpanTags(localSpan, spanTags);
tagSpanLogs(localSpan, spanLogs);
@ -138,27 +138,15 @@ class BaseInterceptorMethods {
ContextManager.activeSpan().log(t);
}
private void evalAndPopulate(Map<String, Object> context, Map<String, String> exprMap, Map<String, String> toMap) {
if (exprMap != null && !exprMap.isEmpty()) {
for (Map.Entry<String, String> entry : exprMap.entrySet()) {
String expression = entry.getValue();
if (isReturnedObjExpression(expression)) {
continue;
}
toMap.put(entry.getKey(), CustomizeExpression.parseExpression(expression, context));
}
}
}
private void evalReturnAndPopulate(Map<String, Object> context, Map<String, String> exprMap,
private void evalAndPopulate(Map<String, Object> context, boolean returnExpr, Map<String, String> exprMap,
Map<String, String> toMap) {
if (exprMap != null && !exprMap.isEmpty()) {
for (Map.Entry<String, String> entry : exprMap.entrySet()) {
String expression = entry.getValue();
if (!isReturnedObjExpression(expression)) {
if (isReturnedObjExpression(expression) != returnExpr) {
continue;
}
toMap.put(entry.getKey(), CustomizeExpression.parseReturnExpression(expression, context));
toMap.put(entry.getKey(), CustomizeExpression.parseExpression(expression, context));
}
}
}

View File

@ -60,6 +60,18 @@ Implementing enhancements to custom classes requires two steps.
<tag key="tag_5_1">arg[0].[0]</tag>
<log key="log_5_1">arg[1]</log>
</method>
<method method="retArray([Ljava.lang.Object;)" operation_name="/retArray" static="false">
<tag key="tag_ret">returnedObj.[0]</tag>
<log key="log_map">returnedObj.[1]</log>
</method>
<method method="retList(java.util.List)" operation_name="/retList" static="false">
<tag key="tag_ret">returnedObj.[0]</tag>
<log key="log_map">returnedObj.[1]</log>
</method>
<method method="retMap(java.util.Map)" operation_name="/retMap" static="false">
<tag key="tag_ret">returnedObj.['k1']</tag>
<log key="log_map">returnedObj.['k2']</log>
</method>
</class>
</enhanced>

View File

@ -153,6 +153,45 @@ segmentItems:
- {key: p2, value: '16'}
- {key: username, value: lisi}
skipAnalysis: 'true'
- operationName: test.apache.skywalking.apm.testcase.toolkit.controller.TestService.testTagAnnotationReturnArray(java.lang.String,java.lang.Integer)
parentSpanId: 0
spanId: 11
spanLayer: Unknown
startTime: nq 0
endTime: nq 0
componentId: 0
isError: false
spanType: Local
peer: ''
tags:
- {key: username, value: lisi}
skipAnalysis: 'true'
- operationName: test.apache.skywalking.apm.testcase.toolkit.controller.TestService.testTagAnnotationReturnList(java.lang.String,java.lang.Integer)
parentSpanId: 0
spanId: 12
spanLayer: Unknown
startTime: nq 0
endTime: nq 0
componentId: 0
isError: false
spanType: Local
peer: ''
tags:
- {key: username, value: wangwu}
skipAnalysis: 'true'
- operationName: test.apache.skywalking.apm.testcase.toolkit.controller.TestService.testTagAnnotationReturnMap(java.lang.String,java.lang.Integer)
parentSpanId: 0
spanId: 13
spanLayer: Unknown
startTime: nq 0
endTime: nq 0
componentId: 0
isError: false
spanType: Local
peer: ''
tags:
- {key: username, value: zhaoliu}
skipAnalysis: 'true'
- operationName: GET:/case/tool-kit
parentSpanId: -1
spanId: 0

View File

@ -59,6 +59,9 @@ public class TestController {
testService.testTagAnnotation("testTagAnnotationParam1", "testTagAnnotationParam2");
testService.testTagAnnotationReturnInfo("zhangsan", 15);
TestService.testStatic("lisi", 16);
testService.testTagAnnotationReturnArray("lisi", 16);
testService.testTagAnnotationReturnList("wangwu", 17);
testService.testTagAnnotationReturnMap("zhaoliu", 18);
TraceContext.putCorrelation(CORRELATION_CONTEXT_KEY, CORRELATION_CONTEXT_VALUE);
ActiveSpan.tag("traceID", TraceContext.traceId());
ActiveSpan.tag("segmentID", TraceContext.segmentId());

View File

@ -18,6 +18,10 @@
package test.apache.skywalking.apm.testcase.toolkit.controller;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@ -97,6 +101,26 @@ public class TestService {
ActiveSpan.info("TestInfoMsg");
}
@Trace
@Tag(key = "username", value = "returnedObj.[0].username")
public User[] testTagAnnotationReturnArray(final String username, final Integer age) {
return new User[]{new User(username, age)};
}
@Trace
@Tag(key = "username", value = "returnedObj.[0].username")
public List<User> testTagAnnotationReturnList(final String username, final Integer age) {
return Arrays.asList(new User(username, age));
}
@Trace
@Tag(key = "username", value = "returnedObj.['user'].username")
public Map<String, User> testTagAnnotationReturnMap(final String username, final Integer age) {
Map<String, User> userMap = new HashMap<>();
userMap.put("user", new User(username, age));
return userMap;
}
public void asyncRunnable(Runnable runnable) {
SERVICE.submit(RunnableWrapper.of(runnable));
}

View File

@ -59,11 +59,23 @@
<log key="log_1_1">arg[1]</log>
</method>
<method method="method([Ljava.lang.Object;)" operation_name="/method_4" static="false">
<tag key="tag_4_1">arg[0].[0]</tag>CustomizeController
<tag key="tag_4_1">arg[0].[0]</tag>
</method>
<method method="method(java.util.List,int.class)" operation_name="/method_5" static="false">
<tag key="tag_5_1">arg[0].[0]</tag>
<log key="log_5_1">arg[1]</log>
</method>
<method method="retArray([Ljava.lang.Object;)" operation_name="/retArray" static="false">
<tag key="tag_ret">returnedObj.[0]</tag>
<log key="log_map">returnedObj.[1]</log>
</method>
<method method="retList(java.util.List)" operation_name="/retList" static="false">
<tag key="tag_ret">returnedObj.[0]</tag>
<log key="log_map">returnedObj.[1]</log>
</method>
<method method="retMap(java.util.Map)" operation_name="/retMap" static="false">
<tag key="tag_ret">returnedObj.['k1']</tag>
<log key="log_map">returnedObj.['k2']</log>
</method>
</class>
</enhanced>

View File

@ -136,6 +136,45 @@ segmentItems:
- logEvent:
- {key: log_map, value: '100'}
skipAnalysis: 'false'
- operationName: /retArray
parentSpanId: 0
spanId: 11
startTime: nq 0
endTime: nq 0
isError: false
spanType: Local
tags:
- {key: tag_ret, value: '1'}
logs:
- logEvent:
- {key: log_map, value: '2'}
skipAnalysis: 'false'
- operationName: /retList
parentSpanId: 0
spanId: 12
startTime: nq 0
endTime: nq 0
isError: false
spanType: Local
tags:
- {key: tag_ret, value: 'a2'}
logs:
- logEvent:
- {key: log_map, value: 'a3'}
skipAnalysis: 'false'
- operationName: /retMap
parentSpanId: 0
spanId: 13
startTime: nq 0
endTime: nq 0
isError: false
spanType: Local
tags:
- {key: tag_ret, value: 'v1'}
logs:
- logEvent:
- {key: log_map, value: 'v2'}
skipAnalysis: 'false'
- operationName: GET:/case/customize
parentSpanId: -1
spanId: 0

View File

@ -81,6 +81,20 @@ public class CustomizeController {
testService1.retString("str0");
testService1.retModel0(m0);
testService2.retArray(new Object[] {
'1',
2,
"3"
});
testService2.retList(new ArrayList() {{
add("a2");
add("a3");
}});
testService2.retMap(new HashMap() {{
put("k1", "v1");
put("k2", "v2");
}});
LOGGER.info(SUCCESS);
return SUCCESS;
}

View File

@ -19,6 +19,7 @@
package test.apache.skywalking.apm.testcase.customize.service;
import java.util.List;
import java.util.Map;
public class TestService2 {
@ -33,4 +34,16 @@ public class TestService2 {
public void method(List str0, int count) {
}
public Object[] retArray(Object[] objects) {
return objects;
}
public List retList(List str0) {
return str0;
}
public Map retMap(Map map) {
return map;
}
}