From 1ff25ac4f4b67d869c1fb1b2ddb30a192b325fbb Mon Sep 17 00:00:00 2001 From: harvies Date: Tue, 22 Sep 2020 17:09:24 +0800 Subject: [PATCH] Support mongo driver 4.x version. (#5512) --- .github/workflows/plugins-test.1.yaml | 1 + .../apm-sdk-plugin/mongodb-4.x-plugin/pom.xml | 46 ++++++ .../MongoDBClientDelegateInstrumentation.java | 100 ++++++++++++ ...ngoDBOperationExecutorInstrumentation.java | 87 +++++++++++ .../MongoDBClientDelegateInterceptor.java | 70 +++++++++ .../MongoDBOperationExecutorInterceptor.java | 64 ++++++++ .../mongodb/v4/support/MongoConstants.java | 32 ++++ .../v4/support/MongoOperationHelper.java | 143 +++++++++++++++++ .../mongodb/v4/support/MongoPluginConfig.java | 42 +++++ .../v4/support/MongoRemotePeerHelper.java | 44 ++++++ .../mongodb/v4/support/MongoSpanHelper.java | 50 ++++++ .../src/main/resources/skywalking-plugin.def | 19 +++ .../MongoDBClientDelegateInterceptorTest.java | 72 +++++++++ ...ngoDBOperationExecutorInterceptorTest.java | 141 +++++++++++++++++ apm-sniffer/apm-sdk-plugin/pom.xml | 1 + .../service-agent/java-agent/Plugin-list.md | 1 + .../java-agent/Supported-list.md | 2 +- .../mongodb-4.x-scenario/bin/startup.sh | 21 +++ .../config/expectedData.yaml | 141 +++++++++++++++++ .../mongodb-4.x-scenario/configuration.yml | 24 +++ .../scenarios/mongodb-4.x-scenario/pom.xml | 145 ++++++++++++++++++ .../src/main/assembly/assembly.xml | 41 +++++ .../apm/testcase/mongodb/Application.java | 34 ++++ .../mongodb/controller/CaseController.java | 77 ++++++++++ .../src/main/resources/application.yaml | 25 +++ .../src/main/resources/log4j2.xml | 30 ++++ .../mongodb-4.x-scenario/support-version.list | 23 +++ 27 files changed, 1475 insertions(+), 1 deletion(-) create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/pom.xml create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/MongoDBClientDelegateInstrumentation.java create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/MongoDBOperationExecutorInstrumentation.java create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/MongoDBClientDelegateInterceptor.java create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/MongoDBOperationExecutorInterceptor.java create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoConstants.java create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoOperationHelper.java create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoPluginConfig.java create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoRemotePeerHelper.java create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoSpanHelper.java create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/resources/skywalking-plugin.def create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBClientDelegateInterceptorTest.java create mode 100644 apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBOperationExecutorInterceptorTest.java create mode 100644 test/plugin/scenarios/mongodb-4.x-scenario/bin/startup.sh create mode 100644 test/plugin/scenarios/mongodb-4.x-scenario/config/expectedData.yaml create mode 100644 test/plugin/scenarios/mongodb-4.x-scenario/configuration.yml create mode 100644 test/plugin/scenarios/mongodb-4.x-scenario/pom.xml create mode 100644 test/plugin/scenarios/mongodb-4.x-scenario/src/main/assembly/assembly.xml create mode 100644 test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/Application.java create mode 100644 test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java create mode 100644 test/plugin/scenarios/mongodb-4.x-scenario/src/main/resources/application.yaml create mode 100644 test/plugin/scenarios/mongodb-4.x-scenario/src/main/resources/log4j2.xml create mode 100644 test/plugin/scenarios/mongodb-4.x-scenario/support-version.list diff --git a/.github/workflows/plugins-test.1.yaml b/.github/workflows/plugins-test.1.yaml index 2b4e1fd17e..f6adf64bfc 100644 --- a/.github/workflows/plugins-test.1.yaml +++ b/.github/workflows/plugins-test.1.yaml @@ -47,6 +47,7 @@ jobs: - { name: 'kotlin-coroutine-scenario', title: 'Kotlin Coroutine 1.0.1-1.3.3 (4)' } - { name: 'lettuce-scenario', title: 'Lettuce 5.x (17)' } - { name: 'mongodb-3.x-scenario', title: 'Mongodb 3.4.0-3.11.1 (22)' } + - { name: 'mongodb-4.x-scenario', title: 'Mongodb 4.0.0-4.1.0 (7)' } - { name: 'netty-socketio-scenario', title: 'Netty-SocketIO 1.x (4)' } - { name: 'postgresql-above9.4.1207-scenario', title: 'PostgreSQL 9.4.1207+ (62)' } steps: diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/pom.xml new file mode 100644 index 0000000000..b2ca1c98a7 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/pom.xml @@ -0,0 +1,46 @@ + + + + 4.0.0 + + apm-sdk-plugin + org.apache.skywalking + 8.2.0-SNAPSHOT + + + apm-mongodb-4.x-plugin + jar + + mongodb-plugin + http://maven.apache.org + + + UTF-8 + 4.1.0 + + + + + org.mongodb + mongodb-driver-sync + ${mongodb-driver.version} + provided + + + diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/MongoDBClientDelegateInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/MongoDBClientDelegateInstrumentation.java new file mode 100644 index 0000000000..6967847593 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/MongoDBClientDelegateInstrumentation.java @@ -0,0 +1,100 @@ +/* + * 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.plugin.mongodb.v4.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.plugin.mongodb.v4.interceptor.MongoDBClientDelegateInterceptor; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * Enhance {@code com.mongodb.client.internal.MongoClientDelegate} instance, and intercept {@code + * com.mongodb.client.internal.MongoClientDelegate#getOperationExecutor()}, this is the only way to get + * OperationExecutor which is unified entrance of execute mongo command. Inject the remotePeer into enhanced OperationExecutor. + *

+ * support: 4.0.0 or higher + * + * @see MongoDBOperationExecutorInstrumentation + * @see MongoDBClientDelegateInterceptor + */ +public class MongoDBClientDelegateInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String WITNESS_CLASS = "com.mongodb.internal.connection.Cluster"; + + private static final String ENHANCE_CLASS = "com.mongodb.client.internal.MongoClientDelegate"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v4.interceptor.MongoDBClientDelegateInterceptor"; + + @Override + protected String[] witnessClasses() { + return new String[] {WITNESS_CLASS}; + } + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArgumentWithType(0, "com.mongodb.internal.connection.Cluster"); + } + + @Override + public String getConstructorInterceptor() { + return INTERCEPTOR_CLASS; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("getOperationExecutor"); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/MongoDBOperationExecutorInstrumentation.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/MongoDBOperationExecutorInstrumentation.java new file mode 100644 index 0000000000..76107836a2 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/define/MongoDBOperationExecutorInstrumentation.java @@ -0,0 +1,87 @@ +/* + * 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.plugin.mongodb.v4.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +/** + * same with org.apache.skywalking.apm.plugin.mongodb.v3.define.v38.MongoDBOperationExecutorInstrumentation + *

+ * support: 4.0.x or higher + */ +public class MongoDBOperationExecutorInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String WITNESS_CLASS = "com.mongodb.internal.operation.FindOperation"; + + private static final String ENHANCE_CLASS = "com.mongodb.client.internal.MongoClientDelegate$DelegateOperationExecutor"; + + private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.mongodb.v4.interceptor.MongoDBOperationExecutorInterceptor"; + + private static final String METHOD_NAME = "execute"; + + private static final String ARGUMENT_TYPE = "com.mongodb.client.ClientSession"; + + @Override + protected String[] witnessClasses() { + return new String[] {WITNESS_CLASS}; + } + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers + .named(METHOD_NAME) + .and(ArgumentTypeNameMatch.takesArgumentWithType(2, ARGUMENT_TYPE)) + .or(ElementMatchers.named(METHOD_NAME).and(ArgumentTypeNameMatch.takesArgumentWithType(3, ARGUMENT_TYPE))); + } + + @Override + public String getMethodsInterceptor() { + return INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/MongoDBClientDelegateInterceptor.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/MongoDBClientDelegateInterceptor.java new file mode 100644 index 0000000000..932bbea008 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/MongoDBClientDelegateInterceptor.java @@ -0,0 +1,70 @@ +/* + * 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.plugin.mongodb.v4.interceptor; + +import com.mongodb.internal.connection.Cluster; +import org.apache.skywalking.apm.agent.core.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.mongodb.v4.support.MongoRemotePeerHelper; + +import java.lang.reflect.Method; + +public class MongoDBClientDelegateInterceptor implements InstanceConstructorInterceptor, InstanceMethodsAroundInterceptor { + + private static final ILog LOGGER = LogManager.getLogger(MongoDBClientDelegateInterceptor.class); + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + Cluster cluster = (Cluster) allArguments[0]; + String remotePeer = MongoRemotePeerHelper.getRemotePeer(cluster); + objInst.setSkyWalkingDynamicField(remotePeer); + } + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) { + // do nothing + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) { + if (ret instanceof EnhancedInstance) { + // pass remotePeer to OperationExecutor, which has be enhanced as EnhancedInstance + // See: org.apache.skywalking.apm.plugin.mongodb.v3.define.v37.MongoDBOperationExecutorInstrumentation + EnhancedInstance retInstance = (EnhancedInstance) ret; + String remotePeer = (String) objInst.getSkyWalkingDynamicField(); + if (LOGGER.isDebugEnable()) { + LOGGER.debug("Mark OperationExecutor remotePeer: {}", remotePeer); + } + retInstance.setSkyWalkingDynamicField(remotePeer); + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + // do nothing + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/MongoDBOperationExecutorInterceptor.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/MongoDBOperationExecutorInterceptor.java new file mode 100644 index 0000000000..dbdf7c6926 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/interceptor/MongoDBOperationExecutorInterceptor.java @@ -0,0 +1,64 @@ +/* + * 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.plugin.mongodb.v4.interceptor; + +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.logging.api.ILog; +import org.apache.skywalking.apm.agent.core.logging.api.LogManager; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.plugin.mongodb.v4.support.MongoSpanHelper; + +import java.lang.reflect.Method; + +@SuppressWarnings("Duplicates") +public class MongoDBOperationExecutorInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ILog LOGGER = LogManager.getLogger(MongoDBOperationExecutorInterceptor.class); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) { + String executeMethod = allArguments[0].getClass().getSimpleName(); + // OperationExecutor has included th remotePeer + // See: MongoDBClientDelegateInterceptor.afterMethod + String remotePeer = (String) objInst.getSkyWalkingDynamicField(); + if (LOGGER.isDebugEnable()) { + LOGGER.debug("Mongo execute: [executeMethod: {}, remotePeer: {}]", executeMethod, remotePeer); + } + MongoSpanHelper.createExitSpan(executeMethod, remotePeer, allArguments[0]); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) { + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + AbstractSpan activeSpan = ContextManager.activeSpan(); + activeSpan.log(t); + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoConstants.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoConstants.java new file mode 100644 index 0000000000..5c8da40b8d --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoConstants.java @@ -0,0 +1,32 @@ +/* + * 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.plugin.mongodb.v4.support; + +public class MongoConstants { + + private MongoConstants() { + } + + public static final String DB_TYPE = "MongoDB"; + + public static final String MONGO_DB_OP_PREFIX = "MongoDB/"; + + public static final String EMPTY = ""; + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoOperationHelper.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoOperationHelper.java new file mode 100644 index 0000000000..45d0481fbc --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoOperationHelper.java @@ -0,0 +1,143 @@ +/* + * 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.plugin.mongodb.v4.support; + +import com.mongodb.internal.bulk.DeleteRequest; +import com.mongodb.internal.bulk.InsertRequest; +import com.mongodb.internal.bulk.UpdateRequest; +import com.mongodb.internal.bulk.WriteRequest; +import com.mongodb.internal.operation.CountOperation; +import com.mongodb.internal.operation.CreateCollectionOperation; +import com.mongodb.internal.operation.CreateIndexesOperation; +import com.mongodb.internal.operation.CreateViewOperation; +import com.mongodb.internal.operation.DeleteOperation; +import com.mongodb.internal.operation.DistinctOperation; +import com.mongodb.internal.operation.FindAndDeleteOperation; +import com.mongodb.internal.operation.FindAndReplaceOperation; +import com.mongodb.internal.operation.FindAndUpdateOperation; +import com.mongodb.internal.operation.FindOperation; +import com.mongodb.internal.operation.InsertOperation; +import com.mongodb.internal.operation.ListCollectionsOperation; +import com.mongodb.internal.operation.MapReduceToCollectionOperation; +import com.mongodb.internal.operation.MapReduceWithInlineResultsOperation; +import com.mongodb.internal.operation.MixedBulkWriteOperation; +import com.mongodb.internal.operation.UpdateOperation; +import org.bson.BsonDocument; + +import java.util.List; + +@SuppressWarnings({ + "deprecation", + "Duplicates" +}) +public class MongoOperationHelper { + + private MongoOperationHelper() { + + } + + /** + * Convert ReadOperation interface or WriteOperation interface to the implementation class. Get the method name and + * filter info. + * @param obj operation + * @return result + */ + public static String getTraceParam(Object obj) { + if (obj instanceof CountOperation) { + BsonDocument filter = ((CountOperation) obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof DistinctOperation) { + BsonDocument filter = ((DistinctOperation) obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof FindOperation) { + BsonDocument filter = ((FindOperation) obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof ListCollectionsOperation) { + BsonDocument filter = ((ListCollectionsOperation) obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof MapReduceWithInlineResultsOperation) { + BsonDocument filter = ((MapReduceWithInlineResultsOperation) obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof DeleteOperation) { + List writeRequestList = ((DeleteOperation) obj).getDeleteRequests(); + return getFilter(writeRequestList); + } else if (obj instanceof InsertOperation) { + List writeRequestList = ((InsertOperation) obj).getInsertRequests(); + return getFilter(writeRequestList); + } else if (obj instanceof UpdateOperation) { + List writeRequestList = ((UpdateOperation) obj).getUpdateRequests(); + return getFilter(writeRequestList); + } else if (obj instanceof CreateCollectionOperation) { + String filter = ((CreateCollectionOperation) obj).getCollectionName(); + return limitFilter(filter); + } else if (obj instanceof CreateIndexesOperation) { + List filter = ((CreateIndexesOperation) obj).getIndexNames(); + return limitFilter(filter.toString()); + } else if (obj instanceof CreateViewOperation) { + String filter = ((CreateViewOperation) obj).getViewName(); + return limitFilter(filter); + } else if (obj instanceof FindAndDeleteOperation) { + BsonDocument filter = ((FindAndDeleteOperation) obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof FindAndReplaceOperation) { + BsonDocument filter = ((FindAndReplaceOperation) obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof FindAndUpdateOperation) { + BsonDocument filter = ((FindAndUpdateOperation) obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof MapReduceToCollectionOperation) { + BsonDocument filter = ((MapReduceToCollectionOperation) obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof MixedBulkWriteOperation) { + List writeRequestList = ((MixedBulkWriteOperation) obj).getWriteRequests(); + return getFilter(writeRequestList); + } else { + return MongoConstants.EMPTY; + } + } + + private static String getFilter(List writeRequestList) { + StringBuilder params = new StringBuilder(); + for (WriteRequest request : writeRequestList) { + if (request instanceof InsertRequest) { + params.append(((InsertRequest) request).getDocument().toString()).append(","); + } else if (request instanceof DeleteRequest) { + params.append(((DeleteRequest) request).getFilter()).append(","); + } else if (request instanceof UpdateRequest) { + params.append(((UpdateRequest) request).getFilter()).append(","); + } + final int filterLengthLimit = MongoPluginConfig.Plugin.MongoDB.FILTER_LENGTH_LIMIT; + if (filterLengthLimit > 0 && params.length() > filterLengthLimit) { + return params.substring(0, filterLengthLimit) + "..."; + } + } + return params.toString(); + } + + private static String limitFilter(String filter) { + final StringBuilder params = new StringBuilder(); + final int filterLengthLimit = MongoPluginConfig.Plugin.MongoDB.FILTER_LENGTH_LIMIT; + if (filterLengthLimit > 0 && filter.length() > filterLengthLimit) { + return params.append(filter, 0, filterLengthLimit).append("...").toString(); + } else { + return filter; + } + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoPluginConfig.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoPluginConfig.java new file mode 100644 index 0000000000..81f55852bb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoPluginConfig.java @@ -0,0 +1,42 @@ +/* + * 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.plugin.mongodb.v4.support; + +import org.apache.skywalking.apm.agent.core.boot.PluginConfig; + +public class MongoPluginConfig { + public static class Plugin { + @PluginConfig(root = MongoPluginConfig.class) + public static class MongoDB { + /** + * 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; + + /** + * For the sake of performance, SkyWalking won't save the entire parameters string into the tag, but only + * the first {@code FILTER_LENGTH_LIMIT} characters. + *

+ * Set a negative number to save the complete parameter string to the tag. + */ + public static int FILTER_LENGTH_LIMIT = 256; + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoRemotePeerHelper.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoRemotePeerHelper.java new file mode 100644 index 0000000000..1dcdb288f8 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoRemotePeerHelper.java @@ -0,0 +1,44 @@ +/* + * 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.plugin.mongodb.v4.support; + +import com.mongodb.ServerAddress; +import com.mongodb.connection.ServerDescription; +import com.mongodb.internal.connection.Cluster; + +@SuppressWarnings("deprecation") +public class MongoRemotePeerHelper { + + private MongoRemotePeerHelper() { + } + + /** + * + * @param cluster cluster + * @return result + */ + public static String getRemotePeer(Cluster cluster) { + StringBuilder peersBuilder = new StringBuilder(); + for (ServerDescription description : cluster.getDescription().getServerDescriptions()) { + ServerAddress address = description.getAddress(); + peersBuilder.append(address.getHost()).append(":").append(address.getPort()).append(";"); + } + return peersBuilder.substring(0, peersBuilder.length() - 1); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoSpanHelper.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoSpanHelper.java new file mode 100644 index 0000000000..c01953b0c4 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/mongodb/v4/support/MongoSpanHelper.java @@ -0,0 +1,50 @@ +/* + * 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.plugin.mongodb.v4.support; + +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.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +public class MongoSpanHelper { + + private MongoSpanHelper() { + } + + /** + * createExitSpan + * @param executeMethod executeMethod + * @param remotePeer remotePeer + * @param operation operation + */ + public static void createExitSpan(String executeMethod, String remotePeer, Object operation) { + AbstractSpan span = ContextManager.createExitSpan( + MongoConstants.MONGO_DB_OP_PREFIX + executeMethod, new ContextCarrier(), remotePeer); + span.setComponent(ComponentsDefine.MONGO_DRIVER); + Tags.DB_TYPE.set(span, MongoConstants.DB_TYPE); + SpanLayer.asDB(span); + + if (MongoPluginConfig.Plugin.MongoDB.TRACE_PARAM) { + Tags.DB_BIND_VARIABLES.set(span, MongoOperationHelper.getTraceParam(operation)); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..a019f7c913 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,19 @@ +# 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. + +# v4.0.0~ +mongodb-4.x=org.apache.skywalking.apm.plugin.mongodb.v4.define.MongoDBClientDelegateInstrumentation +mongodb-4.x=org.apache.skywalking.apm.plugin.mongodb.v4.define.MongoDBOperationExecutorInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBClientDelegateInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBClientDelegateInterceptorTest.java new file mode 100644 index 0000000000..16c2e31ed5 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBClientDelegateInterceptorTest.java @@ -0,0 +1,72 @@ +/* + * 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.plugin.mongodb.v4; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.plugin.mongodb.v4.interceptor.MongoDBClientDelegateInterceptor; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +public class MongoDBClientDelegateInterceptorTest { + + private MongoDBClientDelegateInterceptor interceptor; + + @Mock + private EnhancedInstance clientDelegateEnhancedInstance; + + private EnhancedInstance retEnhancedInstance; + + private final static String REMOTE_PEER = "127.0.0.1:27017"; + + @Before + public void setUp() { + interceptor = new MongoDBClientDelegateInterceptor(); + retEnhancedInstance = new FieldEnhancedInstance(); + when(clientDelegateEnhancedInstance.getSkyWalkingDynamicField()).thenReturn(REMOTE_PEER); + } + + @Test + public void testAfterMethod() { + interceptor.afterMethod(clientDelegateEnhancedInstance, null, null, null, retEnhancedInstance); + Assert.assertEquals(REMOTE_PEER, retEnhancedInstance.getSkyWalkingDynamicField()); + } + + private static class FieldEnhancedInstance implements EnhancedInstance { + + private Object skyWalkingDynamicField; + + @Override + public Object getSkyWalkingDynamicField() { + return skyWalkingDynamicField; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.skyWalkingDynamicField = value; + } + } + +} diff --git a/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBOperationExecutorInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBOperationExecutorInterceptorTest.java new file mode 100644 index 0000000000..d5dc3e57f5 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/mongodb-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/mongodb/v4/MongoDBOperationExecutorInterceptorTest.java @@ -0,0 +1,141 @@ +/* + * 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.plugin.mongodb.v4; + +import com.mongodb.MongoNamespace; +import com.mongodb.ReadConcern; +import com.mongodb.client.internal.OperationExecutor; +import com.mongodb.internal.operation.FindOperation; +import com.mongodb.internal.operation.WriteOperation; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.LogDataEntity; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.context.util.TagValuePair; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.helper.SpanHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.plugin.mongodb.v4.interceptor.MongoDBOperationExecutorInterceptor; +import org.apache.skywalking.apm.plugin.mongodb.v4.support.MongoPluginConfig; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.codecs.Decoder; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +import java.lang.reflect.Method; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class MongoDBOperationExecutorInterceptorTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + @Mock + private EnhancedInstance enhancedInstance; + + private MongoDBOperationExecutorInterceptor interceptor; + + private Object[] arguments; + + private Class[] argumentTypes; + + @Before + public void setUp() { + + interceptor = new MongoDBOperationExecutorInterceptor(); + + MongoPluginConfig.Plugin.MongoDB.TRACE_PARAM = true; + + when(enhancedInstance.getSkyWalkingDynamicField()).thenReturn("127.0.0.1:27017"); + + BsonDocument document = new BsonDocument(); + document.append("name", new BsonString("by")); + MongoNamespace mongoNamespace = new MongoNamespace("test.user"); + Decoder decoder = PowerMockito.mock(Decoder.class); + FindOperation findOperation = new FindOperation(mongoNamespace, decoder); + findOperation.filter(document); + + arguments = new Object[] {findOperation}; + argumentTypes = new Class[] {findOperation.getClass()}; + } + + @Test + public void testIntercept() throws Throwable { + interceptor.beforeMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + interceptor.afterMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + + MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertMongoSpan(spans.get(0)); + } + + @Test + public void testInterceptWithException() throws Throwable { + interceptor.beforeMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + interceptor.handleMethodException( + enhancedInstance, getMethod(), arguments, argumentTypes, new RuntimeException()); + interceptor.afterMethod(enhancedInstance, getMethod(), arguments, argumentTypes, null); + + MatcherAssert.assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertMongoSpan(spans.get(0)); + List logDataEntities = SpanHelper.getLogs(spans.get(0)); + assertThat(logDataEntities.size(), is(1)); + SpanAssert.assertException(logDataEntities.get(0), RuntimeException.class); + } + + private void assertMongoSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), is("MongoDB/FindOperation")); + assertThat(SpanHelper.getComponentId(span), is(42)); + List tags = SpanHelper.getTags(span); + assertThat(tags.get(1).getValue(), is("{\"name\": \"by\"}")); + assertThat(tags.get(0).getValue(), is("MongoDB")); + assertThat(span.isExit(), is(true)); + assertThat(SpanHelper.getLayer(span), CoreMatchers.is(SpanLayer.DB)); + } + + private Method getMethod() throws Exception { + return OperationExecutor.class.getMethod("execute", WriteOperation.class, ReadConcern.class); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index a28cc63344..78e66961e5 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -36,6 +36,7 @@ tomcat-7.x-8.x-plugin motan-plugin mongodb-3.x-plugin + mongodb-4.x-plugin feign-default-http-9.x-plugin okhttp-3.x-plugin spring-plugins diff --git a/docs/en/setup/service-agent/java-agent/Plugin-list.md b/docs/en/setup/service-agent/java-agent/Plugin-list.md index b8974e5fa1..4d4c9fcd98 100644 --- a/docs/en/setup/service-agent/java-agent/Plugin-list.md +++ b/docs/en/setup/service-agent/java-agent/Plugin-list.md @@ -41,6 +41,7 @@ - memcache-2.x - mongodb-2.x - mongodb-3.x +- mongodb-4.x - motan-0.x - mysql-5.x - mysql-6.x diff --git a/docs/en/setup/service-agent/java-agent/Supported-list.md b/docs/en/setup/service-agent/java-agent/Supported-list.md index b37f853e3e..bac5bd4490 100644 --- a/docs/en/setup/service-agent/java-agent/Supported-list.md +++ b/docs/en/setup/service-agent/java-agent/Supported-list.md @@ -58,7 +58,7 @@ * [Jedis](https://github.com/xetorthio/jedis) 2.x * [Redisson](https://github.com/redisson/redisson) Easy Java Redis client 3.5.2+ * [Lettuce](https://github.com/lettuce-io/lettuce-core) 5.x - * [MongoDB Java Driver](https://github.com/mongodb/mongo-java-driver) 2.13-2.14, 3.4.0-3.11.1 + * [MongoDB Java Driver](https://github.com/mongodb/mongo-java-driver) 2.13-2.14, 3.4.0-3.11.1, 4.0.0-4.1.0 * Memcached Client * [Spymemcached](https://github.com/couchbase/spymemcached) 2.x * [Xmemcached](https://github.com/killme2008/xmemcached) 2.x diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/bin/startup.sh b/test/plugin/scenarios/mongodb-4.x-scenario/bin/startup.sh new file mode 100644 index 0000000000..df07bc1934 --- /dev/null +++ b/test/plugin/scenarios/mongodb-4.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} -Dskywalking.plugin.mongodb.trace_param=true ${home}/../libs/mongodb-4.x-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/mongodb-4.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..78d77b411e --- /dev/null +++ b/test/plugin/scenarios/mongodb-4.x-scenario/config/expectedData.yaml @@ -0,0 +1,141 @@ +# 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. +segmentItems: + - serviceName: mongodb-4.x-scenario + segmentSize: ge 2 + segments: + - segmentId: not null + spans: + - operationName: MongoDB/CreateCollectionOperation + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.bind_vars, value: 'testCollection'} + skipAnalysis: 'false' + - operationName: MongoDB/MixedBulkWriteOperation + operationId: 0 + parentSpanId: 0 + spanId: 2 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.bind_vars, value: not null} + skipAnalysis: 'false' + - operationName: MongoDB/FindOperation + operationId: 0 + parentSpanId: 0 + spanId: 3 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.bind_vars, value: '{"name": "org"}'} + skipAnalysis: 'false' + - operationName: MongoDB/MixedBulkWriteOperation + operationId: 0 + parentSpanId: 0 + spanId: 4 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.bind_vars, value: '{"name": "org"},'} + skipAnalysis: 'false' + - operationName: MongoDB/FindOperation + operationId: 0 + parentSpanId: 0 + spanId: 5 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.bind_vars, value: '{"name": "testA"}'} + skipAnalysis: 'false' + - operationName: MongoDB/MixedBulkWriteOperation + operationId: 0 + parentSpanId: 0 + spanId: 6 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.bind_vars, value: '{"id": "1"},'} + skipAnalysis: 'false' + - operationName: MongoDB/DropDatabaseOperation + operationId: 0 + parentSpanId: 0 + spanId: 7 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 42 + isError: false + spanType: Exit + peer: mongodb-server:27017 + tags: + - {key: db.type, value: MongoDB} + - {key: db.bind_vars, value: null} + skipAnalysis: 'false' + - operationName: /mongodb-4.x-scenario/case/mongodb-4.x-scenario + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - {key: url, value: 'http://localhost:8080/mongodb-4.x-scenario/case/mongodb-4.x-scenario'} + - {key: http.method, value: GET} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/configuration.yml b/test/plugin/scenarios/mongodb-4.x-scenario/configuration.yml new file mode 100644 index 0000000000..47e10eaaab --- /dev/null +++ b/test/plugin/scenarios/mongodb-4.x-scenario/configuration.yml @@ -0,0 +1,24 @@ +# 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. + +type: jvm +entryService: http://localhost:8080/mongodb-4.x-scenario/case/mongodb-4.x-scenario +healthCheck: http://localhost:8080/mongodb-4.x-scenario/case/healthCheck +startScript: ./bin/startup.sh +dependencies: + mongodb-server: + image: mongo:4.2 + hostname: mongodb-server diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/pom.xml b/test/plugin/scenarios/mongodb-4.x-scenario/pom.xml new file mode 100644 index 0000000000..2552518f84 --- /dev/null +++ b/test/plugin/scenarios/mongodb-4.x-scenario/pom.xml @@ -0,0 +1,145 @@ + + + + + org.apache.skywalking.apm.testcase + mongodb-4.x-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + 4.0.0 + 2.1.6.RELEASE + 1.18.10 + + + skywalking-mongodb-4.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot-version} + pom + import + + + org.mongodb + mongodb-driver-core + ${test.framework.version} + + + org.mongodb + mongodb-driver-sync + ${test.framework.version} + + + org.mongodb + bson + ${test.framework.version} + + + + + + + org.mongodb + mongodb-driver-core + + + org.mongodb + mongodb-driver-sync + + + org.mongodb + bson + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + mongodb-4.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + maven-compiler-plugin + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..01b0eb0118 --- /dev/null +++ b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/mongodb-4.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/Application.java b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/Application.java new file mode 100644 index 0000000000..1dd56fec23 --- /dev/null +++ b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/Application.java @@ -0,0 +1,34 @@ +/* + * 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.testcase.mongodb; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception e) { + // Never do this + } + } +} diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java new file mode 100644 index 0000000000..298ac22916 --- /dev/null +++ b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/mongodb/controller/CaseController.java @@ -0,0 +1,77 @@ +/* + * 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.testcase.mongodb.controller; + +import com.mongodb.client.*; +import org.bson.BsonDocument; +import org.bson.Document; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.mongodb.client.model.Filters.eq; + +@RestController +@RequestMapping("/case") +public class CaseController { + + @Value(value = "${mongodb.uri}") + private String connectionString; + + @GetMapping("/healthCheck") + public String health() { + // check connect to mongodb server + try (MongoClient mongoClient = MongoClients.create(connectionString)) { + return "success"; + } + } + + @RequestMapping("/mongodb-4.x-scenario") + public String mongoDBCase() { + try (MongoClient mongoClient = MongoClients.create(connectionString)) { + MongoDatabase db = mongoClient.getDatabase("test-database"); + // CreateCollectionOperation + db.createCollection("testCollection"); + + MongoCollection collection = db.getCollection("testCollection"); + Document document = Document.parse("{id: 1, name: \"test\"}"); + // MixedBulkWriteOperation + collection.insertOne(document); + + // FindOperation + FindIterable findIterable = collection.find(eq("name", "org")); + findIterable.first(); + + // MixedBulkWriteOperation + collection.updateOne(eq("name", "org"), BsonDocument.parse("{ $set : { \"name\": \"testA\"} }")); + + // FindOperation + findIterable = collection.find(eq("name", "testA")); + findIterable.first(); + + // MixedBulkWriteOperation + collection.deleteOne(eq("id", "1")); + + // DropDatabaseOperation + mongoClient.getDatabase("test-database").drop(); + } + return "success"; + } +} diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000..200fb5d3de --- /dev/null +++ b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/resources/application.yaml @@ -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. +# +# +server: + port: 8080 + servlet: + context-path: /mongodb-4.x-scenario +logging: + config: classpath:log4j2.xml +mongodb: + uri: mongodb://mongodb-server/test \ No newline at end of file diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/mongodb-4.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/mongodb-4.x-scenario/support-version.list b/test/plugin/scenarios/mongodb-4.x-scenario/support-version.list new file mode 100644 index 0000000000..53a8936935 --- /dev/null +++ b/test/plugin/scenarios/mongodb-4.x-scenario/support-version.list @@ -0,0 +1,23 @@ +# 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. + +4.0.0 +4.0.1 +4.0.2 +4.0.3 +4.0.4 +4.0.5 +4.1.0 \ No newline at end of file