feat: add neo4j-4.x-plugin (#7099)

This commit is contained in:
wallezhang 2021-06-22 15:36:51 +08:00 committed by GitHub
parent 09944e00a9
commit 1bc69370bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2212 additions and 0 deletions

View File

@ -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:

View File

@ -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.

View File

@ -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");
}

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>apm-sdk-plugin</artifactId>
<groupId>org.apache.skywalking</groupId>
<version>8.7.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>apm-neo4j-4.x-plugin</artifactId>
<packaging>jar</packaging>
<name>neo4j-4.x-plugin</name>
<properties>
<neo4j.version>4.2.5</neo4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>${neo4j.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -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.
* <p>
* 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.
* <p>
* Set a negative number to save the complete sql body to the tag.
*/
public static int CYPHER_BODY_MAX_LENGTH = 2048;
}
}
}

View File

@ -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");
}

View File

@ -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<Connection> connectionStage = (CompletionStage<Connection>) 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) {
}
}

View File

@ -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<EnhancedInstance> transactionStage = (CompletionStage<EnhancedInstance>) 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) {
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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:
* <pre>
* 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.
* </pre>
*/
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);
}
}
}

View File

@ -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:
* <pre>
* 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.
* </pre>
*/
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);
}
}
}

View File

@ -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.
* <p>
* {@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.
* </p>
* <p>
* `acquireConnection()` method is used for getting connection information.
* </p>
*/
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<MethodDescription> 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<MethodDescription> 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<MethodDescription> 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<MethodDescription> 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;
}
}
};
}
}

View File

@ -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.
* <p>
* 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.
* </p>
*/
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<MethodDescription> 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;
}
}
};
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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() {
}
}

View File

@ -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;
}
}

View File

@ -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<Connection> stage = (CompletionStage<Connection>) 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<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
assertNotNull(traceSegments);
assertThat(traceSegments.size(), is(0));
});
}
}

View File

@ -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<EnhancedInstance> result = (CompletionStage<EnhancedInstance>) 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<EnhancedInstance> retStage = (CompletionStage<EnhancedInstance>) 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());
}
}

View File

@ -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()));
}
}

View File

@ -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<String, Value> 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<String> result = (CompletionStage<String>) sessionRunInterceptor
.afterMethod(enhancedInstance, method, new Object[]{query}, new Class[]{Query.class},
CompletableFuture.completedFuture("result"));
assertThat(result.toCompletableFuture().get(), is("result"));
List<TraceSegment> 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<String> result = (CompletionStage<String>) sessionRunInterceptor
.afterMethod(enhancedInstance, method, new Object[]{query}, new Class[]{Query.class},
CompletableFuture.completedFuture("result"));
assertThat(result.toCompletableFuture().get(), is("result"));
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
assertThat(traceSegments.size(), is(1));
final List<AbstractTracingSpan> 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"));
}
}

View File

@ -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<String, Value> 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<String> result = (CompletionStage<String>) transactionRunInterceptor
.afterMethod(mockUnmanagedTransaction, method, new Object[]{null}, new Class[0],
CompletableFuture.completedFuture("result"));
assertThat(result.toCompletableFuture().get(), is("result"));
final List<TraceSegment> 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<String> result = (CompletionStage<String>) transactionRunInterceptor
.afterMethod(mockUnmanagedTransaction, method, new Object[]{query}, new Class[]{Query.class},
CompletableFuture.completedFuture("result"));
assertThat(result.toCompletableFuture().get(), is("result"));
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
assertThat(traceSegments.size(), is(1));
final List<AbstractTracingSpan> 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"));
}
}

View File

@ -112,6 +112,7 @@
<module>mssql-jdbc-plugin</module>
<module>cxf-3.x-plugin</module>
<module>jsonrpc4j-1.x-plugin</module>
<module>neo4j-4.x-plugin</module>
</modules>
<packaging>pom</packaging>

View File

@ -118,3 +118,4 @@
- apache-cxf-3.x
- jsonrpc4j
- spring-cloud-gateway-3.x
- neo4j-4.x

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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 &

View File

@ -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: [ ]

View File

@ -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

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>org.apache.skywalking.apm.testcase</groupId>
<artifactId>neo4j-4.x-scenario</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<compiler.version>1.8</compiler.version>
<test.framework.version>4.0.0</test.framework.version>
<spring-boot-version>2.5.1</spring-boot-version>
</properties>
<name>skywalking-neo4j-4.x-scenario</name>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>${test.framework.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.4.6</version>
</dependency>
</dependencies>
<build>
<finalName>neo4j-4.x-scenario</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${compiler.version}</source>
<target>${compiler.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
<outputDirectory>./target/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
-->
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>./bin</directory>
<fileMode>0775</fileMode>
</fileSet>
</fileSets>
<files>
<file>
<source>${project.build.directory}/neo4j-4.x-scenario.jar</source>
<outputDirectory>./libs</outputDirectory>
<fileMode>0775</fileMode>
</file>
</files>
</assembly>

View File

@ -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
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
-->
<Configuration status="WARN">
<Appenders>
<Console name="Console">
<PatternLayout charset="UTF-8"
pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

View File

@ -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