From fb2eb49291765fc522698ced1af9a559c9a6c4ab Mon Sep 17 00:00:00 2001 From: eyalkoren Date: Tue, 30 Oct 2018 17:36:53 +0200 Subject: [PATCH 1/4] Support older ES client versions --- .../apm-es-restclient-plugin-5_6/pom.xml | 43 +++ .../ESRestClientInstrumentationHelper.java | 2 +- ...lasticsearchRestClientInstrumentation.java | 152 +++++++++ .../apm/es/restclient/v5_6}/package-info.java | 2 +- ....elastic.apm.bci.ElasticApmInstrumentation | 1 + ...sticsearchRestClientInstrumentationIT.java | 302 ++++++++++++++++++ .../apm-es-restclient-plugin-6_4/pom.xml | 43 +++ .../ESRestClientInstrumentationHelper.java | 65 ++++ ...lasticsearchRestClientInstrumentation.java | 2 +- .../apm/es/restclient/v6_4/package-info.java | 23 ++ ....elastic.apm.bci.ElasticApmInstrumentation | 1 + ...sticsearchRestClientInstrumentationIT.java | 10 +- ...tClientInstrumentationIT_RealReporter.java | 2 +- .../apm-es-restclient-plugin/pom.xml | 34 +- ....elastic.apm.bci.ElasticApmInstrumentation | 1 - elastic-apm-agent/pom.xml | 7 +- 16 files changed, 651 insertions(+), 39 deletions(-) create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/pom.xml rename apm-agent-plugins/apm-es-restclient-plugin/{src/main/java/co/elastic/apm/es/restclient => apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6}/ESRestClientInstrumentationHelper.java (98%) create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentation.java rename apm-agent-plugins/apm-es-restclient-plugin/{src/main/java/co/elastic/apm/es/restclient => apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6}/package-info.java (94%) create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentationIT.java create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/pom.xml create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/ESRestClientInstrumentationHelper.java rename apm-agent-plugins/apm-es-restclient-plugin/{src/main/java/co/elastic/apm/es/restclient => apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4}/ElasticsearchRestClientInstrumentation.java (99%) create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/package-info.java create mode 100644 apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation rename apm-agent-plugins/apm-es-restclient-plugin/{src/test/java/co/elastic/apm/es/restclient => apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/es/restclient/v6_4}/ElasticsearchRestClientInstrumentationIT.java (97%) rename apm-agent-plugins/apm-es-restclient-plugin/{src/test/java/co/elastic/apm/es/restclient => apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/es/restclient/v6_4}/ElasticsearchRestClientInstrumentationIT_RealReporter.java (99%) delete mode 100644 apm-agent-plugins/apm-es-restclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/pom.xml new file mode 100644 index 0000000000..606785d24f --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + apm-es-restclient-plugin + co.elastic.apm + 0.9.0-SNAPSHOT + + + apm-es-restclient-plugin-5_6 + ${project.groupId}:${project.artifactId} + + + 5.6.0 + + + + + + org.elasticsearch.client + elasticsearch-rest-client + ${version.elasticsearch} + provided + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + ${version.elasticsearch} + provided + + + + + fr.pilato.elasticsearch.testcontainers + testcontainers-elasticsearch + 0.1 + test + + + diff --git a/apm-agent-plugins/apm-es-restclient-plugin/src/main/java/co/elastic/apm/es/restclient/ESRestClientInstrumentationHelper.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ESRestClientInstrumentationHelper.java similarity index 98% rename from apm-agent-plugins/apm-es-restclient-plugin/src/main/java/co/elastic/apm/es/restclient/ESRestClientInstrumentationHelper.java rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ESRestClientInstrumentationHelper.java index bdc9001500..6064c2113a 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/src/main/java/co/elastic/apm/es/restclient/ESRestClientInstrumentationHelper.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ESRestClientInstrumentationHelper.java @@ -17,7 +17,7 @@ * limitations under the License. * #L% */ -package co.elastic.apm.es.restclient; +package co.elastic.apm.es.restclient.v5_6; import co.elastic.apm.bci.VisibleForAdvice; import co.elastic.apm.report.serialize.DslJsonSerializer; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentation.java new file mode 100644 index 0000000000..e72e2eb0b6 --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentation.java @@ -0,0 +1,152 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * Licensed 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. + * #L% + */ +package co.elastic.apm.es.restclient.v5_6; + +import co.elastic.apm.bci.ElasticApmInstrumentation; +import co.elastic.apm.bci.VisibleForAdvice; +import co.elastic.apm.impl.transaction.AbstractSpan; +import co.elastic.apm.impl.transaction.Span; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.http.HttpEntity; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.ResponseException; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; + +import static net.bytebuddy.matcher.ElementMatchers.declaresMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +/** + * Instrumentation for Elasticsearch RestClient, currently supporting only synchronized queries. + * All sync operations go through org.elasticsearch.client.RestClient#performRequest(org.elasticsearch.client.Request) + */ +public class ElasticsearchRestClientInstrumentation extends ElasticApmInstrumentation { + @VisibleForAdvice + public static final String SEARCH_QUERY_PATH_SUFFIX = "_search"; + @VisibleForAdvice + public static final String SPAN_TYPE = "db.elasticsearch.request"; + @VisibleForAdvice + public static final String DB_CONTEXT_TYPE = "elasticsearch"; + + @Advice.OnMethodEnter + private static void onBeforeExecute(@Advice.Argument(0) String method, + @Advice.Argument(1) String endpoint, + @Advice.Argument(3) HttpEntity entity, + @Advice.Local("span") Span span) { + if (tracer == null) { + return; + } + final AbstractSpan activeSpan = tracer.getActive(); + if (activeSpan == null || !activeSpan.isSampled()) { + return; + } + span = activeSpan.createSpan() + .withType(SPAN_TYPE) + .appendToName("Elasticsearch: ").appendToName(method).appendToName(" ").appendToName(endpoint); + span.getContext().getDb().withType(DB_CONTEXT_TYPE); + span.activate(); + + if (span.isSampled()) { + span.getContext().getHttp().withMethod(method); + if (endpoint.endsWith(SEARCH_QUERY_PATH_SUFFIX)) { + if (entity != null && entity.isRepeatable()) { + try { + String body = ESRestClientInstrumentationHelper.readRequestBody(entity.getContent(), endpoint); + if (body != null && !body.isEmpty()) { + span.getContext().getDb().withStatement(body); + } + } catch (IOException e) { + // We can't log from here + } + } + } + } + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void onAfterExecute(@Advice.Return @Nullable Response response, + @Advice.Local("span") @Nullable Span span, + @Advice.Thrown @Nullable Throwable t) { + if (span != null) { + try { + String url = null; + int statusCode = -1; + if(response != null) { + url = response.getHost().toURI(); + statusCode = response.getStatusLine().getStatusCode(); + } else if(t != null) { + if (t instanceof ResponseException) { + ResponseException esre = (ResponseException) t; + url = esre.getResponse().getHost().toURI(); + statusCode = esre.getResponse().getStatusLine().getStatusCode(); + + /* + // Add tags so that they will be copied to error capture + span.addTag(QUERY_STATUS_CODE_KEY, Integer.toString(statusCode)); + span.addTag(ELASTICSEARCH_NODE_URL_KEY, url); + span.addTag(ERROR_REASON_KEY, esre.getResponse().getStatusLine().getReasonPhrase()); + */ + } + span.captureException(t); + } + + if(url != null && !url.isEmpty()) { + span.getContext().getHttp().withUrl(url); + } + if(statusCode > 0) { + span.getContext().getHttp().withStatusCode(statusCode); + } + } finally { + span.deactivate().end(); + } + + } + } + + @Override + public ElementMatcher getTypeMatcher() { + return named("org.elasticsearch.client.RestClient"). + and(not( + declaresMethod(named("performRequest") + .and(takesArguments(1) + .and(takesArgument(0, named("org.elasticsearch.client.Request"))))))); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("performRequest") + .and(takesArguments(6) + .and(takesArgument(4, named("org.elasticsearch.client.HttpAsyncResponseConsumerFactory")))); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Collections.singleton("elasticsearch-restclient"); + } +} diff --git a/apm-agent-plugins/apm-es-restclient-plugin/src/main/java/co/elastic/apm/es/restclient/package-info.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/package-info.java similarity index 94% rename from apm-agent-plugins/apm-es-restclient-plugin/src/main/java/co/elastic/apm/es/restclient/package-info.java rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/package-info.java index 12f8af4895..8e7ab29086 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/src/main/java/co/elastic/apm/es/restclient/package-info.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/package-info.java @@ -18,6 +18,6 @@ * #L% */ @NonnullApi -package co.elastic.apm.es.restclient; +package co.elastic.apm.es.restclient.v5_6; import co.elastic.apm.annotation.NonnullApi; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation new file mode 100644 index 0000000000..7977689b3f --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation @@ -0,0 +1 @@ +co.elastic.apm.es.restclient.v5_6.ElasticsearchRestClientInstrumentation diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentationIT.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentationIT.java new file mode 100644 index 0000000000..7b4eb43d7a --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/test/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentationIT.java @@ -0,0 +1,302 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * Licensed 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. + * #L% + */ +package co.elastic.apm.es.restclient.v5_6; + +import co.elastic.apm.AbstractInstrumentationTest; +import co.elastic.apm.impl.error.ErrorCapture; +import co.elastic.apm.impl.transaction.Db; +import co.elastic.apm.impl.transaction.Http; +import co.elastic.apm.impl.transaction.Span; +import co.elastic.apm.impl.transaction.Transaction; +import fr.pilato.elasticsearch.containers.ElasticsearchContainer; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.delete.DeleteResponse; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.action.update.UpdateResponse; +import org.elasticsearch.client.ResponseException; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static co.elastic.apm.es.restclient.v5_6.ElasticsearchRestClientInstrumentation.SEARCH_QUERY_PATH_SUFFIX; +import static co.elastic.apm.es.restclient.v5_6.ElasticsearchRestClientInstrumentation.SPAN_TYPE; +import static co.elastic.apm.es.restclient.v5_6.ElasticsearchRestClientInstrumentation.DB_CONTEXT_TYPE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; + +public class ElasticsearchRestClientInstrumentationIT extends AbstractInstrumentationTest { + private static final String USER_NAME = "elastic-user"; + private static final String PASSWORD = "elastic-pass"; + + @SuppressWarnings("NullableProblems") + private static ElasticsearchContainer container; + @SuppressWarnings("NullableProblems") + private static RestClient lowLevelClient; + private static RestHighLevelClient client; + + private static final String INDEX = "my-index"; + private static final String SECOND_INDEX = "my-second-index"; + private static final String DOC_ID = "38uhjds8s4g"; + private static final String DOC_TYPE = "_doc"; + private static final String FOO = "foo"; + private static final String BAR = "bar"; + private static final String BAZ = "baz"; + + /** + * This Integration testing relies on this + * ES testcontainer module (will be replaced with an official version once merged into the + * testcontainers repo) + */ + @BeforeClass + public static void startElasticsearchContainerAndClient() throws IOException { + // Start the container + // TODO: the env setting is to pass the bootstrap check of "vm.max_map_count" which fails on the CI env. + // TODO: Can be removed after changing to the official testcontainer + container = (ElasticsearchContainer) new ElasticsearchContainer().withEnv("discovery.type", "single-node"); + container.start(); + + // Create the client + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(USER_NAME, PASSWORD)); + + RestClientBuilder builder = RestClient.builder(container.getHost()) + .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); + lowLevelClient = builder.build(); + client = new RestHighLevelClient(lowLevelClient); + + lowLevelClient.performRequest("PUT", "/" + INDEX); + reporter.reset(); + } + + @AfterClass + public static void stopElasticsearchContainerAndClient() throws IOException { + lowLevelClient.performRequest("DELETE", "/" + INDEX); + container.stop(); + lowLevelClient.close(); + } + + @Before + public void startTransaction() { + Transaction transaction = tracer.startTransaction().activate(); + transaction.setName("ES Transaction"); + transaction.withType("request"); + transaction.withResult("success"); + } + + @After + public void endTransaction() { + Transaction currentTransaction = tracer.currentTransaction(); + if (currentTransaction != null) { + currentTransaction.end(); + } + reporter.reset(); + } + + @Test + public void testTryToDeleteNonExistingIndex() throws IOException { + ResponseException re = null; + try { + lowLevelClient.performRequest("POST", "/non-existing/1/_mapping"); + } catch (ResponseException e) { + re = e; + } + assertThat(re).isNotNull(); + assertThat(re.getResponse().getStatusLine().getStatusCode()).isEqualTo(400); + + System.out.println(reporter.generateErrorPayloadJson()); + + List errorCaptures = reporter.getErrors(); + assertThat(errorCaptures).hasSize(1); + ErrorCapture errorCapture = errorCaptures.get(0); + assertThat(errorCapture.getException()).isNotNull(); +/* + Map tags = errorCapture.getContext().getTags(); + assertThat(tags).containsKey(QUERY_STATUS_CODE_KEY); + assertThat(tags.get(QUERY_STATUS_CODE_KEY)).isEqualTo("404"); + assertThat(tags).containsKey(ERROR_REASON_KEY); + assertThat(tags).containsKey(ELASTICSEARCH_NODE_URL_KEY); + assertThat(tags.get(ELASTICSEARCH_NODE_URL_KEY)).isEqualTo(container.getHost().toURI().toString()); +*/ + } + + private void validateSpanContent(Span span, String expectedName, int statusCode, String method) { + assertThat(span.getType()).isEqualTo(SPAN_TYPE); + assertThat(span.getName().toString()).isEqualTo(expectedName); + validateHttpContextContent(span.getContext().getHttp(), statusCode, method); + + assertThat(span.getContext().getDb().getType()).isEqualTo(DB_CONTEXT_TYPE); + + if (!expectedName.contains(SEARCH_QUERY_PATH_SUFFIX)) { + assertThat(span.getContext().getDb().getStatement()).isNull(); + } + } + + private void validateHttpContextContent(Http http, int statusCode, String method) { + assertThat(http).isNotNull(); + assertThat(http.getMethod()).isEqualTo(method); + assertThat(http.getStatusCode()).isEqualTo(statusCode); + assertThat(http.getUrl()).isEqualTo(container.getHost().toURI().toString()); + } + + private void validateDbContextContent(Span span, String statement) { + Db db = span.getContext().getDb(); + assertThat(db.getType()).isEqualTo(DB_CONTEXT_TYPE); + assertThat(db.getStatement()).isEqualTo(statement); + } + + @Test + public void testCreateAndDeleteIndex() throws IOException { + // Create an Index + lowLevelClient.performRequest("PUT", "/" + SECOND_INDEX); + + System.out.println(reporter.generateTransactionPayloadJson()); + + List spans = reporter.getSpans(); + assertThat(spans).hasSize(1); + validateSpanContent(spans.get(0), String.format("Elasticsearch: PUT /%s", SECOND_INDEX), 200, "PUT"); + + // Delete the index + reporter.reset(); + + lowLevelClient.performRequest("DELETE", "/" + SECOND_INDEX); + + System.out.println(reporter.generateTransactionPayloadJson()); + + spans = reporter.getSpans(); + assertThat(spans).hasSize(1); + validateSpanContent(spans.get(0), String.format("Elasticsearch: DELETE /%s", SECOND_INDEX), 200, "DELETE"); + } + + @Test + public void testDocumentScenario() throws IOException { + // Index a document + IndexResponse ir = client.index(new IndexRequest(INDEX, DOC_TYPE, DOC_ID).source( + jsonBuilder() + .startObject() + .field(FOO, BAR) + .endObject() + ).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)); + assertThat(ir.status().getStatus()).isEqualTo(200); + + System.out.println(reporter.generateTransactionPayloadJson()); + + List spans = reporter.getSpans(); + assertThat(spans).hasSize(1); + validateSpanContent(spans.get(0), String.format("Elasticsearch: PUT /%s/%s/%s", INDEX, DOC_TYPE, DOC_ID), 201, "PUT"); + + // Search the index + reporter.reset(); + + SearchRequest searchRequest = new SearchRequest(INDEX); + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); + sourceBuilder.query(QueryBuilders.termQuery(FOO, BAR)); + sourceBuilder.from(0); + sourceBuilder.size(5); + searchRequest.source(sourceBuilder); + SearchResponse sr = client.search(searchRequest); + assertThat(sr.getHits().totalHits).isEqualTo(1L); + assertThat(sr.getHits().getAt(0).getSourceAsMap().get(FOO)).isEqualTo(BAR); + + System.out.println(reporter.generateTransactionPayloadJson()); + + spans = reporter.getSpans(); + assertThat(spans).hasSize(1); + Span searchSpan = spans.get(0); + validateSpanContent(searchSpan, String.format("Elasticsearch: GET /%s/_search", INDEX), 200, "GET"); + validateDbContextContent(searchSpan, "{\"from\":0,\"size\":5,\"query\":{\"term\":{\"foo\":{\"value\":\"bar\",\"boost\":1.0}}}}"); + + // Now update and re-search + reporter.reset(); + + Map jsonMap = new HashMap<>(); + jsonMap.put(FOO, BAZ); + UpdateResponse ur = client.update(new UpdateRequest(INDEX, DOC_TYPE, DOC_ID).doc(jsonMap) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)); + assertThat(ur.status().getStatus()).isEqualTo(200); + sr = client.search(new SearchRequest(INDEX)); + assertThat(sr.getHits().getAt(0).getSourceAsMap().get(FOO)).isEqualTo(BAZ); + + System.out.println(reporter.generateTransactionPayloadJson()); + + spans = reporter.getSpans(); + assertThat(spans).hasSize(2); + boolean updateSpanFound = false; + for(Span span: spans) { + if(span.getName().toString().contains("_update")) { + updateSpanFound = true; + break; + } + } + assertThat(updateSpanFound).isTrue(); + + // Finally - delete the document + reporter.reset(); + DeleteResponse dr = client.delete(new DeleteRequest(INDEX, DOC_TYPE, DOC_ID)); + validateSpanContent(spans.get(0), String.format("Elasticsearch: DELETE /%s/%s/%s", INDEX, DOC_TYPE, DOC_ID), 200, "DELETE"); + + System.out.println(reporter.generateTransactionPayloadJson()); + } + + @Test + public void testScenarioAsBulkRequest() throws IOException { + client.bulk(new BulkRequest() + .add(new IndexRequest(INDEX, DOC_TYPE, "2").source( + jsonBuilder() + .startObject() + .field(FOO, BAR) + .endObject() + )) + .add(new IndexRequest(INDEX, DOC_TYPE, "3").source( + jsonBuilder() + .startObject() + .field(FOO, BAR) + .endObject() + )) + ); + + System.out.println(reporter.generateTransactionPayloadJson()); + + List spans = reporter.getSpans(); + assertThat(spans).hasSize(1); + assertThat(spans.get(0).getName().toString()).isEqualTo("Elasticsearch: POST /_bulk"); + } +} diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/pom.xml new file mode 100644 index 0000000000..2ab719fe24 --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + apm-es-restclient-plugin + co.elastic.apm + 0.9.0-SNAPSHOT + + + apm-es-restclient-plugin-6_4 + ${project.groupId}:${project.artifactId} + + + 6.4.1 + + + + + + org.elasticsearch.client + elasticsearch-rest-client + ${version.elasticsearch} + provided + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + ${version.elasticsearch} + provided + + + + + fr.pilato.elasticsearch.testcontainers + testcontainers-elasticsearch + 0.1 + test + + + diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/ESRestClientInstrumentationHelper.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/ESRestClientInstrumentationHelper.java new file mode 100644 index 0000000000..ccd4c321aa --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/ESRestClientInstrumentationHelper.java @@ -0,0 +1,65 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * Licensed 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. + * #L% + */ +package co.elastic.apm.es.restclient.v6_4; + +import co.elastic.apm.bci.VisibleForAdvice; +import co.elastic.apm.report.serialize.DslJsonSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +@VisibleForAdvice +public class ESRestClientInstrumentationHelper { + private static final Logger logger = LoggerFactory.getLogger(ESRestClientInstrumentationHelper.class); + + private static ThreadLocal bodyReadBuffer = new ThreadLocal<>(); + private static final byte CURLY_BRACKET_UTF8 = '{'; + + @Nullable + @VisibleForAdvice + public static String readRequestBody(InputStream bodyIS, String endpoint) throws IOException { + String body = null; + try { + byte[] data = bodyReadBuffer.get(); + if (data == null) { + // The DslJsonSerializer.MAX_LONG_STRING_VALUE_LENGTH is actually used to count chars and not bytes, but that's not + // that important, the most important is that we limit the payload size we read and decode + data = new byte[DslJsonSerializer.MAX_LONG_STRING_VALUE_LENGTH]; + bodyReadBuffer.set(data); + } + int length = bodyIS.read(data, 0, data.length); + + // read only if UTF8-encoded (based on the first byte being UTF8 of curly bracket char) + if(data[0] == CURLY_BRACKET_UTF8) { + body = new String(data, 0, length, StandardCharsets.UTF_8); + } + } catch (IOException e) { + logger.info("Failed to read request body for " + endpoint); + } finally { + bodyIS.close(); + } + + return body; + } +} diff --git a/apm-agent-plugins/apm-es-restclient-plugin/src/main/java/co/elastic/apm/es/restclient/ElasticsearchRestClientInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/ElasticsearchRestClientInstrumentation.java similarity index 99% rename from apm-agent-plugins/apm-es-restclient-plugin/src/main/java/co/elastic/apm/es/restclient/ElasticsearchRestClientInstrumentation.java rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/ElasticsearchRestClientInstrumentation.java index 8bd28ddd7c..a290d971f9 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/src/main/java/co/elastic/apm/es/restclient/ElasticsearchRestClientInstrumentation.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/ElasticsearchRestClientInstrumentation.java @@ -17,7 +17,7 @@ * limitations under the License. * #L% */ -package co.elastic.apm.es.restclient; +package co.elastic.apm.es.restclient.v6_4; import co.elastic.apm.bci.ElasticApmInstrumentation; import co.elastic.apm.bci.VisibleForAdvice; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/package-info.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/package-info.java new file mode 100644 index 0000000000..cde3b1be0d --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/es/restclient/v6_4/package-info.java @@ -0,0 +1,23 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * Licensed 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. + * #L% + */ +@NonnullApi +package co.elastic.apm.es.restclient.v6_4; + +import co.elastic.apm.annotation.NonnullApi; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation new file mode 100644 index 0000000000..b3ee759511 --- /dev/null +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation @@ -0,0 +1 @@ +co.elastic.apm.es.restclient.v6_4.ElasticsearchRestClientInstrumentation diff --git a/apm-agent-plugins/apm-es-restclient-plugin/src/test/java/co/elastic/apm/es/restclient/ElasticsearchRestClientInstrumentationIT.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/es/restclient/v6_4/ElasticsearchRestClientInstrumentationIT.java similarity index 97% rename from apm-agent-plugins/apm-es-restclient-plugin/src/test/java/co/elastic/apm/es/restclient/ElasticsearchRestClientInstrumentationIT.java rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/es/restclient/v6_4/ElasticsearchRestClientInstrumentationIT.java index 77894fff11..0b887ba5f2 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/src/test/java/co/elastic/apm/es/restclient/ElasticsearchRestClientInstrumentationIT.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/es/restclient/v6_4/ElasticsearchRestClientInstrumentationIT.java @@ -17,7 +17,7 @@ * limitations under the License. * #L% */ -package co.elastic.apm.es.restclient; +package co.elastic.apm.es.restclient.v6_4; import co.elastic.apm.AbstractInstrumentationTest; import co.elastic.apm.impl.error.ErrorCapture; @@ -60,9 +60,9 @@ import java.util.List; import java.util.Map; -import static co.elastic.apm.es.restclient.ElasticsearchRestClientInstrumentation.SEARCH_QUERY_PATH_SUFFIX; -import static co.elastic.apm.es.restclient.ElasticsearchRestClientInstrumentation.SPAN_TYPE; -import static co.elastic.apm.es.restclient.ElasticsearchRestClientInstrumentation.DB_CONTEXT_TYPE; +import static co.elastic.apm.es.restclient.v6_4.ElasticsearchRestClientInstrumentation.SEARCH_QUERY_PATH_SUFFIX; +import static co.elastic.apm.es.restclient.v6_4.ElasticsearchRestClientInstrumentation.SPAN_TYPE; +import static co.elastic.apm.es.restclient.v6_4.ElasticsearchRestClientInstrumentation.DB_CONTEXT_TYPE; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.assertj.core.api.Assertions.assertThat; @@ -175,7 +175,7 @@ private void validateHttpContextContent(Http http, int statusCode, String method assertThat(http).isNotNull(); assertThat(http.getMethod()).isEqualTo(method); assertThat(http.getStatusCode()).isEqualTo(statusCode); - assertThat(http.getUrl()).isEqualTo(container.getHost().toURI().toString()); + assertThat(http.getUrl()).isEqualTo(container.getHost().toURI()); } private void validateDbContextContent(Span span, String statement) { diff --git a/apm-agent-plugins/apm-es-restclient-plugin/src/test/java/co/elastic/apm/es/restclient/ElasticsearchRestClientInstrumentationIT_RealReporter.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/es/restclient/v6_4/ElasticsearchRestClientInstrumentationIT_RealReporter.java similarity index 99% rename from apm-agent-plugins/apm-es-restclient-plugin/src/test/java/co/elastic/apm/es/restclient/ElasticsearchRestClientInstrumentationIT_RealReporter.java rename to apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/es/restclient/v6_4/ElasticsearchRestClientInstrumentationIT_RealReporter.java index 8d6f2f980f..12f95c2303 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/src/test/java/co/elastic/apm/es/restclient/ElasticsearchRestClientInstrumentationIT_RealReporter.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/es/restclient/v6_4/ElasticsearchRestClientInstrumentationIT_RealReporter.java @@ -17,7 +17,7 @@ * limitations under the License. * #L% */ -package co.elastic.apm.es.restclient; +package co.elastic.apm.es.restclient.v6_4; import co.elastic.apm.bci.ElasticApmAgent; import co.elastic.apm.configuration.SpyConfiguration; diff --git a/apm-agent-plugins/apm-es-restclient-plugin/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/pom.xml index 6c617369f7..0bade6de0c 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/pom.xml +++ b/apm-agent-plugins/apm-es-restclient-plugin/pom.xml @@ -8,36 +8,14 @@ 0.9.0-SNAPSHOT 4.0.0 + pom + + + apm-es-restclient-plugin-5_6 + apm-es-restclient-plugin-6_4 + apm-es-restclient-plugin ${project.groupId}:${project.artifactId} - - 6.4.1 - - - - - - org.elasticsearch.client - elasticsearch-rest-client - ${version.elasticsearch} - provided - - - org.elasticsearch.client - elasticsearch-rest-high-level-client - ${version.elasticsearch} - provided - - - - - fr.pilato.elasticsearch.testcontainers - testcontainers-elasticsearch - 0.1 - test - - - diff --git a/apm-agent-plugins/apm-es-restclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation b/apm-agent-plugins/apm-es-restclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation deleted file mode 100644 index 72158a4de3..0000000000 --- a/apm-agent-plugins/apm-es-restclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.bci.ElasticApmInstrumentation +++ /dev/null @@ -1 +0,0 @@ -co.elastic.apm.es.restclient.ElasticsearchRestClientInstrumentation diff --git a/elastic-apm-agent/pom.xml b/elastic-apm-agent/pom.xml index 8d4573d313..0718c75e44 100644 --- a/elastic-apm-agent/pom.xml +++ b/elastic-apm-agent/pom.xml @@ -173,7 +173,12 @@ ${project.groupId} - apm-es-restclient-plugin + apm-es-restclient-plugin-5_6 + ${project.version} + + + ${project.groupId} + apm-es-restclient-plugin-6_4 ${project.version} From e321ac8cceae78e4a7cbc5da185aa18c70c730ed Mon Sep 17 00:00:00 2001 From: eyalkoren Date: Sun, 4 Nov 2018 12:46:18 +0200 Subject: [PATCH 2/4] Using activeSpan() API --- .../restclient/v5_6/ElasticsearchRestClientInstrumentation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentation.java index e72e2eb0b6..36f4807194 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentation.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/es/restclient/v5_6/ElasticsearchRestClientInstrumentation.java @@ -62,7 +62,7 @@ private static void onBeforeExecute(@Advice.Argument(0) String method, if (tracer == null) { return; } - final AbstractSpan activeSpan = tracer.getActive(); + final AbstractSpan activeSpan = tracer.activeSpan(); if (activeSpan == null || !activeSpan.isSampled()) { return; } From a0f4cbdd6dc71301908352d53b70ae5e57bb6629 Mon Sep 17 00:00:00 2001 From: eyalkoren Date: Sun, 4 Nov 2018 12:48:58 +0200 Subject: [PATCH 3/4] Fixing a doc typo --- docs/configuration.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 9d960e5ca3..471612b959 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -1109,7 +1109,7 @@ The default unit for this option is `ms` # # Events like transactions and spans are buffered when the agent can't keep up with sending them to the APM Server or if the APM server is down. # -# If the queue is full, events are rejected which means you will transactions and spans in that case. +# If the queue is full, events are rejected which means you will lose transactions and spans in that case. # This guards the application from crashing in case the APM server is unavailable for a longer period of time. # # A lower value will decrease the heap overhead of the agent, From c33e493da49b114c42525e6cffbbb151724beee7 Mon Sep 17 00:00:00 2001 From: eyalkoren Date: Sun, 4 Nov 2018 13:17:28 +0200 Subject: [PATCH 4/4] Adding header --- .../plugin/api/ApiScopeInstrumentation.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/plugin/api/ApiScopeInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/plugin/api/ApiScopeInstrumentation.java index 8f059c7a4d..957bc4786a 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/plugin/api/ApiScopeInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/plugin/api/ApiScopeInstrumentation.java @@ -1,3 +1,22 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 Elastic and contributors + * %% + * Licensed 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. + * #L% + */ package co.elastic.apm.plugin.api; import co.elastic.apm.bci.ElasticApmInstrumentation;