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;
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..36f4807194
--- /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.activeSpan();
+ 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 super TypeDescription> 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 super MethodDescription> 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/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,
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}