diff --git a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java index 38f9dcad1417..c8dec42ee00f 100644 --- a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java +++ b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java @@ -122,6 +122,8 @@ public class ComponentsDefine { public static final OfficialComponent RESTEASY = new OfficialComponent(62, "RESTEasy"); + public static final OfficialComponent SOLRJ = new OfficialComponent(63, "solrj"); + private static ComponentsDefine INSTANCE = new ComponentsDefine(); private String[] components; @@ -179,6 +181,7 @@ public ComponentsDefine() { addComponent(VERTX); addComponent(SPRING_CLOUD_GATEWAY); addComponent(RESTEASY); + addComponent(SOLRJ); } private void addComponent(OfficialComponent component) { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java index 14ef3a0a4714..77847001aba0 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java @@ -193,5 +193,18 @@ public static class Toolkit { */ public static boolean USE_QUALIFIED_NAME_AS_OPERATION_NAME = false; } + + public static class SolrJ { + /** + * If true, trace all the query parameters(include deleteByIds and deleteByQuery) in Solr query request, default is false. + */ + public static boolean TRACE_STATEMENT = false; + + /** + * If true, trace all the operation parameters in Solr request, default is false. + */ + public static boolean TRACE_OPS_PARAMS = false; + } + } } diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index e850ccd8b213..90bb1a886cc2 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -73,6 +73,7 @@ dubbo-2.7.x-conflict-patch vertx-plugins resteasy-plugin + solrj-7.x-plugin pom diff --git a/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/pom.xml new file mode 100644 index 000000000000..31b931ade3bb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/pom.xml @@ -0,0 +1,47 @@ + + + + + apm-sdk-plugin + org.apache.skywalking + 6.2.0-SNAPSHOT + + 4.0.0 + + apm-solrj-7.x-plugin + jar + + solrj-7.x-plugin + http://maven.apache.org + + + UTF-8 + 7.7.1 + + + + + org.apache.solr + solr-solrj + ${solr-solrj.version} + provided + + + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/SolrConnectorInterceptor.java b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/SolrConnectorInterceptor.java new file mode 100644 index 000000000000..69faa3a81d03 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/SolrConnectorInterceptor.java @@ -0,0 +1,61 @@ +/* + * 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.solrj; + +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.skywalking.apm.agent.core.context.CarrierItem; +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.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 java.lang.reflect.Method; + +public class SolrConnectorInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + if (ContextManager.isActive()) { + HttpRequestBase request = (HttpRequestBase) allArguments[0]; + + ContextCarrier carrier = new ContextCarrier(); + ContextManager.inject(carrier); + + CarrierItem items = carrier.items(); + while (items.hasNext()) { + items = items.next(); + request.setHeader(items.getHeadKey(), items.getHeadValue()); + } + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + if (ContextManager.isActive()) { + ContextManager.activeSpan().errorOccurred().log(t); + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/commons/SolrjInstance.java b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/commons/SolrjInstance.java new file mode 100644 index 000000000000..91e4cfb362f0 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/commons/SolrjInstance.java @@ -0,0 +1,39 @@ +/* + * 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.solrj.commons; + +public class SolrjInstance { + private String collection = "Unknown"; + private String remotePeer = "Unknown"; + + public String getCollection() { + return collection; + } + + public void setCollection(String collection) { + this.collection = collection; + } + + public String getRemotePeer() { + return remotePeer; + } + + public void setRemotePeer(String remotePeer) { + this.remotePeer = remotePeer; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/commons/SolrjTags.java b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/commons/SolrjTags.java new file mode 100644 index 000000000000..3040b850b529 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/commons/SolrjTags.java @@ -0,0 +1,39 @@ +/* + * 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.solrj.commons; + +import org.apache.skywalking.apm.agent.core.context.tag.StringTag; + +public class SolrjTags { + public static StringTag TAG_QT = new StringTag("qt"); + public static StringTag TAG_COLLECTION = new StringTag("collection"); + + public static StringTag TAG_Q_TIME = new StringTag("QTime"); + public static StringTag TAG_STATUS = new StringTag("status"); + + public static StringTag TAG_START = new StringTag("start"); + public static StringTag TAG_SORT_BY = new StringTag("sort"); + public static StringTag TAG_NUM_FOUND = new StringTag("numFound"); + + public static StringTag TAG_SOFT_COMMIT = new StringTag("softCommit"); + public static StringTag TAG_COMMIT_WITHIN = new StringTag("commitWithin"); + public static StringTag TAG_MAX_OPTIMIZE_SEGMENTS = new StringTag("maxOptimizeSegs"); + + public static StringTag TAG_DOCS_SIZE = new StringTag("docsSize"); + public static StringTag TAG_DELETE_VALUE = new StringTag("delete.by"); +} diff --git a/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/define/SolrClientInstrumentation.java b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/define/SolrClientInstrumentation.java new file mode 100644 index 000000000000..f552f158527b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/solrj/define/SolrClientInstrumentation.java @@ -0,0 +1,91 @@ +/* + * 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.solrj.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +public class SolrClientInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + @Override + protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[]{ + new ConstructorInterceptPoint() { + + @Override + public String getConstructorInterceptor() { + return "org.apache.skywalking.apm.plugin.solrj.SolrClientInterceptor"; + } + + @Override + public ElementMatcher getConstructorMatcher() { + return ElementMatchers.any(); + } + } + }; + } + + @Override + protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override + public boolean isOverrideArgs() { + return false; + } + + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named("request").and(ElementMatchers.takesArguments(3)); + } + + @Override + public String getMethodsInterceptor() { + return "org.apache.skywalking.apm.plugin.solrj.SolrClientInterceptor"; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public boolean isOverrideArgs() { + return false; + } + + @Override + public ElementMatcher getMethodsMatcher() { + return ElementMatchers.named("executeMethod"); + } + + @Override + public String getMethodsInterceptor() { + return "org.apache.skywalking.apm.plugin.solrj.SolrConnectorInterceptor"; + } + } + }; + } + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName("org.apache.solr.client.solrj.impl.HttpSolrClient"); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000000..f9181f2021f9 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,17 @@ +# 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. + +solrj-7.x=org.apache.skywalking.apm.plugin.solrj.define.SolrClientInstrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/solrj/SolrClientInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/solrj/SolrClientInterceptorTest.java new file mode 100644 index 000000000000..c7311a8c6b88 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/solrj-7.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/solrj/SolrClientInterceptorTest.java @@ -0,0 +1,428 @@ +/* + * 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.solrj; + +import com.google.common.collect.Lists; +import org.apache.skywalking.apm.agent.core.conf.Config; +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.*; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.apache.skywalking.apm.plugin.solrj.commons.SolrjInstance; +import org.apache.solr.client.solrj.ResponseParser; +import org.apache.solr.client.solrj.SolrRequest; +import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.request.AbstractUpdateRequest; +import org.apache.solr.client.solrj.request.QueryRequest; +import org.apache.solr.client.solrj.request.UpdateRequest; +import org.apache.solr.common.*; +import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.util.NamedList; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +import java.lang.reflect.Method; +import java.util.List; + +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class SolrClientInterceptorTest { + SolrClientInterceptor interceptor = new SolrClientInterceptor(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + @Mock + private HttpSolrClient client; + + @Mock + private Method method; + + @Mock + private EnhancedInstance enhancedInstance; + + private Object[] arguments = null; + private Class[] argumentType = new Class[] { + SolrRequest.class, + ResponseParser.class, + String.class + }; + private String collection = null; + private HttpSolrClient.Builder builder; + + @Mock + private SolrjInstance instance; + private NamedList header; + + @Before + public void setup() throws Exception { + builder = new HttpSolrClient.Builder().withBaseSolrUrl("http://solr-server:8983/solr/collection"); + enhancedInstance = new EnhanceHttpSolrClient(builder); + + when(instance.getCollection()).thenReturn("collection"); + when(instance.getRemotePeer()).thenReturn("solr-server:8983"); + enhancedInstance.setSkyWalkingDynamicField(instance); + + header = new NamedList(); + header.add("status", 0); + header.add("QTime", 5); + +// Config.Plugin.SolrJ.TRACE_STATEMENT = true; +// Config.Plugin.SolrJ.TRACE_OPS_PARAMS = true; + } + + + @Test + public void testConstructor() throws Throwable { + arguments = new Object[] {builder}; + interceptor.onConstruct(enhancedInstance, arguments); + SolrjInstance instance = (SolrjInstance) enhancedInstance.getSkyWalkingDynamicField(); + Assert.assertEquals(instance.getRemotePeer(), "solr-server:8983"); + Assert.assertEquals(instance.getCollection(), "collection"); + } + + + @Test + public void testUpdateWithAdd() throws Throwable { + UpdateRequest request = new UpdateRequest(); + List docs = Lists.newArrayList(); + for (int start = 0; start < 100; start++) { + SolrInputDocument doc = new SolrInputDocument(); + doc.addField("id", start); + docs.add(doc); + } + arguments = new Object[] { + request.add(docs), + null, + collection + }; + interceptor.beforeMethod(enhancedInstance, method, arguments, argumentType, null); + interceptor.afterMethod(enhancedInstance, method, arguments, argumentType, getResponse()); + + List segments = segmentStorage.getTraceSegments(); + Assert.assertEquals(segments.size(), 1); + + List spans = SegmentHelper.getSpans(segments.get(0)); + Assert.assertEquals(spans.size(), 1); + + AbstractTracingSpan span = spans.get(0); + int pox = 0; + if (Config.Plugin.SolrJ.TRACE_STATEMENT) { + SpanAssert.assertTag(span, ++pox, "100"); + } + if (Config.Plugin.SolrJ.TRACE_OPS_PARAMS) { + SpanAssert.assertTag(span, ++pox, "-1"); + } + spanCommonAssert(span, pox,"solrJ/collection/update/ADD"); + } + + @Test + public void testUpdateWithCommit() throws Throwable { + final boolean softCommit = false; + AbstractUpdateRequest request = (new UpdateRequest()).setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true, false); + arguments = new Object[]{ + request, + null, + collection + }; + interceptor.beforeMethod(enhancedInstance, method, arguments, argumentType, null); + interceptor.afterMethod(enhancedInstance, method, arguments, argumentType, getResponse()); + + List segments = segmentStorage.getTraceSegments(); + Assert.assertEquals(segments.size(), 1); + + List spans = SegmentHelper.getSpans(segments.get(0)); + Assert.assertEquals(spans.size(), 1); + + int start = 0; + AbstractTracingSpan span = spans.get(0); + if (Config.Plugin.SolrJ.TRACE_OPS_PARAMS) { + SpanAssert.assertTag(span, ++start, String.valueOf(softCommit)); + } + spanCommonAssert(span, start, "solrJ/collection/update/COMMIT"); + } + + @Test + public void testUpdateWithOptimize() throws Throwable { + final int maxSegments = 1; + AbstractUpdateRequest request = (new UpdateRequest()).setAction(AbstractUpdateRequest.ACTION.OPTIMIZE, false, true, maxSegments); + arguments = new Object[]{ + request, + null, + collection + }; + interceptor.beforeMethod(enhancedInstance, method, arguments, argumentType, null); + interceptor.afterMethod(enhancedInstance, method, arguments, argumentType, getResponse()); + + List segments = segmentStorage.getTraceSegments(); + List spans = SegmentHelper.getSpans(segments.get(0)); + + Assert.assertEquals(segments.size(), 1); + Assert.assertEquals(spans.size(), 1); + + AbstractTracingSpan span = spans.get(0); + int start = 0; + if (Config.Plugin.SolrJ.TRACE_OPS_PARAMS) { + SpanAssert.assertTag(span, ++start, String.valueOf(maxSegments)); + } + spanCommonAssert(span, start, "solrJ/collection/update/OPTIMIZE"); + } + + @Test + public void testQuery() throws Throwable { + QueryRequest request = new QueryRequest(); + arguments = new Object[] { + request, + null, + collection + }; + + interceptor.beforeMethod(enhancedInstance, method, arguments, argumentType, null); + interceptor.afterMethod(enhancedInstance, method, arguments, argumentType, getQueryResponse()); + + List segments = segmentStorage.getTraceSegments(); + List spans = SegmentHelper.getSpans(segments.get(0)); + + Assert.assertEquals(segments.size(), 1); + Assert.assertEquals(spans.size(), 1); + + AbstractTracingSpan span = spans.get(0); + querySpanAssert(span, "/select", 100, "solrJ/collection/select"); + } + + @Test + public void testGet() throws Throwable { + ModifiableSolrParams reqParams = new ModifiableSolrParams(); + if (StringUtils.isEmpty(reqParams.get("qt"))) { + reqParams.set("qt", new String[]{"/get"}); + } + reqParams.set("ids", new String[] {"99", "98"}); + QueryRequest request = new QueryRequest(reqParams); + + arguments = new Object[] { + request, + null, + collection + }; + interceptor.beforeMethod(enhancedInstance, method, arguments, argumentType, null); + interceptor.afterMethod(enhancedInstance, method, arguments, argumentType, getGetResponse()); + + List segments = segmentStorage.getTraceSegments(); + List spans = SegmentHelper.getSpans(segments.get(0)); + Assert.assertEquals(segments.size(), 1); + Assert.assertEquals(spans.size(), 1); + + AbstractTracingSpan span = spans.get(0); + querySpanAssert(span, "/get", 1, "solrJ/collection/get"); + } + + @Test + public void testDeleteById() throws Throwable { + UpdateRequest request = new UpdateRequest(); + arguments = new Object[] { + request.deleteById("12"), + null, + collection + }; + interceptor.beforeMethod(enhancedInstance, method, arguments, argumentType, null); + interceptor.afterMethod(enhancedInstance, method, arguments, argumentType, getResponse()); + + List segments = segmentStorage.getTraceSegments(); + List spans = SegmentHelper.getSpans(segments.get(0)); + + Assert.assertEquals(segments.size(), 1); + Assert.assertEquals(spans.size(), 1); + + AbstractTracingSpan span = spans.get(0); + spanDeleteAssert(span, "solrJ/collection/update/DELETE_BY_IDS", "[12]"); + } + + @Test + public void testDeleteByQuery() throws Throwable { + UpdateRequest request = new UpdateRequest(); + arguments = new Object[] { + request.deleteByQuery("id:[2 TO 5]"), + null, + collection + }; + interceptor.beforeMethod(enhancedInstance, method, arguments, argumentType, null); + interceptor.afterMethod(enhancedInstance, method, arguments, argumentType, getResponse()); + + List segments = segmentStorage.getTraceSegments(); + List spans = SegmentHelper.getSpans(segments.get(0)); + + Assert.assertEquals(segments.size(), 1); + Assert.assertEquals(spans.size(), 1); + + AbstractTracingSpan span = spans.get(0); + spanDeleteAssert(span, "solrJ/collection/update/DELETE_BY_QUERY", "[id:[2 TO 5]]"); + } + + @Test + public void testException() throws Throwable { + QueryRequest request = new QueryRequest(); + arguments = new Object[] { + request, + null, + collection + }; + NamedList response = new NamedList(); + NamedList header = new NamedList(); + header.add("status", 500); + header.add("QTime", 5); + response.add("responseHeader", header); + + interceptor.beforeMethod(enhancedInstance, method, arguments, argumentType, null); + interceptor.handleMethodException(enhancedInstance, method, arguments, argumentType, + new SolrException(SolrException.ErrorCode.SERVER_ERROR, "for test", new Exception())); + interceptor.afterMethod(enhancedInstance, method, arguments, argumentType, response); + + List segments = segmentStorage.getTraceSegments(); + List spans = SegmentHelper.getSpans(segments.get(0)); + + Assert.assertEquals(segments.size(), 1); + Assert.assertEquals(spans.size(), 1); + + AbstractTracingSpan span = spans.get(0); + SpanAssert.assertOccurException(span, true); + } + + + + private void querySpanAssert(AbstractSpan span, String qt, int numFound, String operationName) { + Assert.assertEquals(span.getOperationName(), operationName); + SpanAssert.assertTag(span, 0, "Solr"); + SpanAssert.assertTag(span, 1, "0"); + SpanAssert.assertTag(span, 2, qt); + + int start = 3; + if (Config.Plugin.SolrJ.TRACE_STATEMENT) { + start++; + } + SpanAssert.assertTag(span, start++, "5"); + SpanAssert.assertTag(span, start++, String.valueOf(numFound)); + } + + private void spanCommonAssert(AbstractSpan span, int start, String operationName) { + SpanAssert.assertComponent(span, ComponentsDefine.SOLRJ); + SpanAssert.assertOccurException(span, false); + SpanAssert.assertLogSize(span, 0); + SpanAssert.assertLayer(span, SpanLayer.DB); + + SpanAssert.assertTag(span, 0, "Solr"); + SpanAssert.assertTag(span, start + 1, "5"); + + Assert.assertEquals(span.getOperationName(), operationName); + } + + private void spanDeleteAssert(AbstractSpan span, String operationName, String statement) { + Assert.assertEquals(span.getOperationName(), operationName); + SpanAssert.assertComponent(span, ComponentsDefine.SOLRJ); + SpanAssert.assertOccurException(span, false); + SpanAssert.assertLogSize(span, 0); + SpanAssert.assertLayer(span, SpanLayer.DB); + + SpanAssert.assertTag(span, 0, "Solr"); + + int start = 0; + if (Config.Plugin.SolrJ.TRACE_STATEMENT) { + SpanAssert.assertTag(span, ++start, statement); + } + if (Config.Plugin.SolrJ.TRACE_OPS_PARAMS) { + SpanAssert.assertTag(span, ++start, "-1"); + } + + SpanAssert.assertTag(span, start + 1, "5"); + } + + private NamedList getResponse() { + NamedList response = new NamedList(); + response.add("responseHeader", header); + return response; + } + + private NamedList getQueryResponse() { + NamedList response = new NamedList(); + response.add("responseHeader", header); + SolrDocumentList list = new SolrDocumentList(); + list.setStart(0); + list.setNumFound(100); + list.setMaxScore(.0f); + + for (int start = 0; start < 10; start++) { + SolrDocument doc = new SolrDocument(); + doc.addField("id", start); + doc.addField("_version", 1634676349644832768L); + list.add(doc); + } + response.add("response", list); + return response; + } + + private NamedList getGetResponse() { + NamedList response = new NamedList(); + response.add("responseHeader", header); + SolrDocumentList list = new SolrDocumentList(); + list.setStart(0); + list.setNumFound(1); + list.setMaxScore(.0f); + + SolrDocument doc = new SolrDocument(); + doc.addField("id", 1); + doc.addField("_version", 1634676349644832768L); + list.add(doc); + + response.add("response", list); + return response; + } + + class EnhanceHttpSolrClient extends HttpSolrClient implements EnhancedInstance { + Object value = null; + + protected EnhanceHttpSolrClient(Builder builder) { + super(builder); + } + + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + } + +} \ No newline at end of file diff --git a/docs/en/setup/service-agent/java-agent/README.md b/docs/en/setup/service-agent/java-agent/README.md index a14cd44b786b..9b727d5d2a24 100644 --- a/docs/en/setup/service-agent/java-agent/README.md +++ b/docs/en/setup/service-agent/java-agent/README.md @@ -82,6 +82,8 @@ property key | Description | Default | `plugin.elasticsearch.trace_dsl`|If true, trace all the DSL(Domain Specific Language) in ElasticSearch access, default is false.|`false`| `plugin.springmvc.use_qualified_name_as_endpoint_name`|If true, the fully qualified method name will be used as the endpoint name instead of the request URL, default is false.|`false`| `plugin.toolit.use_qualified_name_as_operation_name`|If true, the fully qualified method name will be used as the operation name instead of the given operation name, default is false.|`false`| +`plugin.solrj.trace_statement`|If true, trace all the query parameters(include deleteByIds and deleteByQuery) in Solr query request, default is false.|`false`| +`plugin.solrj.trace_ops_params`|If true, trace all the operation parameters in Solr request, default is false.|`false`| ## Optional Plugins Java agent plugins are all pluggable. Optional plugins could be provided in `optional-plugins` folder under agent or 3rd party repositores. diff --git a/docs/en/setup/service-agent/java-agent/Supported-list.md b/docs/en/setup/service-agent/java-agent/Supported-list.md index 2baf906a2eb2..c43fd7d4a1eb 100644 --- a/docs/en/setup/service-agent/java-agent/Supported-list.md +++ b/docs/en/setup/service-agent/java-agent/Supported-list.md @@ -53,6 +53,9 @@ * [Xmemcached](https://github.com/killme2008/xmemcached) 2.x * [Elasticsearch](https://github.com/elastic/elasticsearch) * [transport-client](https://github.com/elastic/elasticsearch/tree/master/client/transport) 5.2.x-5.6.x + * [Solr](https://github.com/apache/lucene-solr/) + * [SolrJ](https://github.com/apache/lucene-solr/tree/master/solr/solrj) 7.x + * Service Discovery * [Netflix Eureka](https://github.com/Netflix/eureka) * Distributed Coordination diff --git a/oap-server/server-core/src/test/resources/component-libraries.yml b/oap-server/server-core/src/test/resources/component-libraries.yml index 97733d80264d..defa1d68732e 100644 --- a/oap-server/server-core/src/test/resources/component-libraries.yml +++ b/oap-server/server-core/src/test/resources/component-libraries.yml @@ -198,6 +198,12 @@ spring-cloud-gateway: RESTEasy: id: 62 languages: Java +SolrJ: + id: 63 + languages: Java +Solr: + id: 64 + languages: Java # .NET/.NET Core components # [3000, 4000) for C#/.NET only @@ -295,3 +301,4 @@ Component-Server-Mappings: Pomelo.EntityFrameworkCore.MySql: Mysql Npgsql.EntityFrameworkCore.PostgreSQL: PostgreSQL transport-client: Elasticsearch + SolrJ: Solr \ No newline at end of file diff --git a/oap-server/server-starter/src/main/resources/component-libraries.yml b/oap-server/server-starter/src/main/resources/component-libraries.yml index 4d9598842b57..3179c6b5d8b2 100644 --- a/oap-server/server-starter/src/main/resources/component-libraries.yml +++ b/oap-server/server-starter/src/main/resources/component-libraries.yml @@ -216,6 +216,13 @@ spring-cloud-gateway: RESTEasy: id: 62 languages: Java +SolrJ: + id: 63 + languages: Java +Solr: + id: 64 + languages: Java + # .NET/.NET Core components # [3000, 4000) for C#/.NET only @@ -315,3 +322,4 @@ Component-Server-Mappings: Pomelo.EntityFrameworkCore.MySql: Mysql Npgsql.EntityFrameworkCore.PostgreSQL: PostgreSQL transport-client: Elasticsearch + SolrJ: Solr \ No newline at end of file