Skip to content

Commit be3870b

Browse files
Add option elasticsearch capture body urls (#3091)
* add elasticsearch_capture_body_urls option * add version added tag for doc * update generated doc * add apm-es-restclient-plugin-common dependency to fix PackagingTest failure * fix doc break and add changelog * Update CHANGELOG.asciidoc Co-authored-by: SylvainJuge <[email protected]> --------- Co-authored-by: SylvainJuge <[email protected]>
1 parent b6c6436 commit be3870b

File tree

8 files changed

+216
-65
lines changed

8 files changed

+216
-65
lines changed

CHANGELOG.asciidoc

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ communication - {pull}2996[#2996]
4141
* Add the <<config-long-field-max-length>> config to enable capturing larger values for specific fields - {pull}3027[#3027]
4242
* Provide fallback correlation when `ecs-logging-java` is used - {pull}3064[#3064]
4343
* Added separate Java 8 build with updated log4j2 - {pull}3076[#3076]
44+
* Add <<config-elasticsearch-capture-body-urls, elasticsearch_capture_body_urls>> option to customize which Elasticsearch request bodies are captured - {pull}3091[#3091]
4445
4546
[float]
4647
===== Bug fixes

apm-agent-builds/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@
7979
<artifactId>apm-dubbo-plugin</artifactId>
8080
<version>${project.version}</version>
8181
</dependency>
82+
<dependency>
83+
<groupId>${project.groupId}</groupId>
84+
<artifactId>apm-es-restclient-plugin-common</artifactId>
85+
<version>${project.version}</version>
86+
</dependency>
8287
<dependency>
8388
<groupId>${project.groupId}</groupId>
8489
<artifactId>apm-es-restclient-plugin-5_6</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.esrestclient;
20+
21+
import co.elastic.apm.agent.common.util.WildcardMatcher;
22+
import co.elastic.apm.agent.matcher.WildcardMatcherValueConverter;
23+
import org.stagemonitor.configuration.ConfigurationOption;
24+
import org.stagemonitor.configuration.ConfigurationOptionProvider;
25+
import org.stagemonitor.configuration.converter.ListValueConverter;
26+
27+
import java.util.Arrays;
28+
import java.util.List;
29+
30+
public class ElasticsearchConfiguration extends ConfigurationOptionProvider {
31+
private final ConfigurationOption<List<WildcardMatcher>> captureBodyUrls = ConfigurationOption
32+
.builder(new ListValueConverter<>(new WildcardMatcherValueConverter()), List.class)
33+
.key("elasticsearch_capture_body_urls")
34+
.configurationCategory("Datastore")
35+
.description("The URL path patterns for which the APM agent will capture the request body of outgoing requests to Elasticsearch made with the `elasticsearch-restclient` instrumentation. The default setting captures the body for Elasticsearch REST APIs searches and counts.\n" +
36+
"\n" +
37+
"The captured request body (if any) is stored on the `span.db.statement` field. Captured request bodies are truncated to a maximum length defined by <<config-long-field-max-length>>." +
38+
"\n" +
39+
WildcardMatcher.DOCUMENTATION
40+
)
41+
.tags("added[1.37.0]")
42+
.dynamic(true)
43+
.buildWithDefault(Arrays.asList(
44+
WildcardMatcher.valueOf("*_search"),
45+
WildcardMatcher.valueOf("*_msearch"),
46+
WildcardMatcher.valueOf("*_msearch/template"),
47+
WildcardMatcher.valueOf("*_search/template"),
48+
WildcardMatcher.valueOf("*_count"),
49+
WildcardMatcher.valueOf("*_sql"),
50+
WildcardMatcher.valueOf("*_eql/search"),
51+
WildcardMatcher.valueOf("*_async_search")
52+
));
53+
54+
public List<WildcardMatcher> getCaptureBodyUrls() {
55+
return captureBodyUrls.get();
56+
}
57+
}

apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/main/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelper.java

+4-8
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,24 @@ public class ElasticsearchRestClientInstrumentationHelper {
4848
private static final Logger unsupportedOperationOnceLogger = LoggerUtils.logOnce(logger);
4949
private static final ElasticsearchRestClientInstrumentationHelper INSTANCE = new ElasticsearchRestClientInstrumentationHelper(GlobalTracer.get());
5050

51-
public static final List<WildcardMatcher> QUERY_WILDCARD_MATCHERS = Arrays.asList(
52-
WildcardMatcher.valueOf("*_search"),
53-
WildcardMatcher.valueOf("*_msearch"),
54-
WildcardMatcher.valueOf("*_msearch/template"),
55-
WildcardMatcher.valueOf("*_search/template"),
56-
WildcardMatcher.valueOf("*_count"));
5751
public static final String SPAN_TYPE = "db";
5852
public static final String ELASTICSEARCH = "elasticsearch";
5953
public static final String SPAN_ACTION = "request";
6054
private static final int MAX_POOLED_ELEMENTS = 256;
6155
private final Tracer tracer;
56+
private final ElasticsearchConfiguration config;
6257

6358
private final ObjectPool<ResponseListenerWrapper> responseListenerObjectPool;
6459

6560
public static ElasticsearchRestClientInstrumentationHelper get() {
6661
return INSTANCE;
6762
}
6863

64+
6965
private ElasticsearchRestClientInstrumentationHelper(Tracer tracer) {
7066
this.tracer = tracer;
7167
this.responseListenerObjectPool = tracer.getObjectPoolFactory().createRecyclableObjectPool(MAX_POOLED_ELEMENTS, new ResponseListenerAllocator());
68+
this.config = tracer.getConfig(ElasticsearchConfiguration.class);
7269
}
7370

7471
private class ResponseListenerAllocator implements Allocator<ResponseListenerWrapper> {
@@ -99,10 +96,9 @@ public Span<?> createClientSpan(String method, String endpoint, @Nullable HttpEn
9996
span.getContext().getDb().withType(ELASTICSEARCH);
10097
span.getContext().getServiceTarget().withType(ELASTICSEARCH);
10198
span.activate();
102-
10399
if (span.isSampled()) {
104100
span.getContext().getHttp().withMethod(method);
105-
if (WildcardMatcher.isAnyMatch(QUERY_WILDCARD_MATCHERS, endpoint)) {
101+
if (WildcardMatcher.isAnyMatch(config.getCaptureBodyUrls(), endpoint)) {
106102
if (httpEntity != null && httpEntity.isRepeatable()) {
107103
try {
108104
IOUtils.readUtf8Stream(httpEntity.getContent(), span.getContext().getDb().withStatementBuffer());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
co.elastic.apm.agent.esrestclient.ElasticsearchConfiguration

apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/esrestclient/ElasticsearchRestClientInstrumentationHelperTest.java

+47
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,25 @@
1919
package co.elastic.apm.agent.esrestclient;
2020

2121
import co.elastic.apm.agent.AbstractInstrumentationTest;
22+
import co.elastic.apm.agent.common.util.WildcardMatcher;
23+
import co.elastic.apm.agent.impl.context.Db;
2224
import co.elastic.apm.agent.impl.transaction.Span;
2325
import co.elastic.apm.agent.impl.transaction.Transaction;
2426
import co.elastic.apm.agent.impl.transaction.TransactionTest;
2527
import org.apache.http.HttpHost;
2628
import org.apache.http.HttpVersion;
29+
import org.apache.http.entity.ByteArrayEntity;
2730
import org.apache.http.message.BasicStatusLine;
31+
import org.assertj.core.api.Assertions;
2832
import org.elasticsearch.client.Response;
2933
import org.junit.jupiter.api.AfterEach;
3034
import org.junit.jupiter.api.BeforeEach;
3135
import org.junit.jupiter.api.Test;
3236

37+
import java.util.List;
3338
import java.util.Map;
3439

40+
import static co.elastic.apm.agent.esrestclient.ElasticsearchRestClientInstrumentationHelper.ELASTICSEARCH;
3541
import static co.elastic.apm.agent.testutils.assertions.Assertions.assertThat;
3642
import static org.mockito.ArgumentMatchers.any;
3743
import static org.mockito.Mockito.doAnswer;
@@ -132,4 +138,45 @@ void testNonSampledSpan() {
132138
esSpan.deactivate().end();
133139
}
134140
}
141+
142+
143+
@Test
144+
public void testCaptureBodyUrls() throws Exception {
145+
testCaptureBodyUrls(false);
146+
testCaptureBodyUrls(true);
147+
}
148+
149+
public void testCaptureBodyUrls(boolean captureEverything) throws Exception {
150+
if (captureEverything) {
151+
doReturn(List.of(WildcardMatcher.valueOf("*")))
152+
.when(config.getConfig(ElasticsearchConfiguration.class))
153+
.getCaptureBodyUrls();
154+
assertThat(config.getConfig(ElasticsearchConfiguration.class).getCaptureBodyUrls()).hasSize(1);
155+
} else {
156+
assertThat(config.getConfig(ElasticsearchConfiguration.class).getCaptureBodyUrls()).hasSizeGreaterThan(5);
157+
}
158+
159+
Span span = (Span) helper.createClientSpan("GET", "/_test",
160+
new ByteArrayEntity(new byte[0]));
161+
assertThat(span).isNotNull();
162+
assertThat(tracer.getActive()).isEqualTo(span);
163+
164+
Response response = mockResponse(Map.of());
165+
helper.finishClientSpan(response, span, null);
166+
span.deactivate();
167+
168+
assertThat(tracer.getActive()).isEqualTo(transaction);
169+
assertThat(span).hasType("db").hasSubType("elasticsearch");
170+
assertThat(span.getContext().getServiceTarget())
171+
.hasType("elasticsearch")
172+
.hasNoName();
173+
174+
Db db = span.getContext().getDb();
175+
Assertions.assertThat(db.getType()).isEqualTo(ELASTICSEARCH);
176+
if (captureEverything) {
177+
assertThat((CharSequence) db.getStatementBuffer()).isNotNull();
178+
} else {
179+
assertThat((CharSequence) db.getStatementBuffer()).isNull();
180+
}
181+
}
135182
}

apm-agent-plugins/apm-mongodb/apm-mongodb-common/src/main/java/co/elastic/apm/agent/mongodb/MongoConfiguration.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class MongoConfiguration extends ConfigurationOptionProvider {
3232
private final ConfigurationOption<List<WildcardMatcher>> captureStatementCommands = ConfigurationOption
3333
.builder(new ListValueConverter<>(new WildcardMatcherValueConverter()), List.class)
3434
.key("mongodb_capture_statement_commands")
35-
.configurationCategory("MongoDB")
35+
.configurationCategory("Datastore")
3636
.description("MongoDB command names for which the command document will be captured, limited to common read-only operations by default.\n" +
3737
"Set to ` \"\"` (empty) to disable capture, and `\"*\"` to capture all (which is discouraged as it may lead to sensitive information capture).\n" +
3838
"\n" +

0 commit comments

Comments
 (0)