diff --git a/README.md b/README.md index e6bcd0756..df784b161 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ _In chronological order_ * 徐妍 [@TastySummer](https://github.com/TastySummer) * 彭勇升 [@pengys5](https://github.com/pengys5) * 戴文 - +* 柏杨 [@bai-yang](https://github.com/bai-yang) # Screenshots - Topological graph of application clusters. diff --git a/skywalking-sniffer/skywalking-agent/pom.xml b/skywalking-sniffer/skywalking-agent/pom.xml index 5ff241eae..16a3c2ddb 100644 --- a/skywalking-sniffer/skywalking-agent/pom.xml +++ b/skywalking-sniffer/skywalking-agent/pom.xml @@ -55,6 +55,11 @@ motan-plugin ${project.version} + + com.a.eye + skywalking-mongodb-3.x-plugin + ${project.version} + diff --git a/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/conf/Config.java b/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/conf/Config.java index 96aaafa13..1c88af5aa 100644 --- a/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/conf/Config.java +++ b/skywalking-sniffer/skywalking-api/src/main/java/com/a/eye/skywalking/api/conf/Config.java @@ -81,4 +81,14 @@ public class Config { */ public static LogLevel LEVEL = LogLevel.DEBUG; } + + public static class Plugin { + public static class MongoDB { + /** + * If true, trace all the parameters, default is false. + * Only trace the operation, not include parameters. + */ + public static boolean TRACE_PARAM = false; + } + } } diff --git a/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/pom.xml b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/pom.xml new file mode 100644 index 000000000..956a67a9e --- /dev/null +++ b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + + skywalking-sdk-plugin + com.a.eye + 3.0.1-2017 + + + skywalking-mongodb-3.x-plugin + jar + + mongodb-plugin + http://maven.apache.org + + + UTF-8 + + + + + org.mongodb + mongo-java-driver + 3.4.2 + provided + + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + + attach-sources + + jar + + + + + + + \ No newline at end of file diff --git a/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBMethodInterceptor.java b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBMethodInterceptor.java new file mode 100644 index 000000000..d69fabbe8 --- /dev/null +++ b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBMethodInterceptor.java @@ -0,0 +1,190 @@ +package com.a.eye.skywalking.plugin.mongodb.v3; + +import java.util.List; + +import org.bson.BsonDocument; + +import com.a.eye.skywalking.api.conf.Config; +import com.a.eye.skywalking.api.context.ContextManager; +import com.a.eye.skywalking.api.plugin.interceptor.EnhancedClassInstanceContext; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodInvokeContext; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.MethodInterceptResult; +import com.a.eye.skywalking.trace.Span; +import com.a.eye.skywalking.trace.tag.Tags; +import com.mongodb.ReadPreference; +import com.mongodb.bulk.DeleteRequest; +import com.mongodb.bulk.InsertRequest; +import com.mongodb.bulk.UpdateRequest; +import com.mongodb.bulk.WriteRequest; +import com.mongodb.operation.CountOperation; +import com.mongodb.operation.CreateCollectionOperation; +import com.mongodb.operation.CreateIndexesOperation; +import com.mongodb.operation.CreateViewOperation; +import com.mongodb.operation.DeleteOperation; +import com.mongodb.operation.DistinctOperation; +import com.mongodb.operation.FindAndDeleteOperation; +import com.mongodb.operation.FindAndReplaceOperation; +import com.mongodb.operation.FindAndUpdateOperation; +import com.mongodb.operation.FindOperation; +import com.mongodb.operation.GroupOperation; +import com.mongodb.operation.InsertOperation; +import com.mongodb.operation.ListCollectionsOperation; +import com.mongodb.operation.MapReduceToCollectionOperation; +import com.mongodb.operation.MapReduceWithInlineResultsOperation; +import com.mongodb.operation.MixedBulkWriteOperation; +import com.mongodb.operation.ReadOperation; +import com.mongodb.operation.UpdateOperation; +import com.mongodb.operation.WriteOperation; + +/** + * {@link MongoDBMethodInterceptor} intercept method of {@link com.mongodb.Mongo#execute(ReadOperation, ReadPreference)} + * or {@link com.mongodb.Mongo#execute(WriteOperation)}. record the mongoDB host, operation name and the key of the + * operation. + * + * @author baiyang + */ +public class MongoDBMethodInterceptor implements InstanceMethodsAroundInterceptor { + + /** + * The key name that MongoDB host in {@link EnhancedClassInstanceContext#context}. + */ + static final String MONGODB_HOST = "MONGODB_HOST"; + + /** + * The key name that MongoDB port in {@link EnhancedClassInstanceContext#context}. + */ + static final String MONGODB_PORT = "MONGODB_PORT"; + + private static final String MONGODB_COMPONENT = "MongoDB"; + + private static final String METHOD = "MongoDB/"; + + private static final int FILTER_LENGTH_LIMIT = 256; + + private static final String EMPTY = ""; + + @Override + public void beforeMethod(final EnhancedClassInstanceContext context, + final InstanceMethodInvokeContext interceptorContext, final MethodInterceptResult result) { + Object[] arguments = interceptorContext.allArguments(); + + String methodName = arguments[0].getClass().getSimpleName(); + Span span = ContextManager.createSpan(METHOD + methodName); + Tags.COMPONENT.set(span, MONGODB_COMPONENT); + Tags.DB_TYPE.set(span, MONGODB_COMPONENT); + Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT); + Tags.SPAN_LAYER.asDB(span); + + if (Config.Plugin.MongoDB.TRACE_PARAM) { + Tags.DB_STATEMENT.set(span, methodName + " " + this.getTraceParam(arguments[0])); + } + + } + + @Override + public Object afterMethod(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext, + Object ret) { + Span span = ContextManager.activeSpan(); + Tags.PEER_HOST.set(span, context.get(MONGODB_HOST, String.class)); + Tags.PEER_PORT.set(span, (Integer)context.get(MONGODB_PORT)); + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(Throwable t, EnhancedClassInstanceContext context, + InstanceMethodInvokeContext interceptorContext) { + ContextManager.activeSpan().log(t); + } + + /** + * Convert ReadOperation interface or WriteOperation interface to the implementation class. Get the method name and + * filter info. + */ + @SuppressWarnings("rawtypes") + private String getTraceParam(Object obj) { + if (obj instanceof CountOperation) { + BsonDocument filter = ((CountOperation)obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof DistinctOperation) { + BsonDocument filter = ((DistinctOperation)obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof FindOperation) { + BsonDocument filter = ((FindOperation)obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof GroupOperation) { + BsonDocument filter = ((GroupOperation)obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof ListCollectionsOperation) { + BsonDocument filter = ((ListCollectionsOperation)obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof MapReduceWithInlineResultsOperation) { + BsonDocument filter = ((ListCollectionsOperation)obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof DeleteOperation) { + List writeRequestList = ((DeleteOperation)obj).getDeleteRequests(); + return getFilter(writeRequestList); + } else if (obj instanceof InsertOperation) { + List writeRequestList = ((InsertOperation)obj).getInsertRequests(); + return getFilter(writeRequestList); + } else if (obj instanceof UpdateOperation) { + List writeRequestList = ((UpdateOperation)obj).getUpdateRequests(); + return getFilter(writeRequestList); + } else if (obj instanceof CreateCollectionOperation) { + String filter = ((CreateCollectionOperation)obj).getCollectionName(); + return limitFilter(filter); + } else if (obj instanceof CreateIndexesOperation) { + List filter = ((CreateIndexesOperation)obj).getIndexNames(); + return limitFilter(filter.toString()); + } else if (obj instanceof CreateViewOperation) { + String filter = ((CreateViewOperation)obj).getViewName(); + return limitFilter(filter); + } else if (obj instanceof FindAndDeleteOperation) { + BsonDocument filter = ((FindAndDeleteOperation)obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof FindAndReplaceOperation) { + BsonDocument filter = ((FindAndReplaceOperation)obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof FindAndUpdateOperation) { + BsonDocument filter = ((FindAndUpdateOperation)obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof MapReduceToCollectionOperation) { + BsonDocument filter = ((MapReduceToCollectionOperation)obj).getFilter(); + return limitFilter(filter.toString()); + } else if (obj instanceof MixedBulkWriteOperation) { + List writeRequestList = ((MixedBulkWriteOperation)obj).getWriteRequests(); + return getFilter(writeRequestList); + } else { + return EMPTY; + } + } + + private String getFilter(List writeRequestList) { + StringBuilder params = new StringBuilder(); + for (WriteRequest request : writeRequestList) { + if (request instanceof InsertRequest) { + params.append(((InsertRequest)request).getDocument().toString()).append(","); + } else if (request instanceof DeleteRequest) { + params.append(((DeleteRequest)request).getFilter()).append(","); + } else if (request instanceof UpdateRequest) { + params.append(((UpdateRequest)request).getFilter()).append(","); + } + if (params.length() > FILTER_LENGTH_LIMIT) { + params.append("..."); + break; + } + } + return params.toString(); + } + + private String limitFilter(String filter) { + final StringBuilder params = new StringBuilder(); + if (filter.length() > FILTER_LENGTH_LIMIT) { + return params.append(filter.substring(0, FILTER_LENGTH_LIMIT)).append("...").toString(); + } else { + return filter; + } + } + +} diff --git a/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBReadBindingInterceptor.java b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBReadBindingInterceptor.java new file mode 100644 index 000000000..000422d5f --- /dev/null +++ b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBReadBindingInterceptor.java @@ -0,0 +1,49 @@ +package com.a.eye.skywalking.plugin.mongodb.v3; + +import com.a.eye.skywalking.api.plugin.interceptor.EnhancedClassInstanceContext; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodInvokeContext; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.MethodInterceptResult; +import com.mongodb.ReadPreference; +import com.mongodb.ServerAddress; +import com.mongodb.binding.ReadBinding; + +/** + * {@link MongoDBReadBindingInterceptor} record the host and port information + * from {@link EnhancedClassInstanceContext#context}, + * + * @author baiyang + */ +public class MongoDBReadBindingInterceptor implements + InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedClassInstanceContext context, + InstanceMethodInvokeContext interceptorContext, + MethodInterceptResult result) { + } + + /** + * Execute after {@link com.mongodb.Mongo#getReadBinding(ReadPreference)}, + * record the host and port information + */ + @Override + public Object afterMethod(EnhancedClassInstanceContext context, + InstanceMethodInvokeContext interceptorContext, Object ret) { + ReadBinding readBinding = (ReadBinding)ret; + ServerAddress serverAddress = readBinding.getReadConnectionSource() + .getServerDescription().getAddress(); + String host = serverAddress.getHost(); + Integer port = serverAddress.getPort(); + context.set(MongoDBMethodInterceptor.MONGODB_HOST, host); + context.set(MongoDBMethodInterceptor.MONGODB_PORT, port); + return ret; + } + + @Override + public void handleMethodException(Throwable t, + EnhancedClassInstanceContext context, + InstanceMethodInvokeContext interceptorContext) { + } + +} diff --git a/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBWriteBindingInterceptor.java b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBWriteBindingInterceptor.java new file mode 100644 index 000000000..09a89f6bc --- /dev/null +++ b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBWriteBindingInterceptor.java @@ -0,0 +1,44 @@ +package com.a.eye.skywalking.plugin.mongodb.v3; + +import com.a.eye.skywalking.api.plugin.interceptor.EnhancedClassInstanceContext; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodInvokeContext; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.MethodInterceptResult; +import com.mongodb.ServerAddress; +import com.mongodb.binding.WriteBinding; + +/** + * {@link MongoDBWriteBindingInterceptor} record the host and port information from {@link + * EnhancedClassInstanceContext#context} + * + * @author baiyang + */ +public class MongoDBWriteBindingInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext, + MethodInterceptResult result) { + } + + /** + * Execute after {@link com.mongodb.Mongo#getWriteBinding()}, + * record the host and port information + */ + @Override + public Object afterMethod(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext, + Object ret) { + WriteBinding writeBinding = (WriteBinding)ret; + ServerAddress serverAddress = writeBinding.getWriteConnectionSource().getServerDescription().getAddress(); + String host = serverAddress.getHost(); + Integer port = serverAddress.getPort(); + context.set(MongoDBMethodInterceptor.MONGODB_HOST, host); + context.set(MongoDBMethodInterceptor.MONGODB_PORT, port); + return ret; + } + + @Override + public void handleMethodException(Throwable t, EnhancedClassInstanceContext context, + InstanceMethodInvokeContext interceptorContext) { + } + +} diff --git a/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/define/MongoDBInstrumentation.java b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/define/MongoDBInstrumentation.java new file mode 100644 index 000000000..98f7e08c6 --- /dev/null +++ b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/java/com/a/eye/skywalking/plugin/mongodb/v3/define/MongoDBInstrumentation.java @@ -0,0 +1,76 @@ +package com.a.eye.skywalking.plugin.mongodb.v3.define; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import com.a.eye.skywalking.api.plugin.interceptor.ConstructorInterceptPoint; +import com.a.eye.skywalking.api.plugin.interceptor.InstanceMethodsInterceptPoint; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import com.a.eye.skywalking.plugin.mongodb.v3.MongoDBMethodInterceptor; + +/** + * {@link MongoDBInstrumentation} presents that skywalking intercepts {@link com.mongodb.Mongo#execute(ReadOperation, + * ReadPreference)},{@link com.mongodb.Mongo#execute(WriteOperation)} by using {@link MongoDBMethodInterceptor}. + * + * @author baiyang + */ +public class MongoDBInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.mongodb.Mongo"; + + private static final String MONGDB_READ_BINDING_CLASS = "com.a.eye.skywalking.plugin.mongodb.v3.MongoDBReadBindingInterceptor"; + + private static final String MONGDB_WRITE_BINDING_CLASS = "com.a.eye.skywalking.plugin.mongodb.v3.MongoDBWriteBindingInterceptor"; + + private static final String MONGDB_METHOD_INTERCET_CLASS = "com.a.eye.skywalking.plugin.mongodb.v3.MongoDBMethodInterceptor"; + + @Override + protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; + } + + @Override + protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("execute"); + } + + @Override + public String getMethodsInterceptor() { + return MONGDB_METHOD_INTERCET_CLASS; + } + }, new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("getReadBinding"); + } + + @Override + public String getMethodsInterceptor() { + return MONGDB_READ_BINDING_CLASS; + } + }, new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("getWriteBinding"); + } + + @Override + public String getMethodsInterceptor() { + return MONGDB_WRITE_BINDING_CLASS; + } + } + }; + } + + @Override + protected String enhanceClassName() { + return ENHANCE_CLASS; + } + +} diff --git a/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/resources/skywalking-plugin.def b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..8473327cb --- /dev/null +++ b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1 @@ +com.a.eye.skywalking.plugin.mongodb.v3.define.MongoDBInstrumentation \ No newline at end of file diff --git a/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBMethodInterceptorTest.java b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBMethodInterceptorTest.java new file mode 100644 index 000000000..6902ab01c --- /dev/null +++ b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBMethodInterceptorTest.java @@ -0,0 +1,131 @@ +package com.a.eye.skywalking.plugin.mongodb.v3; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.codecs.Decoder; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.powermock.api.mockito.PowerMockito; + +import com.a.eye.skywalking.api.boot.ServiceManager; +import com.a.eye.skywalking.api.conf.Config; +import com.a.eye.skywalking.api.context.TracerContext; +import com.a.eye.skywalking.api.plugin.interceptor.EnhancedClassInstanceContext; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodInvokeContext; +import com.a.eye.skywalking.sniffer.mock.context.MockTracerContextListener; +import com.a.eye.skywalking.sniffer.mock.context.SegmentAssert; +import com.a.eye.skywalking.trace.LogData; +import com.a.eye.skywalking.trace.Span; +import com.a.eye.skywalking.trace.TraceSegment; +import com.a.eye.skywalking.trace.tag.Tags; +import com.mongodb.MongoNamespace; +import com.mongodb.operation.FindOperation; + +@RunWith(MockitoJUnitRunner.class) +public class MongoDBMethodInterceptorTest { + + private MongoDBMethodInterceptor interceptor; + private MockTracerContextListener mockTracerContextListener; + + @Mock + private EnhancedClassInstanceContext classInstanceContext; + @Mock + private InstanceMethodInvokeContext methodInvokeContext; + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Before + public void setUp() throws Exception { + ServiceManager.INSTANCE.boot(); + + interceptor = new MongoDBMethodInterceptor(); + mockTracerContextListener = new MockTracerContextListener(); + + TracerContext.ListenerManager.add(mockTracerContextListener); + + Config.Plugin.MongoDB.TRACE_PARAM = true; + + when(classInstanceContext.get(MongoDBMethodInterceptor.MONGODB_HOST, String.class)).thenReturn("127.0.0.1"); + when(classInstanceContext.get(MongoDBMethodInterceptor.MONGODB_PORT)).thenReturn(27017); + when(methodInvokeContext.methodName()).thenReturn("find"); + + BsonDocument document = new BsonDocument(); + document.append("name", new BsonString("by")); + MongoNamespace mongoNamespace = new MongoNamespace("test.user"); + Decoder decoder = PowerMockito.mock(Decoder.class); + FindOperation findOperation = new FindOperation(mongoNamespace, decoder); + findOperation.filter(document); + + when(methodInvokeContext.allArguments()).thenReturn(new Object[] {findOperation}); + } + + @Test + public void testIntercept() { + interceptor.beforeMethod(classInstanceContext, methodInvokeContext, null); + interceptor.afterMethod(classInstanceContext, methodInvokeContext, null); + + mockTracerContextListener.assertSize(1); + mockTracerContextListener.assertTraceSegment(0, new SegmentAssert() { + @Override + public void call(TraceSegment traceSegment) { + assertThat(traceSegment.getSpans().size(), is(1)); + Span span = traceSegment.getSpans().get(0); + assertRedisSpan(span); + } + }); + } + + @Test + public void testInterceptWithException() { + interceptor.beforeMethod(classInstanceContext, methodInvokeContext, null); + interceptor.handleMethodException(new RuntimeException(), classInstanceContext, methodInvokeContext); + interceptor.afterMethod(classInstanceContext, methodInvokeContext, null); + + mockTracerContextListener.assertSize(1); + mockTracerContextListener.assertTraceSegment(0, new SegmentAssert() { + @Override + public void call(TraceSegment traceSegment) { + assertThat(traceSegment.getSpans().size(), is(1)); + Span span = traceSegment.getSpans().get(0); + assertRedisSpan(span); + assertThat(span.getLogs().size(), is(1)); + assertLogData(span.getLogs().get(0)); + } + }); + } + + private void assertLogData(LogData logData) { + MatcherAssert.assertThat(logData.getFields().size(), is(4)); + MatcherAssert.assertThat(logData.getFields().get("event"), CoreMatchers.is("error")); + assertEquals(logData.getFields().get("error.kind"), RuntimeException.class.getName()); + assertNull(logData.getFields().get("message")); + } + + private void assertRedisSpan(Span span) { + assertThat(span.getOperationName(), is("MongoDB/FindOperation")); + assertThat(Tags.PEER_HOST.get(span), is("127.0.0.1")); + assertThat(Tags.PEER_PORT.get(span), is(27017)); + assertThat(Tags.COMPONENT.get(span), is("MongoDB")); + assertThat(Tags.DB_STATEMENT.get(span), is("FindOperation { \"name\" : \"by\" }")); + assertThat(Tags.DB_TYPE.get(span), is("MongoDB")); + assertTrue(Tags.SPAN_LAYER.isDB(span)); + } + + @After + public void tearDown() throws Exception { + TracerContext.ListenerManager.remove(mockTracerContextListener); + } + +} diff --git a/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBReadBindingInterceptorTest.java b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBReadBindingInterceptorTest.java new file mode 100644 index 000000000..78b515717 --- /dev/null +++ b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBReadBindingInterceptorTest.java @@ -0,0 +1,61 @@ +package com.a.eye.skywalking.plugin.mongodb.v3; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.a.eye.skywalking.api.plugin.interceptor.EnhancedClassInstanceContext; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodInvokeContext; +import com.mongodb.ServerAddress; +import com.mongodb.binding.ConnectionSource; +import com.mongodb.binding.ReadBinding; +import com.mongodb.connection.ServerConnectionState; +import com.mongodb.connection.ServerDescription; + +@RunWith(PowerMockRunner.class) +public class MongoDBReadBindingInterceptorTest { + + private MongoDBReadBindingInterceptor interceptor; + + @Mock + private EnhancedClassInstanceContext instanceContext; + + @Mock + private InstanceMethodInvokeContext interceptorContext; + + @Mock + private ReadBinding readBinding; + + @Mock + private ConnectionSource connectionSource; + + private ServerAddress address = new ServerAddress("127.0.0.1", 27017); + + @Before + public void setUp() throws Exception { + + interceptor = new MongoDBReadBindingInterceptor(); + + ServerDescription serverDescription = + ServerDescription.builder().address(address).state(ServerConnectionState.CONNECTED).build(); + + PowerMockito.when(connectionSource.getServerDescription()).thenReturn(serverDescription); + + PowerMockito.when(readBinding.getReadConnectionSource()).thenReturn(connectionSource); + + } + + @Test + public void afterMethodTest() throws Exception { + interceptor.afterMethod(instanceContext, interceptorContext, readBinding); + verify(instanceContext, times(1)).set(MongoDBMethodInterceptor.MONGODB_HOST, "127.0.0.1"); + verify(instanceContext, times(1)).set(MongoDBMethodInterceptor.MONGODB_PORT, 27017); + } + +} diff --git a/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBWriteBindingInterceptorTest.java b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBWriteBindingInterceptorTest.java new file mode 100644 index 000000000..138e18c0f --- /dev/null +++ b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBWriteBindingInterceptorTest.java @@ -0,0 +1,61 @@ +package com.a.eye.skywalking.plugin.mongodb.v3; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.a.eye.skywalking.api.plugin.interceptor.EnhancedClassInstanceContext; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodInvokeContext; +import com.mongodb.ServerAddress; +import com.mongodb.binding.ConnectionSource; +import com.mongodb.binding.WriteBinding; +import com.mongodb.connection.ServerConnectionState; +import com.mongodb.connection.ServerDescription; + +@RunWith(PowerMockRunner.class) +public class MongoDBWriteBindingInterceptorTest { + + private MongoDBWriteBindingInterceptor interceptor; + + @Mock + private EnhancedClassInstanceContext instanceContext; + + @Mock + private InstanceMethodInvokeContext interceptorContext; + + @Mock + private WriteBinding writeBinding; + + @Mock + private ConnectionSource connectionSource; + + private ServerAddress address = new ServerAddress("127.0.0.1", 27017); + + @Before + public void setUp() throws Exception { + + interceptor = new MongoDBWriteBindingInterceptor(); + + ServerDescription serverDescription = + ServerDescription.builder().address(address).state(ServerConnectionState.CONNECTED).build(); + + PowerMockito.when(connectionSource.getServerDescription()).thenReturn(serverDescription); + + PowerMockito.when(writeBinding.getWriteConnectionSource()).thenReturn(connectionSource); + + } + + @Test + public void afterMethodTest() throws Exception { + interceptor.afterMethod(instanceContext, interceptorContext, writeBinding); + verify(instanceContext, times(1)).set(MongoDBMethodInterceptor.MONGODB_HOST, "127.0.0.1"); + verify(instanceContext, times(1)).set(MongoDBMethodInterceptor.MONGODB_PORT, 27017); + } + +} diff --git a/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBWriteMethodInterceptorTest.java b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBWriteMethodInterceptorTest.java new file mode 100644 index 000000000..1154f6f57 --- /dev/null +++ b/skywalking-sniffer/skywalking-sdk-plugin/mongodb-3.x-plugin/src/test/java/com/a/eye/skywalking/plugin/mongodb/v3/MongoDBWriteMethodInterceptorTest.java @@ -0,0 +1,142 @@ +package com.a.eye.skywalking.plugin.mongodb.v3; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.powermock.api.mockito.PowerMockito; + +import com.a.eye.skywalking.api.boot.ServiceManager; +import com.a.eye.skywalking.api.conf.Config; +import com.a.eye.skywalking.api.context.TracerContext; +import com.a.eye.skywalking.api.plugin.interceptor.EnhancedClassInstanceContext; +import com.a.eye.skywalking.api.plugin.interceptor.enhance.InstanceMethodInvokeContext; +import com.a.eye.skywalking.sniffer.mock.context.MockTracerContextListener; +import com.a.eye.skywalking.sniffer.mock.context.SegmentAssert; +import com.a.eye.skywalking.trace.LogData; +import com.a.eye.skywalking.trace.Span; +import com.a.eye.skywalking.trace.TraceSegment; +import com.a.eye.skywalking.trace.tag.Tags; +import com.mongodb.MongoNamespace; +import com.mongodb.WriteConcern; +import com.mongodb.bulk.DeleteRequest; +import com.mongodb.operation.DeleteOperation; + +@RunWith(MockitoJUnitRunner.class) +public class MongoDBWriteMethodInterceptorTest { + + private MongoDBMethodInterceptor interceptor; + private MockTracerContextListener mockTracerContextListener; + + @Mock + private EnhancedClassInstanceContext classInstanceContext; + @Mock + private InstanceMethodInvokeContext methodInvokeContext; + + @Before + public void setUp() throws Exception { + ServiceManager.INSTANCE.boot(); + + interceptor = new MongoDBMethodInterceptor(); + mockTracerContextListener = new MockTracerContextListener(); + + TracerContext.ListenerManager.add(mockTracerContextListener); + + Config.Plugin.MongoDB.TRACE_PARAM = true; + + when(classInstanceContext.get(MongoDBMethodInterceptor.MONGODB_HOST, String.class)).thenReturn("127.0.0.1"); + when(classInstanceContext.get(MongoDBMethodInterceptor.MONGODB_PORT)).thenReturn(27017); + when(methodInvokeContext.methodName()).thenReturn("find"); + + BsonDocument document = new BsonDocument(); + document.append("name", new BsonString("by")); + + List requestList = new ArrayList(); + + DeleteRequest deleteRequest = new DeleteRequest(document); + + requestList.add(deleteRequest); + + MongoNamespace mongoNamespace = new MongoNamespace("test.user"); + + WriteConcern writeConcern = PowerMockito.mock(WriteConcern.class); + + DeleteOperation deleteOperation = new DeleteOperation(mongoNamespace, false, writeConcern, requestList); + + when(methodInvokeContext.allArguments()).thenReturn(new Object[] {deleteOperation}); + } + + @Test + public void testIntercept() { + interceptor.beforeMethod(classInstanceContext, methodInvokeContext, null); + interceptor.afterMethod(classInstanceContext, methodInvokeContext, null); + + mockTracerContextListener.assertSize(1); + mockTracerContextListener.assertTraceSegment(0, new SegmentAssert() { + @Override + public void call(TraceSegment traceSegment) { + assertThat(traceSegment.getSpans().size(), is(1)); + Span span = traceSegment.getSpans().get(0); + assertRedisSpan(span); + } + }); + } + + private void assertRedisSpan(Span span) { + assertThat(span.getOperationName(), is("MongoDB/DeleteOperation")); + assertThat(Tags.PEER_HOST.get(span), is("127.0.0.1")); + assertThat(Tags.PEER_PORT.get(span), is(27017)); + assertThat(Tags.COMPONENT.get(span), is("MongoDB")); + assertThat(Tags.DB_STATEMENT.get(span), is("DeleteOperation { \"name\" : \"by\" },")); + assertThat(Tags.DB_TYPE.get(span), is("MongoDB")); + assertTrue(Tags.SPAN_LAYER.isDB(span)); + } + + @Test + public void testInterceptWithException() { + interceptor.beforeMethod(classInstanceContext, methodInvokeContext, null); + interceptor.handleMethodException(new RuntimeException(), classInstanceContext, methodInvokeContext); + interceptor.afterMethod(classInstanceContext, methodInvokeContext, null); + + mockTracerContextListener.assertSize(1); + mockTracerContextListener.assertTraceSegment(0, new SegmentAssert() { + @Override + public void call(TraceSegment traceSegment) { + assertThat(traceSegment.getSpans().size(), is(1)); + Span span = traceSegment.getSpans().get(0); + assertRedisSpan(span); + assertThat(span.getLogs().size(), is(1)); + assertLogData(span.getLogs().get(0)); + } + }); + } + + private void assertLogData(LogData logData) { + MatcherAssert.assertThat(logData.getFields().size(), is(4)); + MatcherAssert.assertThat(logData.getFields().get("event"), CoreMatchers.is("error")); + assertEquals(logData.getFields().get("error.kind"), RuntimeException.class.getName()); + assertNull(logData.getFields().get("message")); + } + + @After + public void tearDown() throws Exception { + TracerContext.ListenerManager.remove(mockTracerContextListener); + } + +} diff --git a/skywalking-sniffer/skywalking-sdk-plugin/pom.xml b/skywalking-sniffer/skywalking-sdk-plugin/pom.xml index 814529c36..3c9e66864 100644 --- a/skywalking-sniffer/skywalking-sdk-plugin/pom.xml +++ b/skywalking-sniffer/skywalking-sdk-plugin/pom.xml @@ -17,6 +17,7 @@ jedis-2.x-plugin tomcat-7.x-8.x-plugin motan-plugin + mongodb-3.x-plugin pom