From a3241f0a592eecf88cfd6519d6ce23f223f575c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=99=9F=20Wu=20Sheng?= Date: Tue, 27 Aug 2019 15:07:44 +0800 Subject: [PATCH] Support op group name in agent config core. (#3299) * Support op group name in agent config core. * Provide core API for operation name grouping, rest template plugin update, and doc update. * Fix doc * Fix test case. * Fix missing changes. * add shuyun to powered-by.md (#3341) * add shuyun to powered-by.md * update powered-by.md * Remove static method. --- .../apm/util/ConfigInitializer.java | 52 +++++++---- .../apm/agent/core/conf/Config.java | 74 ++++++++------- .../agent/core/conf/OPGroupDefinition.java | 25 ++++++ .../context/OperationNameFormatService.java | 90 +++++++++++++++++++ ...skywalking.apm.agent.core.boot.BootService | 1 + .../agent/core/boot/ServiceManagerTest.java | 2 +- .../async/FutureGetInterceptor.java | 3 +- .../async/RestExecuteInterceptor.java | 20 ++++- .../sync/RestExecuteInterceptor.java | 11 ++- .../setup/service-agent/java-agent/README.md | 1 + .../java-agent/op_name_group_rule.md | 22 +++++ docs/powered-by.md | 1 + 12 files changed, 243 insertions(+), 59 deletions(-) create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/OPGroupDefinition.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/OperationNameFormatService.java create mode 100644 docs/en/setup/service-agent/java-agent/op_name_group_rule.md diff --git a/apm-commons/apm-util/src/main/java/org/apache/skywalking/apm/util/ConfigInitializer.java b/apm-commons/apm-util/src/main/java/org/apache/skywalking/apm/util/ConfigInitializer.java index 6e9f2fb348..0072d0055d 100644 --- a/apm-commons/apm-util/src/main/java/org/apache/skywalking/apm/util/ConfigInitializer.java +++ b/apm-commons/apm-util/src/main/java/org/apache/skywalking/apm/util/ConfigInitializer.java @@ -23,12 +23,12 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.logging.Logger; /** - * Init a class's static fields by a {@link Properties}, - * including static fields and static inner classes. + * Init a class's static fields by a {@link Properties}, including static fields and static inner classes. *

* Created by wusheng on 2017/1/9. */ @@ -44,21 +44,39 @@ public class ConfigInitializer { for (Field field : recentConfigType.getFields()) { if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) { String configKey = (parentDesc + "." + field.getName()).toLowerCase(); - String value = properties.getProperty(configKey); - if (value != null) { - Class type = field.getType(); - if (type.equals(int.class)) - field.set(null, Integer.valueOf(value)); - else if (type.equals(String.class)) - field.set(null, value); - else if (type.equals(long.class)) - field.set(null, Long.valueOf(value)); - else if (type.equals(boolean.class)) - field.set(null, Boolean.valueOf(value)); - else if (type.equals(List.class)) - field.set(null, convert2List(value)); - else if (type.isEnum()) - field.set(null, Enum.valueOf((Class)type, value.toUpperCase())); + /** + * Map config format is, config_key[map_key]=map_value + * Such as plugin.opgroup.resttemplate.rule[abc]=/url/path + */ + if (field.getType().equals(Map.class)) { + Map map = (Map)field.get(null); + for (Object key : properties.keySet()) { + String stringKey = key.toString(); + if (stringKey.startsWith(configKey + "[") && stringKey.endsWith("]")) { + String itemKey = stringKey.substring(configKey.length() + 1, stringKey.length() - 1); + map.put(itemKey, properties.getProperty(stringKey)); + } + } + } else { + /** + * Others typical field type + */ + String value = properties.getProperty(configKey); + if (value != null) { + Class type = field.getType(); + if (type.equals(int.class)) + field.set(null, Integer.valueOf(value)); + else if (type.equals(String.class)) + field.set(null, value); + else if (type.equals(long.class)) + field.set(null, Long.valueOf(value)); + else if (type.equals(boolean.class)) + field.set(null, Boolean.valueOf(value)); + else if (type.equals(List.class)) + field.set(null, convert2List(value)); + else if (type.isEnum()) + field.set(null, Enum.valueOf((Class)type, value.toUpperCase())); + } } } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java index d5cb7686c8..74589959a0 100755 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java @@ -19,14 +19,13 @@ package org.apache.skywalking.apm.agent.core.conf; +import java.util.HashMap; +import java.util.Map; import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; import org.apache.skywalking.apm.agent.core.logging.core.LogLevel; import org.apache.skywalking.apm.agent.core.logging.core.LogOutput; import org.apache.skywalking.apm.agent.core.logging.core.WriterFactory; -import java.util.HashMap; -import java.util.Map; - /** * This is the core config in sniffer agent. * @@ -41,14 +40,14 @@ public class Config { public static String NAMESPACE = ""; /** - * Service name is showed in skywalking-ui. Suggestion: set a unique name for each service, - * service instance nodes share the same code + * Service name is showed in skywalking-ui. Suggestion: set a unique name for each service, service instance + * nodes share the same code */ public static String SERVICE_NAME = ""; /** - * Authentication active is based on backend setting, see application.yml for more details. - * For most scenarios, this needs backend extensions, only basic match auth provided in default implementation. + * Authentication active is based on backend setting, see application.yml for more details. For most scenarios, + * this needs backend extensions, only basic match auth provided in default implementation. */ public static String AUTHENTICATION = ""; @@ -70,8 +69,8 @@ public class Config { public static int SPAN_LIMIT_PER_SEGMENT = 300; /** - * If true, skywalking agent will save all instrumented classes files in `/debugging` folder. - * Skywalking team may ask for these files in order to resolve compatible problem. + * If true, skywalking agent will save all instrumented classes files in `/debugging` folder. Skywalking team + * may ask for these files in order to resolve compatible problem. */ public static boolean IS_OPEN_DEBUGGING_CLASS = false; @@ -96,16 +95,14 @@ public class Config { public static int CAUSE_EXCEPTION_DEPTH = 5; /** - * How long should the agent wait (in minute) - * before re-registering to the OAP server - * after receiving reset command + * How long should the agent wait (in minute) before re-registering to the OAP server after receiving reset + * command */ public static int COOL_DOWN_THRESHOLD = 10; /** - * Force reconnection period of grpc, based on grpc_channel_check_interval. - * If count of check grpc channel status more than this number. - * The channel check will call channel.getState(true) to requestConnection. + * Force reconnection period of grpc, based on grpc_channel_check_interval. If count of check grpc channel + * status more than this number. The channel check will call channel.getState(true) to requestConnection. */ public static long FORCE_RECONNECTION_PERIOD = 1; } @@ -177,19 +174,13 @@ public class Config { public static LogOutput OUTPUT = LogOutput.FILE; /** - * The log patten. Default is "%level %timestamp %thread %class : %msg %throwable". - * Each conversion specifiers starts with a percent sign '%' and fis followed by conversion word. - * There are some default conversion specifiers: - * %thread = ThreadName - * %level = LogLevel {@link LogLevel} - * %timestamp = The now() who format is 'yyyy-MM-dd HH:mm:ss:SSS' - * %class = SimpleName of TargetClass - * %msg = Message of user input - * %throwable = Throwable of user input - * %agent_name = ServiceName of Agent {@link Agent#SERVICE_NAME} + * The log patten. Default is "%level %timestamp %thread %class : %msg %throwable". Each conversion specifiers + * starts with a percent sign '%' and fis followed by conversion word. There are some default conversion + * specifiers: %thread = ThreadName %level = LogLevel {@link LogLevel} %timestamp = The now() who format is + * 'yyyy-MM-dd HH:mm:ss:SSS' %class = SimpleName of TargetClass %msg = Message of user input %throwable = + * Throwable of user input %agent_name = ServiceName of Agent {@link Agent#SERVICE_NAME} * * @see org.apache.skywalking.apm.agent.core.logging.core.PatternLogger#DEFAULT_CONVERTER_MAP - * */ public static String PATTERN = "%level %timestamp %thread %class : %msg %throwable"; } @@ -203,7 +194,8 @@ public class Config { public static class MongoDB { /** - * If true, trace all the parameters in MongoDB access, default is false. Only trace the operation, not include parameters. + * If true, trace all the parameters in MongoDB access, default is false. Only trace the operation, not + * include parameters. */ public static boolean TRACE_PARAM = false; } @@ -230,27 +222,29 @@ public class Config { public static class SpringMVC { /** - * If true, the fully qualified method name will be used as the endpoint name instead of the request URL, default is false. + * If true, the fully qualified method name will be used as the endpoint name instead of the request URL, + * default is false. */ public static boolean USE_QUALIFIED_NAME_AS_ENDPOINT_NAME = false; } public static class Toolkit { /** - * If true, the fully qualified method name will be used as the operation name instead of the given operation name, default is false. + * If true, the fully qualified method name will be used as the operation name instead of the given + * operation name, default is false. */ public static boolean USE_QUALIFIED_NAME_AS_OPERATION_NAME = false; } public static class MySQL { /** - * If set to true, the parameters of the sql (typically {@link java.sql.PreparedStatement}) - * would be collected. + * If set to true, the parameters of the sql (typically {@link java.sql.PreparedStatement}) would be + * collected. */ public static boolean TRACE_SQL_PARAMETERS = false; /** - * For the sake of performance, SkyWalking won't save the entire parameters string into the tag, - * but only the first {@code SQL_PARAMETERS_MAX_LENGTH} characters. + * For the sake of performance, SkyWalking won't save the entire parameters string into the tag, but only + * the first {@code SQL_PARAMETERS_MAX_LENGTH} characters. * * Set a negative number to save the complete parameter string to the tag. */ @@ -259,7 +253,8 @@ public class Config { public static class SolrJ { /** - * If true, trace all the query parameters(include deleteByIds and deleteByQuery) in Solr query request, default is false. + * If true, trace all the query parameters(include deleteByIds and deleteByQuery) in Solr query request, + * default is false. */ public static boolean TRACE_STATEMENT = false; @@ -269,5 +264,16 @@ public class Config { public static boolean TRACE_OPS_PARAMS = false; } + /** + * Operation name group rules + */ + public static class OPGroup { + /** + * Rules for RestTemplate plugin + */ + public static class RestTemplate implements OPGroupDefinition { + public static Map RULE = new HashMap(); + } + } } } diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/OPGroupDefinition.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/OPGroupDefinition.java new file mode 100644 index 0000000000..604205412f --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/OPGroupDefinition.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.agent.core.conf; + +/** + * @author wusheng + */ +public interface OPGroupDefinition { +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/OperationNameFormatService.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/OperationNameFormatService.java new file mode 100644 index 0000000000..6fef376ade --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/OperationNameFormatService.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.agent.core.context; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.skywalking.apm.agent.core.boot.BootService; +import org.apache.skywalking.apm.agent.core.boot.DefaultImplementor; +import org.apache.skywalking.apm.agent.core.conf.Config; +import org.apache.skywalking.apm.agent.core.conf.OPGroupDefinition; +import org.apache.skywalking.apm.util.StringFormatGroup; + +/** + * Support operation name format by config. Every plugin could declare its own rule to avoid performance concerns. + * + * Right now, the rule is REGEX based, it definitely has much space to optimize, because basically, only `*` is required + * to be supported. + * + * @author wusheng + */ +@DefaultImplementor +public class OperationNameFormatService implements BootService { + private static final Map RULES = new ConcurrentHashMap(); + + @Override public void prepare() throws Throwable { + for (Class ruleName : Config.Plugin.OPGroup.class.getClasses()) { + if (!OPGroupDefinition.class.isAssignableFrom(ruleName)) { + continue; + } + StringFormatGroup formatGroup = RULES.get(ruleName); + if (formatGroup == null) { + formatGroup = new StringFormatGroup(); + RULES.put(ruleName, formatGroup); + } + for (Field ruleNameField : ruleName.getFields()) { + if (ruleNameField.getType().equals(Map.class)) { + Map rule = (Map)ruleNameField.get(null); + for (Map.Entry entry : rule.entrySet()) { + formatGroup.addRule(entry.getKey(), entry.getValue()); + } + } + } + } + } + + @Override public void boot() throws Throwable { + + } + + @Override public void onComplete() throws Throwable { + + } + + @Override public void shutdown() throws Throwable { + + } + + /** + * Format the operation name based on group rules + * + * @param definition in the Config + * @param opName represents the operation name literal string + * @return format string if rule matched or the given opName + */ + public String formatOperationName(Class definition, String opName) { + StringFormatGroup formatGroup = RULES.get(definition); + if (formatGroup == null) { + return opName; + } else { + return formatGroup.format(opName).getName(); + } + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService b/apm-sniffer/apm-agent-core/src/main/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService index b0a4a5e711..7dbc784f7d 100644 --- a/apm-sniffer/apm-agent-core/src/main/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService +++ b/apm-sniffer/apm-agent-core/src/main/resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService @@ -25,3 +25,4 @@ org.apache.skywalking.apm.agent.core.remote.ServiceAndEndpointRegisterClient org.apache.skywalking.apm.agent.core.context.ContextManagerExtendService org.apache.skywalking.apm.agent.core.commands.CommandService org.apache.skywalking.apm.agent.core.commands.CommandExecutorService +org.apache.skywalking.apm.agent.core.context.OperationNameFormatService diff --git a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerTest.java b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerTest.java index 743ddf74bf..ff5b3a8081 100644 --- a/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerTest.java +++ b/apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/boot/ServiceManagerTest.java @@ -55,7 +55,7 @@ public class ServiceManagerTest { public void testServiceDependencies() throws Exception { HashMap registryService = getFieldValue(ServiceManager.INSTANCE, "bootedServices"); - assertThat(registryService.size(), is(9)); + assertThat(registryService.size(), is(10)); assertTraceSegmentServiceClient(ServiceManager.INSTANCE.findService(TraceSegmentServiceClient.class)); assertContextManager(ServiceManager.INSTANCE.findService(ContextManager.class)); diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/async/FutureGetInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/async/FutureGetInterceptor.java index bda35ed5c7..2502fa5fff 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/async/FutureGetInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/async/FutureGetInterceptor.java @@ -20,7 +20,6 @@ package org.apache.skywalking.apm.plugin.spring.resttemplate.async; import java.lang.reflect.Method; -import java.net.URI; import org.apache.skywalking.apm.agent.core.context.ContextManager; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; @@ -33,7 +32,7 @@ public class FutureGetInterceptor implements InstanceMethodsAroundInterceptor { public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { Object[] cacheValues = (Object[])objInst.getSkyWalkingDynamicField(); - ContextManager.createLocalSpan("future/get:" + ((URI)cacheValues[0]).getPath()); + ContextManager.createLocalSpan("future/get:" + cacheValues[0]); } @Override diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/async/RestExecuteInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/async/RestExecuteInterceptor.java index 9b80b6ac52..81c0eb8893 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/async/RestExecuteInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/async/RestExecuteInterceptor.java @@ -20,9 +20,12 @@ package org.apache.skywalking.apm.plugin.spring.resttemplate.async; import java.lang.reflect.Method; import java.net.URI; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.conf.Config; import org.apache.skywalking.apm.agent.core.context.ContextCarrier; import org.apache.skywalking.apm.agent.core.context.ContextManager; import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.OperationNameFormatService; import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; @@ -34,6 +37,7 @@ import org.apache.skywalking.apm.plugin.spring.commons.EnhanceCacheObjects; import org.springframework.http.HttpMethod; public class RestExecuteInterceptor implements InstanceMethodsAroundInterceptor { + private OperationNameFormatService nameFormatService; @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, @@ -42,15 +46,23 @@ public class RestExecuteInterceptor implements InstanceMethodsAroundInterceptor final HttpMethod httpMethod = (HttpMethod)allArguments[1]; final ContextCarrier contextCarrier = new ContextCarrier(); + if (nameFormatService == null) { + nameFormatService = ServiceManager.INSTANCE.findService(OperationNameFormatService.class); + } + String remotePeer = requestURL.getHost() + ":" + (requestURL.getPort() > 0 ? requestURL.getPort() : "https".equalsIgnoreCase(requestURL.getScheme()) ? 443 : 80); - AbstractSpan span = ContextManager.createExitSpan(requestURL.getPath(), contextCarrier, remotePeer); + + String formatURIPath = nameFormatService.formatOperationName(Config.Plugin.OPGroup.RestTemplate.class, requestURL.getPath()); + AbstractSpan span = ContextManager.createExitSpan( + formatURIPath, + contextCarrier, remotePeer); span.setComponent(ComponentsDefine.SPRING_REST_TEMPLATE); Tags.URL.set(span, requestURL.getScheme() + "://" + requestURL.getHost() + ":" + requestURL.getPort() + requestURL.getPath()); Tags.HTTP.METHOD.set(span, httpMethod.toString()); SpanLayer.asHttp(span); Object[] cacheValues = new Object[3]; - cacheValues[0] = requestURL; + cacheValues[0] = formatURIPath; cacheValues[1] = contextCarrier; objInst.setSkyWalkingDynamicField(cacheValues); } @@ -61,8 +73,8 @@ public class RestExecuteInterceptor implements InstanceMethodsAroundInterceptor Object[] cacheValues = (Object[])objInst.getSkyWalkingDynamicField(); cacheValues[2] = ContextManager.capture(); if (ret != null) { - URI uri = (URI)cacheValues[0]; - ((EnhancedInstance)ret).setSkyWalkingDynamicField(new EnhanceCacheObjects(uri.getPath(), ComponentsDefine.SPRING_REST_TEMPLATE, SpanLayer.HTTP, (ContextSnapshot)cacheValues[2])); + String uri = (String)cacheValues[0]; + ((EnhancedInstance)ret).setSkyWalkingDynamicField(new EnhanceCacheObjects(uri, ComponentsDefine.SPRING_REST_TEMPLATE, SpanLayer.HTTP, (ContextSnapshot)cacheValues[2])); } ContextManager.stopSpan(); return ret; diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/sync/RestExecuteInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/sync/RestExecuteInterceptor.java index f634df071d..c5060b4ecf 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/sync/RestExecuteInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/resttemplate-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/resttemplate/sync/RestExecuteInterceptor.java @@ -20,7 +20,10 @@ package org.apache.skywalking.apm.plugin.spring.resttemplate.sync; import java.lang.reflect.Method; import java.net.URI; +import org.apache.skywalking.apm.agent.core.boot.ServiceManager; +import org.apache.skywalking.apm.agent.core.conf.Config; import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.OperationNameFormatService; import org.apache.skywalking.apm.agent.core.context.tag.Tags; import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; @@ -32,6 +35,7 @@ import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.springframework.http.HttpMethod; public class RestExecuteInterceptor implements InstanceMethodsAroundInterceptor { + private OperationNameFormatService nameFormatService; @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, @@ -40,8 +44,13 @@ public class RestExecuteInterceptor implements InstanceMethodsAroundInterceptor final HttpMethod httpMethod = (HttpMethod)allArguments[1]; final ContextCarrier contextCarrier = new ContextCarrier(); + if (nameFormatService == null) { + nameFormatService = ServiceManager.INSTANCE.findService(OperationNameFormatService.class); + } + String remotePeer = requestURL.getHost() + ":" + (requestURL.getPort() > 0 ? requestURL.getPort() : "https".equalsIgnoreCase(requestURL.getScheme()) ? 443 : 80); - AbstractSpan span = ContextManager.createExitSpan(requestURL.getPath(), contextCarrier, remotePeer); + String formatURIPath = nameFormatService.formatOperationName(Config.Plugin.OPGroup.RestTemplate.class, requestURL.getPath()); + AbstractSpan span = ContextManager.createExitSpan(formatURIPath, contextCarrier, remotePeer); span.setComponent(ComponentsDefine.SPRING_REST_TEMPLATE); Tags.URL.set(span, requestURL.getScheme() + "://" + requestURL.getHost() + ":" + requestURL.getPort() + requestURL.getPath()); diff --git a/docs/en/setup/service-agent/java-agent/README.md b/docs/en/setup/service-agent/java-agent/README.md index 3dfb2dfca4..e6d48dd232 100755 --- a/docs/en/setup/service-agent/java-agent/README.md +++ b/docs/en/setup/service-agent/java-agent/README.md @@ -100,6 +100,7 @@ property key | Description | Default | `plugin.mysql.sql_parameters_max_length`|If set to positive number, the `db.sql.parameters` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem.|`512`| `plugin.solrj.trace_statement`|If true, trace all the query parameters(include deleteByIds and deleteByQuery) in Solr query request, default is false.|`false`| `plugin.solrj.trace_ops_params`|If true, trace all the operation parameters in Solr request, default is false.|`false`| +`plugin.opgroup.*`|Support operation name customize group rules in different plugins. Read [Group rule supported plugins](op_name_group_rule.md)|Not set| ## Optional Plugins Java agent plugins are all pluggable. Optional plugins could be provided in `optional-plugins` folder under agent or 3rd party repositories. diff --git a/docs/en/setup/service-agent/java-agent/op_name_group_rule.md b/docs/en/setup/service-agent/java-agent/op_name_group_rule.md new file mode 100644 index 0000000000..ea24f18619 --- /dev/null +++ b/docs/en/setup/service-agent/java-agent/op_name_group_rule.md @@ -0,0 +1,22 @@ +# Operation Name Group Rule +Operation Name in auto instrumentation agent is unpredictable, some time, target application carries parameter in it, due to the parameter included in URI mostly. +Those operation name are also as known endpoint name in most cases. +Such as /api/checkTicket/tk/{userToken}. + +We solved most of these cases, by leverage the parameter pattern path in framework, such as SpringMVC, Webflux, etc. +But it is undetected in RPC client side, such as HTTP restful client. +In this case, we have to ask the users to set the group rule manually. + +All rules are supported to set through agent.config, system properties and system env, like other agent settings. +- Config format, `plugin.opgroup.`plugin name`.rule[`rule name`]`=pattern regex expression + +Multiple configuration items for a single plugin with different keys are supports, such as +1. plugin.opgroup.resttemplate.rule[/rule1]=/path1 +1. plugin.opgroup.resttemplate.rule[/rule2]=/path2 +1. plugin.opgroup.resttemplate.rule[/rule2]=/path3 + +We have following plugins supporting operation name group. + +| Plugin | Config Key | Example | +|:----:|:-----:|:----| +|RestTemplate| plugin.opgroup.resttemplate.rule | plugin.opgroup.resttemplate.rule[/user/auth/{token}]=`\/user\/auth\/.*` | diff --git a/docs/powered-by.md b/docs/powered-by.md index fbfe217973..19e9277432 100644 --- a/docs/powered-by.md +++ b/docs/powered-by.md @@ -68,6 +68,7 @@ or providing commercial products including Apache SkyWalking. 1. Rongjinbao. 深圳融金宝互联网金融服务有限公司. http://www.rjb777.com 1. Shouqi Limousine & chauffeur Group 首约科技(北京)有限公司. https://www.01zhuanche.com/ 1. shuaibaoshop.com 宁波鲸灵网络科技有限公司 http://www.shuaibaoshop.com/ +1. shuyun.com 杭州数云信息技术有限公司 http://www.shuyun.com/ 1. Sijibao.com 司机宝 https://www.sijibao.com/ 1. Sinolink Securities Co.,Ltd. 国金证券佣金宝 http://www.yongjinbao.com.cn/ 1. Source++ https://sourceplusplus.com -- GitLab