diff --git a/.github/workflows/plugins-test.3.yaml b/.github/workflows/plugins-test.3.yaml index d74d22a4b..2efa6ff60 100644 --- a/.github/workflows/plugins-test.3.yaml +++ b/.github/workflows/plugins-test.3.yaml @@ -77,6 +77,7 @@ jobs: - dbcp-2.x-scenario - jsonrpc4j-1.x-scenario - gateway-3.x-scenario + - neo4j-4.x-scenario steps: - uses: actions/checkout@v2 with: diff --git a/CHANGES.md b/CHANGES.md index 62b3e03af..3d1d1653d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Release Notes. * Agent supports the collection of JVM arguments and jar dependency information. * [Temporary] Support authentication for log report channel. This feature and grpc channel is going to be removed after Satellite 0.2.0 release. * Remove deprecated gRPC method, `io.grpc.ManagedChannelBuilder#nameResolverFactory`. See [gRPC-java 7133](https://github.com/grpc/grpc-java/issues/7133) for more details. +* Add `Neo4j-4.x` plugin. #### OAP-Backend * Disable Spring sleuth meter analyzer by default. diff --git a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java index 79dda4eb2..e3fb4f9c6 100755 --- a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java +++ b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java @@ -200,4 +200,6 @@ public class ComponentsDefine { public static final OfficialComponent SEATA = new OfficialComponent(108, "Seata"); public static final OfficialComponent MYBATIS = new OfficialComponent(109, "MyBatis"); + + public static final OfficialComponent NEO4J = new OfficialComponent(112, "Neo4j"); } diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/pom.xml new file mode 100644 index 000000000..8ca163d0e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/pom.xml @@ -0,0 +1,47 @@ + + + + + + apm-sdk-plugin + org.apache.skywalking + 8.7.0-SNAPSHOT + + 4.0.0 + + apm-neo4j-4.x-plugin + jar + neo4j-4.x-plugin + + + 4.2.5 + + + + + org.neo4j.driver + neo4j-java-driver + ${neo4j.version} + provided + + + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/Neo4jPluginConfig.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/Neo4jPluginConfig.java new file mode 100644 index 000000000..474cb19c2 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/Neo4jPluginConfig.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.neo4j.v4x; + +import org.apache.skywalking.apm.agent.core.boot.PluginConfig; + +public class Neo4jPluginConfig { + + public static class Plugin { + + @PluginConfig(root = Neo4jPluginConfig.class) + public static class Neo4j { + + /** + * If set to true, the parameters of the cypher would be collected. + */ + public static boolean TRACE_CYPHER_PARAMETERS = false; + /** + * For the sake of performance, SkyWalking won't save the entire parameters string into the tag, but only + * the first {@code CYPHER_PARAMETERS_MAX_LENGTH} characters. + *

+ * Set a negative number to save the complete parameter string to the tag. + */ + public static int CYPHER_PARAMETERS_MAX_LENGTH = 512; + /** + * For the sake of performance, SkyWalking won't save the entire sql body into the tag, but only the first + * {@code CYPHER_BODY_MAX_LENGTH} characters. + *

+ * Set a negative number to save the complete sql body to the tag. + */ + public static int CYPHER_BODY_MAX_LENGTH = 2048; + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/Neo4jPluginConstants.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/Neo4jPluginConstants.java new file mode 100644 index 000000000..70734cb75 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/Neo4jPluginConstants.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.neo4j.v4x; + +import org.apache.skywalking.apm.agent.core.context.tag.StringTag; + +/** + * Constants for neo4j plugin + */ +public final class Neo4jPluginConstants { + + public static final String EMPTY_STRING = ""; + public static final String DB_TYPE = "Neo4j"; + public static final StringTag CYPHER_PARAMETERS_TAG = new StringTag("db.cypher.parameters"); + +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionAcquireConnectionInterceptor.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionAcquireConnectionInterceptor.java new file mode 100644 index 000000000..3cb9be33f --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionAcquireConnectionInterceptor.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.plugin.neo4j.v4x; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.NEO4J; +import static org.apache.skywalking.apm.plugin.neo4j.v4x.Neo4jPluginConstants.DB_TYPE; + +import java.lang.reflect.Method; +import java.util.concurrent.CompletionStage; +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.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.neo4j.driver.internal.spi.Connection; + +/** + * This interceptor is used to enhance {@link org.neo4j.driver.internal.async.NetworkSession#acquireConnection} method, + * which is used to obtain connection information and save it in skywalking dynamic field. + */ +public class SessionAcquireConnectionInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + SessionRequiredInfo requiredInfo = (SessionRequiredInfo) objInst.getSkyWalkingDynamicField(); + if (requiredInfo == null) { + return; + } + + final AbstractSpan span = ContextManager.createExitSpan("Neo4j", "Unset"); + Tags.DB_TYPE.set(span, DB_TYPE); + span.setComponent(NEO4J); + SpanLayer.asDB(span); + ContextManager.continued(requiredInfo.getContextSnapshot()); + span.prepareForAsync(); + ContextManager.stopSpan(); + requiredInfo.setSpan(span); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + SessionRequiredInfo requiredInfo = (SessionRequiredInfo) objInst.getSkyWalkingDynamicField(); + if (requiredInfo == null) { + return ret; + } + + CompletionStage connectionStage = (CompletionStage) ret; + return connectionStage.thenApply(connection -> { + if (connection == null) { + return null; + } + + try { + final AbstractSpan span = requiredInfo.getSpan(); + span.setPeer(connection.serverAddress().toString()); + Tags.DB_INSTANCE + .set(span, connection.databaseName().databaseName().orElse(Neo4jPluginConstants.EMPTY_STRING)); + } catch (Exception e) { + // ignore + } + + return connection; + }); + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionBeginTransactionInterceptor.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionBeginTransactionInterceptor.java new file mode 100644 index 000000000..f2986eb85 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionBeginTransactionInterceptor.java @@ -0,0 +1,63 @@ +/* + * 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.neo4j.v4x; + +import java.lang.reflect.Method; +import java.util.concurrent.CompletionStage; +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; + +/** + * This interceptor is used to propagate the context snapshot in {@link SessionRequiredInfo} after {@link + * org.neo4j.driver.internal.async.NetworkSession#beginTransactionAsync} + * method is invoked. + */ +public class SessionBeginTransactionInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + SessionRequiredInfo requiredInfo = (SessionRequiredInfo) objInst.getSkyWalkingDynamicField(); + if (requiredInfo == null) { + return ret; + } + + CompletionStage transactionStage = (CompletionStage) ret; + return transactionStage.thenApply(transaction -> { + if (transaction == null) { + return null; + } + + transaction.setSkyWalkingDynamicField(requiredInfo); + return transaction; + }); + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionConstructorInterceptor.java new file mode 100644 index 000000000..d11894913 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionConstructorInterceptor.java @@ -0,0 +1,38 @@ +/* + * 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.neo4j.v4x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; + +/** + * This constructor interceptor is used to create {@link SessionRequiredInfo} object and save it in skywalking dynamic + * field. + */ +public class SessionConstructorInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable { + SessionRequiredInfo requiredInfo = new SessionRequiredInfo(); + requiredInfo.setContextSnapshot(ContextManager.capture()); + + objInst.setSkyWalkingDynamicField(requiredInfo); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionRequiredInfo.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionRequiredInfo.java new file mode 100644 index 000000000..de077c8af --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionRequiredInfo.java @@ -0,0 +1,47 @@ +/* + * 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.neo4j.v4x; + +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; + +/** + * Cache session connection information, like database name and host and so on. + */ +public class SessionRequiredInfo { + + private AbstractSpan span; + private ContextSnapshot contextSnapshot; + + public ContextSnapshot getContextSnapshot() { + return contextSnapshot; + } + + public void setContextSnapshot(ContextSnapshot contextSnapshot) { + this.contextSnapshot = contextSnapshot; + } + + public AbstractSpan getSpan() { + return span; + } + + public void setSpan(AbstractSpan span) { + this.span = span; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionRunInterceptor.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionRunInterceptor.java new file mode 100644 index 000000000..aa255eb60 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionRunInterceptor.java @@ -0,0 +1,79 @@ +/* + * 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.neo4j.v4x; + +import java.lang.reflect.Method; +import java.util.concurrent.CompletionStage; +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.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.neo4j.v4x.Neo4jPluginConfig.Plugin.Neo4j; +import org.apache.skywalking.apm.plugin.neo4j.v4x.util.CypherUtils; +import org.neo4j.driver.Query; + +/** + * This interceptor do the following steps: + *

+ * 1. Create exit span before method, and set related tags.
+ * 2. Call {@link AbstractSpan#prepareForAsync()} and {@link ContextManager#stopSpan()} method.
+ * 3. Save span into {@link SessionRequiredInfo} object.
+ * 4. Return a new CompletionStage after method and async finish the span.
+ * 
+ */ +public class SessionRunInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + return ((CompletionStage) ret).thenApply(resultCursor -> { + Query query = (Query) allArguments[0]; + SessionRequiredInfo requiredInfo = (SessionRequiredInfo) objInst.getSkyWalkingDynamicField(); + if (query == null || requiredInfo == null || requiredInfo.getSpan() == null) { + return resultCursor; + } + + final AbstractSpan span = requiredInfo.getSpan(); + span.setOperationName("Neo4j/Session/" + method.getName()); + Tags.DB_STATEMENT.set(span, CypherUtils.limitBodySize(query.text())); + if (Neo4j.TRACE_CYPHER_PARAMETERS) { + Neo4jPluginConstants.CYPHER_PARAMETERS_TAG + .set(span, CypherUtils.limitParametersSize(query.parameters().toString())); + } + span.asyncFinish(); + return resultCursor; + }); + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + SessionRequiredInfo requiredInfo = (SessionRequiredInfo) objInst.getSkyWalkingDynamicField(); + if (requiredInfo != null) { + requiredInfo.getSpan().log(t); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/TransactionRunInterceptor.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/TransactionRunInterceptor.java new file mode 100644 index 000000000..0607f77d4 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/TransactionRunInterceptor.java @@ -0,0 +1,81 @@ +/* + * 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.neo4j.v4x; + +import java.lang.reflect.Method; +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.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.neo4j.v4x.Neo4jPluginConfig.Plugin.Neo4j; +import org.apache.skywalking.apm.plugin.neo4j.v4x.util.CypherUtils; +import org.neo4j.driver.Query; + +/** + * This interceptor do the following steps: + *
+ * 1. Create exit span before method, and set related tags.
+ * 2. Call {@link AbstractSpan#prepareForAsync()} and {@link ContextManager#stopSpan()} method.
+ * 3. Save span into skywalking dynamic field.
+ * 4. Return a new CompletionStage after method and async finish the span.
+ * 
+ */ +public class TransactionRunInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + final Query query = (Query) allArguments[0]; + SessionRequiredInfo requiredInfo = (SessionRequiredInfo) objInst.getSkyWalkingDynamicField(); + if (query == null || requiredInfo == null || requiredInfo.getSpan() == null) { + return; + } + + final AbstractSpan span = requiredInfo.getSpan(); + span.setOperationName("Neo4j/Transaction/" + method.getName()); + Tags.DB_STATEMENT.set(span, CypherUtils.limitBodySize(query.text())); + if (Neo4j.TRACE_CYPHER_PARAMETERS) { + Neo4jPluginConstants.CYPHER_PARAMETERS_TAG + .set(span, CypherUtils.limitParametersSize(query.parameters().toString())); + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + SessionRequiredInfo requiredInfo = (SessionRequiredInfo) objInst.getSkyWalkingDynamicField(); + if (requiredInfo == null || requiredInfo.getSpan() == null) { + return ret; + } + + requiredInfo.getSpan().asyncFinish(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + AbstractSpan span = ((SessionRequiredInfo) objInst.getSkyWalkingDynamicField()).getSpan(); + if (span != null) { + span.log(t); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/define/NetworkSessionInstrumentation.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/define/NetworkSessionInstrumentation.java new file mode 100644 index 000000000..f50685493 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/define/NetworkSessionInstrumentation.java @@ -0,0 +1,135 @@ +/* + * 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.neo4j.v4x.define; + +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; + +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; + +/** + * This instrumentation enhanced {@link org.neo4j.driver.internal.async.NetworkSession} class used at auto-commit + * transactions scenario. + *

+ * {@link org.neo4j.driver.internal.async.NetworkSession} class is used by {@link org.neo4j.driver.internal.InternalSession},{@link + * org.neo4j.driver.internal.async.InternalAsyncSession}, + * and {@link org.neo4j.driver.internal.reactive.InternalRxSession} to send db query and enhance `runAsync()` and + * `runRx()` method should be more generally. + *

+ *

+ * `acquireConnection()` method is used for getting connection information. + *

+ */ +public class NetworkSessionInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private final static String ENHANCED_CLASS = "org.neo4j.driver.internal.async.NetworkSession"; + private final static String RUN_ASYNC_METHOD_NAME = "runAsync"; + private final static String RUN_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.neo4j.v4x.SessionRunInterceptor"; + private final static String RUN_RX_METHOD_NAME = "runRx"; + private final static String ACQUIRE_CONNECTION_METHOD_NAME = "acquireConnection"; + private final static String ACQUIRE_CONNECTION_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.neo4j.v4x.SessionAcquireConnectionInterceptor"; + private final static String CONSTRUCTOR_INTERCEPTOR = "org.apache.skywalking.apm.plugin.neo4j.v4x.SessionConstructorInterceptor"; + private final static String CONSTRUCTOR_ARGUMENT_TYPE = "org.neo4j.driver.internal.DatabaseName"; + private final static String BEGIN_TRANSACTION_METHOD_NAME = "beginTransactionAsync"; + private final static String BEGIN_TRANSACTION_ARGUMENT_TYPE = "org.neo4j.driver.AccessMode"; + private final static String BEGIN_TRANSACTION_INTERCEPTOR = "org.apache.skywalking.apm.plugin.neo4j.v4x.SessionBeginTransactionInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCED_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArgumentWithType(2, CONSTRUCTOR_ARGUMENT_TYPE); + } + + @Override + public String getConstructorInterceptor() { + return CONSTRUCTOR_INTERCEPTOR; + } + } + }; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(RUN_ASYNC_METHOD_NAME).or(named(RUN_RX_METHOD_NAME)); + } + + @Override + public String getMethodsInterceptor() { + return RUN_METHOD_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(ACQUIRE_CONNECTION_METHOD_NAME); + } + + @Override + public String getMethodsInterceptor() { + return ACQUIRE_CONNECTION_METHOD_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(BEGIN_TRANSACTION_METHOD_NAME) + .and(takesArgumentWithType(0, BEGIN_TRANSACTION_ARGUMENT_TYPE)); + } + + @Override + public String getMethodsInterceptor() { + return BEGIN_TRANSACTION_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/define/UnmanagedTransactionInstrumentation.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/define/UnmanagedTransactionInstrumentation.java new file mode 100644 index 000000000..1b46575a4 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/define/UnmanagedTransactionInstrumentation.java @@ -0,0 +1,79 @@ +/* + * 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.neo4j.v4x.define; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +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; + +/** + * This instrumentation enhanced {@link org.neo4j.driver.internal.async.UnmanagedTransaction} class used at Transaction + * functions scenario. + *

+ * Simple session and async session use {@link org.neo4j.driver.internal.async.UnmanagedTransaction#runAsync} method + * and + * reactive session uses {@link org.neo4j.driver.internal.async.UnmanagedTransaction#runRx} method to execute sql + * statement. + *

+ */ +public class UnmanagedTransactionInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private final static String ENHANCED_CLASS = "org.neo4j.driver.internal.async.UnmanagedTransaction"; + private final static String RUN_ASYNC_METHOD_NAME = "runAsync"; + private final static String TRANSACTION_RUN_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.neo4j.v4x.TransactionRunInterceptor"; + private final static String RUN_RX_METHOD_NAME = "runRx"; + + @Override + protected ClassMatch enhanceClass() { + return byName(ENHANCED_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named(RUN_ASYNC_METHOD_NAME).or(named(RUN_RX_METHOD_NAME)); + } + + @Override + public String getMethodsInterceptor() { + return TRANSACTION_RUN_METHOD_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/util/CypherUtils.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/util/CypherUtils.java new file mode 100644 index 000000000..a6093875b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/neo4j/v4x/util/CypherUtils.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.neo4j.v4x.util; + +import org.apache.skywalking.apm.plugin.neo4j.v4x.Neo4jPluginConfig.Plugin.Neo4j; +import org.apache.skywalking.apm.plugin.neo4j.v4x.Neo4jPluginConstants; + +/** + * Cypher language utils + */ +public final class CypherUtils { + + /** + * Limit cypher body according to {@link Neo4j#CYPHER_BODY_MAX_LENGTH} + * + * @param body cypher query body + * @return limited body + */ + public static String limitBodySize(String body) { + if (body == null) { + return Neo4jPluginConstants.EMPTY_STRING; + } + + if (Neo4j.CYPHER_BODY_MAX_LENGTH > 0 && body.length() > Neo4j.CYPHER_BODY_MAX_LENGTH) { + return body.substring(0, Neo4j.CYPHER_BODY_MAX_LENGTH) + "..."; + } + + return body; + } + + /** + * Limit cypher query parameters size according to {@link Neo4j#CYPHER_PARAMETERS_MAX_LENGTH} + * + * @param parameters cypher query parameters + * @return limited parameters + */ + public static String limitParametersSize(String parameters) { + if (parameters == null) { + return Neo4jPluginConstants.EMPTY_STRING; + } + + if (Neo4j.CYPHER_PARAMETERS_MAX_LENGTH > 0 && parameters.length() > Neo4j.CYPHER_PARAMETERS_MAX_LENGTH) { + return parameters.substring(0, Neo4j.CYPHER_PARAMETERS_MAX_LENGTH) + "..."; + } + + return parameters; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..cbff4046a --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,18 @@ +# 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. + +neo4j-4.x=org.apache.skywalking.apm.plugin.neo4j.v4x.define.UnmanagedTransactionInstrumentation +neo4j-4.x=org.apache.skywalking.apm.plugin.neo4j.v4x.define.NetworkSessionInstrumentation diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/MockMethod.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/MockMethod.java new file mode 100644 index 000000000..1974c40c3 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/MockMethod.java @@ -0,0 +1,26 @@ +/* + * 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.neo4j.v4x; + +public class MockMethod { + + public void runAsync() { + + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/MockUnmanagedTransaction.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/MockUnmanagedTransaction.java new file mode 100644 index 000000000..26eba365e --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/MockUnmanagedTransaction.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.neo4j.v4x; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.spi.Connection; + +public class MockUnmanagedTransaction extends UnmanagedTransaction implements EnhancedInstance { + + private Object dynamicField; + + public MockUnmanagedTransaction(Connection connection, + BookmarkHolder bookmarkHolder, long fetchSize) { + super(connection, bookmarkHolder, fetchSize); + } + + @Override + public Object getSkyWalkingDynamicField() { + return dynamicField; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.dynamicField = value; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionAcquireConnectionInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionAcquireConnectionInterceptorTest.java new file mode 100644 index 000000000..3a04f8721 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionAcquireConnectionInterceptorTest.java @@ -0,0 +1,106 @@ +/* + * 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.neo4j.v4x; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.powermock.api.mockito.PowerMockito.when; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.apache.skywalking.apm.agent.core.context.MockContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +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.TracingSegmentRunner; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.spi.Connection; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class SessionAcquireConnectionInterceptorTest { + + private final EnhancedInstance enhancedInstance = new EnhancedInstance() { + private Object value; + + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + }; + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @SegmentStoragePoint + private SegmentStorage segmentStorage; + private SessionAcquireConnectionInterceptor interceptor; + @Mock + private Connection connection; + @Mock + private DatabaseName databaseName; + @Mock + private BoltServerAddress boltServerAddress; + + @Before + public void setUp() throws Exception { + when(connection.databaseName()).thenReturn(databaseName); + when(connection.serverAddress()).thenReturn(boltServerAddress); + when(databaseName.databaseName()).thenReturn(Optional.of("neo4j")); + when(boltServerAddress.toString()).thenReturn("127.0.0.1:7687"); + interceptor = new SessionAcquireConnectionInterceptor(); + SessionRequiredInfo requiredInfo = new SessionRequiredInfo(); + requiredInfo.setContextSnapshot(MockContextSnapshot.INSTANCE.mockContextSnapshot()); + enhancedInstance.setSkyWalkingDynamicField(requiredInfo); + } + + @Test + @SuppressWarnings("unchecked") + public void test() throws Throwable { + interceptor.beforeMethod(enhancedInstance, null, null, null, null); + final CompletionStage stage = (CompletionStage) interceptor + .afterMethod(enhancedInstance, null, null, null, CompletableFuture.completedFuture(connection)); + stage.whenComplete((connection1, throwable) -> { + SessionRequiredInfo requiredInfo = (SessionRequiredInfo) enhancedInstance + .getSkyWalkingDynamicField(); + assertNotNull(requiredInfo); + assertNotNull(requiredInfo.getContextSnapshot()); + assertNotNull(requiredInfo.getSpan()); + final List traceSegments = segmentStorage.getTraceSegments(); + assertNotNull(traceSegments); + assertThat(traceSegments.size(), is(0)); + }); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionBeginTransactionInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionBeginTransactionInterceptorTest.java new file mode 100644 index 000000000..f11febde0 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionBeginTransactionInterceptorTest.java @@ -0,0 +1,103 @@ +/* + * 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.neo4j.v4x; + +import static org.junit.Assert.assertNull; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.apache.skywalking.apm.agent.core.context.MockContextSnapshot; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@SuppressWarnings("unchecked") +public class SessionBeginTransactionInterceptorTest { + + private SessionBeginTransactionInterceptor interceptor; + private EnhancedInstance enhancedInstance; + + @Before + public void setUp() throws Exception { + interceptor = new SessionBeginTransactionInterceptor(); + enhancedInstance = new EnhancedInstance() { + private SessionRequiredInfo sessionRequiredInfo; + + @Override + public Object getSkyWalkingDynamicField() { + return sessionRequiredInfo; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.sessionRequiredInfo = (SessionRequiredInfo) value; + } + }; + } + + @Test + public void testWithoutSessionRequiredInfo() throws Throwable { + final CompletionStage result = (CompletionStage) interceptor + .afterMethod(enhancedInstance, null, null, null, CompletableFuture.completedFuture( + new EnhancedInstance() { + private Object value; + + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + })); + final EnhancedInstance ret = result.toCompletableFuture().get(); + assertNull(ret.getSkyWalkingDynamicField()); + } + + @Test + public void test() throws Throwable { + SessionRequiredInfo sessionRequiredInfo = new SessionRequiredInfo(); + sessionRequiredInfo.setContextSnapshot(MockContextSnapshot.INSTANCE.mockContextSnapshot()); + enhancedInstance.setSkyWalkingDynamicField(sessionRequiredInfo); + CompletionStage retStage = (CompletionStage) interceptor + .afterMethod(enhancedInstance, null, null, null, CompletableFuture.completedFuture( + new EnhancedInstance() { + private Object value; + + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + })); + final EnhancedInstance ret = retStage.toCompletableFuture().get(); + Assert.assertEquals(((SessionRequiredInfo) ret.getSkyWalkingDynamicField()).getContextSnapshot(), + sessionRequiredInfo.getContextSnapshot()); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionConstructorInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionConstructorInterceptorTest.java new file mode 100644 index 000000000..b617dc9c7 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionConstructorInterceptorTest.java @@ -0,0 +1,76 @@ +/* + * 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.neo4j.v4x; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; + +import java.util.Optional; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.MockContextSnapshot; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.neo4j.driver.internal.DatabaseName; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ContextManager.class}) +public class SessionConstructorInterceptorTest { + + private SessionConstructorInterceptor interceptor; + private EnhancedInstance enhancedInstance; + @Mock + private DatabaseName databaseName; + + @Before + public void setUp() throws Exception { + interceptor = new SessionConstructorInterceptor(); + enhancedInstance = new EnhancedInstance() { + private Object value; + + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + }; + when(databaseName.databaseName()).thenReturn(Optional.of("neo4j")); + mockStatic(ContextManager.class); + when(ContextManager.capture()).thenReturn(MockContextSnapshot.INSTANCE.mockContextSnapshot()); + } + + @Test + public void testWithDatabaseName() throws Throwable { + interceptor.onConstruct(enhancedInstance, new Object[]{null, null, databaseName}); + assertNotNull(enhancedInstance.getSkyWalkingDynamicField()); + SessionRequiredInfo requiredInfo = (SessionRequiredInfo) enhancedInstance.getSkyWalkingDynamicField(); + assertThat(requiredInfo.getContextSnapshot(), is(ContextManager.capture())); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionRunInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionRunInterceptorTest.java new file mode 100644 index 000000000..e4866e7b9 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/SessionRunInterceptorTest.java @@ -0,0 +1,204 @@ +/* + * 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.neo4j.v4x; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.NEO4J; +import static org.apache.skywalking.apm.plugin.neo4j.v4x.Neo4jPluginConstants.DB_TYPE; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.powermock.api.mockito.PowerMockito.when; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.MockContextSnapshot; +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.AbstractTracingSpan; +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.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +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.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.neo4j.v4x.Neo4jPluginConfig.Plugin.Neo4j; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.neo4j.driver.Query; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.value.MapValue; +import org.neo4j.driver.internal.value.StringValue; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +@SuppressWarnings("unchecked") +public class SessionRunInterceptorTest { + + private final static String CYPHER = "Match (m:Movie)-[a:ACTED_IN]-(p:Person) RETURN m,a,p"; + private final static String PARAMETERS_STR = "{name: \"John\"}"; + private final static int MAX_LENGTH = 5; + private final static String PARAMETERS_STR_TOO_LONG = "{name..."; + private final static String BODY_TOO_LONG = "Match..."; + private final Method method = MockMethod.class.getMethod("runAsync"); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @SegmentStoragePoint + private SegmentStorage segmentStorage; + private SessionRunInterceptor sessionRunInterceptor; + private EnhancedInstance enhancedInstance; + @Mock + private Query query; + @Mock + private Connection connection; + @Mock + private DatabaseName databaseName; + @Mock + private BoltServerAddress boltServerAddress; + + public SessionRunInterceptorTest() throws NoSuchMethodException { + } + + @Before + public void setUp() throws Exception { + sessionRunInterceptor = new SessionRunInterceptor(); + when(query.text()).thenReturn(CYPHER); + when(connection.databaseName()).thenReturn(databaseName); + when(connection.serverAddress()).thenReturn(boltServerAddress); + when(databaseName.databaseName()).thenReturn(Optional.of("neo4j")); + when(boltServerAddress.toString()).thenReturn("127.0.0.1:7687"); + Map valueMap = new HashMap<>(); + valueMap.put("name", new StringValue("John")); + when(query.parameters()).thenReturn(new MapValue(valueMap)); + enhancedInstance = new EnhancedInstance() { + private Object value; + + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + }; + SessionRequiredInfo requiredInfo = new SessionRequiredInfo(); + requiredInfo.setContextSnapshot(MockContextSnapshot.INSTANCE.mockContextSnapshot()); + enhancedInstance.setSkyWalkingDynamicField(requiredInfo); + final AbstractSpan span = ContextManager.createExitSpan("Neo4j", connection.serverAddress().toString()); + Tags.DB_TYPE.set(span, DB_TYPE); + Tags.DB_INSTANCE.set(span, connection.databaseName().databaseName().orElse(Neo4jPluginConstants.EMPTY_STRING)); + span.setComponent(NEO4J); + SpanLayer.asDB(span); + ContextManager.continued(requiredInfo.getContextSnapshot()); + span.prepareForAsync(); + ContextManager.stopSpan(); + requiredInfo.setSpan(span); + Neo4j.TRACE_CYPHER_PARAMETERS = false; + } + + @Test + public void testWithNoConnectionInfo() throws Throwable { + enhancedInstance.setSkyWalkingDynamicField(null); + sessionRunInterceptor + .beforeMethod(enhancedInstance, method, new Object[]{query}, new Class[]{Query.class}, null); + final CompletionStage result = (CompletionStage) sessionRunInterceptor + .afterMethod(enhancedInstance, method, new Object[]{query}, new Class[]{Query.class}, + CompletableFuture.completedFuture("result")); + assertThat(result.toCompletableFuture().get(), is("result")); + List traceSegments = segmentStorage.getTraceSegments(); + assertThat(traceSegments.size(), is(0)); + } + + @Test + public void testWithConnectionInfo() throws Throwable { + doInvokeInterceptorAndAssert(); + } + + @Test + public void testTraceCypherParameters() throws Throwable { + Neo4j.TRACE_CYPHER_PARAMETERS = true; + doInvokeInterceptorAndAssert(); + } + + @Test + public void testTraceCypherMaxSize() throws Throwable { + Neo4j.TRACE_CYPHER_PARAMETERS = true; + Neo4j.CYPHER_PARAMETERS_MAX_LENGTH = MAX_LENGTH; + Neo4j.CYPHER_BODY_MAX_LENGTH = MAX_LENGTH; + doInvokeInterceptorAndAssert(); + } + + private void doInvokeInterceptorAndAssert() throws Throwable { + final CompletionStage result = (CompletionStage) sessionRunInterceptor + .afterMethod(enhancedInstance, method, new Object[]{query}, new Class[]{Query.class}, + CompletableFuture.completedFuture("result")); + + assertThat(result.toCompletableFuture().get(), is("result")); + final List traceSegments = segmentStorage.getTraceSegments(); + assertThat(traceSegments.size(), is(1)); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + assertNotNull(spans); + assertThat(spans.size(), is(1)); + assertSpan(spans.get(0)); + } + + private void assertSpan(final AbstractTracingSpan span) { + SpanAssert.assertLayer(span, SpanLayer.DB); + SpanAssert.assertComponent(span, ComponentsDefine.NEO4J); + if (Neo4j.TRACE_CYPHER_PARAMETERS) { + SpanAssert.assertTagSize(span, 4); + if (PARAMETERS_STR.length() > Neo4j.CYPHER_PARAMETERS_MAX_LENGTH) { + SpanAssert.assertTag(span, 3, PARAMETERS_STR_TOO_LONG); + } else { + SpanAssert.assertTag(span, 3, PARAMETERS_STR); + } + } else { + SpanAssert.assertTagSize(span, 3); + } + SpanAssert.assertTag(span, 0, DB_TYPE); + SpanAssert.assertTag(span, 1, "neo4j"); + if (CYPHER.length() > Neo4j.CYPHER_BODY_MAX_LENGTH) { + SpanAssert.assertTag(span, 2, BODY_TOO_LONG); + } else { + SpanAssert.assertTag(span, 2, CYPHER); + } + assertTrue(span.isExit()); + assertThat(span.getOperationName(), is("Neo4j/Session/runAsync")); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/TransactionRunInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/TransactionRunInterceptorTest.java new file mode 100644 index 000000000..7af564f11 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/neo4j-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/neo4j/v4x/TransactionRunInterceptorTest.java @@ -0,0 +1,172 @@ +/* + * 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.neo4j.v4x; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.NEO4J; +import static org.apache.skywalking.apm.plugin.neo4j.v4x.Neo4jPluginConstants.DB_TYPE; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.powermock.api.mockito.PowerMockito.when; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.MockContextSnapshot; +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.AbstractTracingSpan; +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.test.helper.SegmentHelper; +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.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.neo4j.v4x.Neo4jPluginConfig.Plugin.Neo4j; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.neo4j.driver.Query; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.value.MapValue; +import org.neo4j.driver.internal.value.StringValue; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +@SuppressWarnings("unchecked") +public class TransactionRunInterceptorTest { + + private final Method method = MockMethod.class.getMethod("runAsync"); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @SegmentStoragePoint + private SegmentStorage segmentStorage; + private TransactionRunInterceptor transactionRunInterceptor; + @Mock + private Query query; + @Mock + private Connection connection; + @Mock + private BoltServerAddress boltServerAddress; + @Mock + private DatabaseName databaseName; + private MockUnmanagedTransaction mockUnmanagedTransaction; + + public TransactionRunInterceptorTest() throws NoSuchMethodException { + } + + @Before + public void setUp() throws Exception { + transactionRunInterceptor = new TransactionRunInterceptor(); + when(boltServerAddress.toString()).thenReturn("127.0.0.1:7687"); + when(connection.serverAddress()).thenReturn(boltServerAddress); + when(databaseName.databaseName()).thenReturn(Optional.of("neo4j")); + when(connection.databaseName()).thenReturn(databaseName); + when(query.text()).thenReturn("Match (m:Movie)-[a:ACTED_IN]-(p:Person) RETURN m,a,p"); + Map valueMap = new HashMap<>(); + valueMap.put("name", new StringValue("John")); + when(query.parameters()).thenReturn(new MapValue(valueMap)); + Neo4j.TRACE_CYPHER_PARAMETERS = false; + mockUnmanagedTransaction = new MockUnmanagedTransaction(connection, null, 10L); + SessionRequiredInfo requiredInfo = new SessionRequiredInfo(); + requiredInfo.setContextSnapshot(MockContextSnapshot.INSTANCE.mockContextSnapshot()); + final AbstractSpan span = ContextManager.createExitSpan("Neo4j", connection.serverAddress().toString()); + Tags.DB_TYPE.set(span, DB_TYPE); + Tags.DB_INSTANCE.set(span, connection.databaseName().databaseName().orElse(Neo4jPluginConstants.EMPTY_STRING)); + span.setComponent(NEO4J); + SpanLayer.asDB(span); + ContextManager.continued(requiredInfo.getContextSnapshot()); + span.prepareForAsync(); + ContextManager.stopSpan(); + requiredInfo.setSpan(span); + mockUnmanagedTransaction.setSkyWalkingDynamicField(requiredInfo); + } + + @Test + public void testWithNoSpan() throws Throwable { + ((SessionRequiredInfo) mockUnmanagedTransaction.getSkyWalkingDynamicField()).setSpan(null); + transactionRunInterceptor + .beforeMethod(mockUnmanagedTransaction, method, new Object[]{null}, new Class[0], null); + final CompletionStage result = (CompletionStage) transactionRunInterceptor + .afterMethod(mockUnmanagedTransaction, method, new Object[]{null}, new Class[0], + CompletableFuture.completedFuture("result")); + assertThat(result.toCompletableFuture().get(), is("result")); + final List traceSegments = segmentStorage.getTraceSegments(); + assertThat(traceSegments.size(), is(0)); + } + + @Test + public void testWithQuery() throws Throwable { + doInvokeInterceptorAndAssert(); + } + + @Test + public void testTraceQueryParameters() throws Throwable { + Neo4j.TRACE_CYPHER_PARAMETERS = true; + doInvokeInterceptorAndAssert(); + } + + private void doInvokeInterceptorAndAssert() throws Throwable { + transactionRunInterceptor + .beforeMethod(mockUnmanagedTransaction, method, new Object[]{query}, new Class[0], null); + final CompletionStage result = (CompletionStage) transactionRunInterceptor + .afterMethod(mockUnmanagedTransaction, method, new Object[]{query}, new Class[]{Query.class}, + CompletableFuture.completedFuture("result")); + + assertThat(result.toCompletableFuture().get(), is("result")); + final List traceSegments = segmentStorage.getTraceSegments(); + assertThat(traceSegments.size(), is(1)); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + assertNotNull(spans); + assertThat(spans.size(), is(1)); + assertSpan(spans.get(0)); + } + + private void assertSpan(final AbstractTracingSpan span) { + SpanAssert.assertLayer(span, SpanLayer.DB); + SpanAssert.assertComponent(span, ComponentsDefine.NEO4J); + if (Neo4j.TRACE_CYPHER_PARAMETERS) { + SpanAssert.assertTagSize(span, 4); + SpanAssert.assertTag(span, 3, "{name: \"John\"}"); + } else { + SpanAssert.assertTagSize(span, 3); + } + SpanAssert.assertTag(span, 0, DB_TYPE); + SpanAssert.assertTag(span, 1, "neo4j"); + SpanAssert.assertTag(span, 2, "Match (m:Movie)-[a:ACTED_IN]-(p:Person) RETURN m,a,p"); + assertTrue(span.isExit()); + assertThat(span.getOperationName(), is("Neo4j/Transaction/runAsync")); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index 1fd478e3d..a6cff87c8 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -112,6 +112,7 @@ mssql-jdbc-plugin cxf-3.x-plugin jsonrpc4j-1.x-plugin + neo4j-4.x-plugin pom 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 43618d1b6..54a31e6f6 100644 --- a/docs/en/setup/service-agent/java-agent/Plugin-list.md +++ b/docs/en/setup/service-agent/java-agent/Plugin-list.md @@ -118,3 +118,4 @@ - apache-cxf-3.x - jsonrpc4j - spring-cloud-gateway-3.x +- neo4j-4.x diff --git a/docs/en/setup/service-agent/java-agent/README.md b/docs/en/setup/service-agent/java-agent/README.md index c5fd39d98..ff2e79b81 100755 --- a/docs/en/setup/service-agent/java-agent/README.md +++ b/docs/en/setup/service-agent/java-agent/README.md @@ -169,6 +169,9 @@ property key | Description | Default | `plugin.toolkit.log.grpc.reporter.upstream_timeout` | How long grpc client will timeout in sending data to upstream. Unit is second.|`30` seconds| `plugin.lettuce.trace_redis_parameters` | If set to true, the parameters of Redis commands would be collected by Lettuce agent.| `false` | `plugin.lettuce.redis_parameter_max_length` | If set to positive number and `plugin.lettuce.trace_redis_parameters` is set to `true`, Redis command parameters would be collected and truncated to this length.| `128` | +`plugin.neo4j.trace_cypher_parameters`|If set to true, the parameters of the cypher would be collected.|`false`| +`plugin.neo4j.cypher_parameters_max_length`|If set to positive number, the `db.cypher.parameters` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem.|`512`| +`plugin.neo4j.cypher_body_max_length`|If set to positive number, the `db.statement` would be truncated to this length, otherwise it would be completely saved, which may cause performance problem.|`2048`| ## Dynamic Configurations All configurations above are static, if you need to change some agent settings at runtime, please read [CDS - Configuration Discovery Service document](configuration-discovery.md) for more details. 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 625a5662b..8f295c4af 100644 --- a/docs/en/setup/service-agent/java-agent/Supported-list.md +++ b/docs/en/setup/service-agent/java-agent/Supported-list.md @@ -85,6 +85,8 @@ metrics based on the tracing data. * [cassandra-java-driver](https://github.com/datastax/java-driver) 3.7.0-3.7.2 * HBase * [hbase-client](https://github.com/apache/hbase) HTable 1.0.0-2.4.2 + * Neo4j + * [Neo4j-java](https://neo4j.com/docs/java-manual/current) 4.x * Service Discovery * [Netflix Eureka](https://github.com/Netflix/eureka) * Distributed Coordination diff --git a/oap-server/server-bootstrap/src/main/resources/component-libraries.yml b/oap-server/server-bootstrap/src/main/resources/component-libraries.yml index 409784d96..0b0ad981d 100755 --- a/oap-server/server-bootstrap/src/main/resources/component-libraries.yml +++ b/oap-server/server-bootstrap/src/main/resources/component-libraries.yml @@ -365,6 +365,9 @@ tcp: AzureHttpTrigger: id: 111 languages: Java,C#,Node.js,Python +Neo4j: + id: 112 + languages: Java # .NET/.NET Core components # [3000, 4000) for C#/.NET only diff --git a/test/plugin/scenarios/neo4j-4.x-scenario/bin/startup.sh b/test/plugin/scenarios/neo4j-4.x-scenario/bin/startup.sh new file mode 100644 index 000000000..4223856c6 --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/bin/startup.sh @@ -0,0 +1,24 @@ +#!/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.neo4j.trace_cypher_parameters=true" ${home}/../libs/neo4j-4.x-scenario.jar & diff --git a/test/plugin/scenarios/neo4j-4.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/neo4j-4.x-scenario/config/expectedData.yaml new file mode 100644 index 000000000..a658b05f3 --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/config/expectedData.yaml @@ -0,0 +1,146 @@ +# 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: neo4j-4.x-scenario + segmentSize: ge 2 + segments: + - segmentId: not null + spans: + - operationName: Neo4j/Transaction/runAsync + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 112 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: db.type, value: Neo4j } + - { key: db.instance, value: neo4j } + - { key: db.statement, value: 'CREATE (a:Person {name: $name}) RETURN a.name' } + - { key: db.cypher.parameters, value: not null } + refs: + - { parentEndpoint: /neo4j-scenario/case/neo4j-scenario, networkAddress: '', + refType: CrossThread, parentSpanId: 0, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: neo4j-4.x-scenario, + traceId: not null } + - segmentId: not null + spans: + - operationName: Neo4j/Session/runAsync + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 112 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: db.type, value: Neo4j } + - { key: db.instance, value: neo4j } + - { key: db.statement, value: 'CREATE (a:Person {name: $name}) RETURN a.name' } + - { key: db.cypher.parameters, value: not null } + - operationName: Neo4j/Session/runAsync + operationId: 0 + parentSpanId: 0 + spanId: 2 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 112 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: db.type, value: Neo4j } + - { key: db.instance, value: neo4j } + - { key: db.statement, value: 'CREATE (a:Person {name: $name}) RETURN a.name' } + - { key: db.cypher.parameters, value: not null } + - operationName: Neo4j/Session/runRx + operationId: 0 + parentSpanId: 0 + spanId: 3 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 112 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: db.type, value: Neo4j } + - { key: db.instance, value: neo4j } + - { key: db.statement, value: 'CREATE (a:Person {name: $name}) RETURN a.name' } + - { key: db.cypher.parameters, value: not null } + - operationName: Neo4j/Transaction/runAsync + operationId: 0 + parentSpanId: 0 + spanId: 4 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 112 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: db.type, value: Neo4j } + - { key: db.instance, value: neo4j } + - { key: db.statement, value: 'CREATE (a:Person {name: $name}) RETURN a.name' } + - { key: db.cypher.parameters, value: not null } + - operationName: Neo4j/Transaction/runRx + operationId: 0 + parentSpanId: 0 + spanId: 5 + spanLayer: Database + startTime: nq 0 + endTime: nq 0 + componentId: 112 + isError: false + spanType: Exit + peer: not null + skipAnalysis: false + tags: + - { key: db.type, value: Neo4j } + - { key: db.instance, value: neo4j } + - { key: db.statement, value: 'CREATE (a:Person {name: $name}) RETURN a.name' } + - { key: db.cypher.parameters, value: not null } + - operationName: /neo4j-scenario/case/neo4j-scenario + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - { key: url, value: 'http://localhost:8080/neo4j-scenario/case/neo4j-scenario' } + - { key: http.method, value: GET } +meterItems: [ ] diff --git a/test/plugin/scenarios/neo4j-4.x-scenario/configuration.yml b/test/plugin/scenarios/neo4j-4.x-scenario/configuration.yml new file mode 100644 index 000000000..d572131f2 --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/configuration.yml @@ -0,0 +1,31 @@ +# 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/neo4j-scenario/case/neo4j-scenario +healthCheck: http://localhost:8080/neo4j-scenario/case/healthCheck +startScript: ./bin/startup.sh +environment: +depends_on: + - neo4j-server +dependencies: + neo4j-server: + image: neo4j:4.2.7-community + hostname: neo4j-server + expose: + - 7687 + environment: + - NEO4J_AUTH=none \ No newline at end of file diff --git a/test/plugin/scenarios/neo4j-4.x-scenario/pom.xml b/test/plugin/scenarios/neo4j-4.x-scenario/pom.xml new file mode 100644 index 000000000..6e52ad3a1 --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/pom.xml @@ -0,0 +1,123 @@ + + + + + org.apache.skywalking.apm.testcase + neo4j-4.x-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + 4.0.0 + 2.5.1 + + + skywalking-neo4j-4.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot-version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.neo4j.driver + neo4j-java-driver + ${test.framework.version} + + + + io.projectreactor + reactor-core + 3.4.6 + + + + + neo4j-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/neo4j-4.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 000000000..a06c3b4e2 --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/neo4j-4.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/neo4j-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/neo4j/Application.java b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/neo4j/Application.java new file mode 100644 index 000000000..5b82047c5 --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/neo4j/Application.java @@ -0,0 +1,46 @@ +/* + * 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.neo4j; + +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class Application { + + @Value("${neo4j.server.url:bolt://neo4j-server:7687}") + private String neo4jServerUrl; + + @Bean + public Driver driver() { + return GraphDatabase.driver(neo4jServerUrl); + } + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception e) { + // Never do this + } + } +} diff --git a/test/plugin/scenarios/neo4j-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/neo4j/controller/CaseController.java b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/neo4j/controller/CaseController.java new file mode 100644 index 000000000..831c8bc50 --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/neo4j/controller/CaseController.java @@ -0,0 +1,53 @@ +/* + * 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.neo4j.controller; + +import javax.annotation.Resource; +import org.apache.skywalking.apm.testcase.neo4j.service.TestCaseService; +import org.neo4j.driver.Driver; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/case") +public class CaseController { + + private static final String SUCCESS = "Success"; + @Resource + private TestCaseService testCaseService; + @Resource + private Driver driver; + + @RequestMapping("/neo4j-scenario") + @ResponseBody + public String testcase() throws Exception { + testCaseService.sessionScenarioTest(driver); + testCaseService.transactionScenarioTest(driver); + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() throws Exception { + driver.verifyConnectivity(); + return SUCCESS; + } + +} diff --git a/test/plugin/scenarios/neo4j-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/neo4j/service/TestCaseService.java b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/neo4j/service/TestCaseService.java new file mode 100644 index 000000000..aa39c5053 --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/neo4j/service/TestCaseService.java @@ -0,0 +1,106 @@ +/* + * 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.neo4j.service; + +import static org.neo4j.driver.Values.parameters; + +import java.util.Random; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.neo4j.driver.Driver; +import org.neo4j.driver.Session; +import org.neo4j.driver.SessionConfig; +import org.neo4j.driver.async.AsyncSession; +import org.neo4j.driver.async.ResultCursor; +import org.neo4j.driver.internal.shaded.reactor.core.publisher.Flux; +import org.neo4j.driver.internal.shaded.reactor.core.publisher.Mono; +import org.neo4j.driver.reactive.RxResult; +import org.neo4j.driver.reactive.RxSession; +import org.springframework.stereotype.Service; + +@Service +public class TestCaseService { + + private static final Logger LOGGER = LogManager.getLogger(TestCaseService.class); + private static final String QUERY = "CREATE (a:Person {name: $name}) RETURN a.name"; + private static final SessionConfig SESSION_CONFIG = SessionConfig.forDatabase("neo4j"); + + public void sessionScenarioTest(Driver driver) { + try (Session session = driver.session(SESSION_CONFIG)) { + final String result = session.run(QUERY, parameters("name", String.valueOf(new Random().nextInt()))) + .single() + .get(0).asString(); + LOGGER.info("Result from simple session: {}", result); + } + + final AsyncSession asyncSession = driver.asyncSession(SESSION_CONFIG); + + final String asyncResult = asyncSession + .runAsync(QUERY, parameters("name", String.valueOf(new Random().nextInt()))) + .thenCompose(ResultCursor::singleAsync) + .exceptionally(error -> { + error.printStackTrace(); + return null; + }) + .thenCompose(record -> asyncSession.closeAsync().thenApply(ignore -> record.get(0).asString())) + .toCompletableFuture().join(); + LOGGER.info("Result from async session: {}", asyncResult); + + Flux.usingWhen(Mono.fromSupplier(() -> driver.rxSession(SESSION_CONFIG)), + session -> Flux + .from(session.run(QUERY, parameters("name", String.valueOf(new Random().nextInt()))) + .records()) + .map(record -> record.get(0).asString()) + .doOnNext(result -> LOGGER.info("Result from rx session: {}", result)), + RxSession::close).blockLast(); + } + + public void transactionScenarioTest(Driver driver) { + try (Session session = driver.session(SESSION_CONFIG)) { + final String result = session + .writeTransaction( + transaction -> transaction + .run(QUERY, parameters("name", String.valueOf(new Random().nextInt()))).single() + .get(0).asString()); + LOGGER.info("Result from simple transaction: {}", result); + } + + final AsyncSession asyncSession = driver.asyncSession(SESSION_CONFIG); + final String asyncResult = asyncSession + .writeTransactionAsync( + asyncTransaction -> asyncTransaction + .runAsync(QUERY, parameters("name", String.valueOf(new Random().nextInt()))) + .thenCompose(ResultCursor::singleAsync) + .thenApply(record -> record.get(0).asString())) + .toCompletableFuture() + .join(); + LOGGER.info("Result from async transaction: {}", asyncResult); + + Flux.usingWhen(Mono.fromSupplier(() -> driver.rxSession(SESSION_CONFIG)), + rxSession -> + rxSession.writeTransaction(rxTransaction -> { + final RxResult result = rxTransaction + .run(QUERY, parameters("name", String.valueOf(new Random().nextInt()))); + return Flux.from(result.records()) + .doOnNext(record -> LOGGER + .info("Result from rx transaction: {}", record.get(0).asString())) + .then(Mono.from(result.consume())); + }), RxSession::close).blockLast(); + } +} diff --git a/test/plugin/scenarios/neo4j-4.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/resources/application.yaml new file mode 100644 index 000000000..d966c0f9b --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/resources/application.yaml @@ -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. +# +# +server: + port: 8080 + servlet: + context-path: /neo4j-scenario +logging: + config: classpath:log4j2.xml \ No newline at end of file diff --git a/test/plugin/scenarios/neo4j-4.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 000000000..b0336f413 --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/neo4j-4.x-scenario/support-version.list b/test/plugin/scenarios/neo4j-4.x-scenario/support-version.list new file mode 100644 index 000000000..c198b789a --- /dev/null +++ b/test/plugin/scenarios/neo4j-4.x-scenario/support-version.list @@ -0,0 +1,20 @@ +# 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.1.0 +4.2.0 +4.3.0