Skip to content

Commit 1ce75c6

Browse files
ENG-20473 Code changes to support configurable allowed content types
1 parent 39bed2a commit 1ce75c6

File tree

8 files changed

+381
-8
lines changed

8 files changed

+381
-8
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright The Hypertrace Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.hypertrace.agent.core.config;
18+
19+
import java.util.Iterator;
20+
import java.util.ServiceLoader;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
24+
/**
25+
* Interface implemented by classes which can provide the names of the content types which can be
26+
* captured.
27+
*/
28+
public interface DataCaptureConfig {
29+
30+
String[] getAllowedContentTypes();
31+
32+
/** This class return an implementation of the DataCaptureConfig interface */
33+
class ConfigProvider {
34+
private static final Logger logger = LoggerFactory.getLogger(ConfigProvider.class);
35+
private static volatile DataCaptureConfig dataCaptureConfig;
36+
37+
/**
38+
* Locates the first concrete implementation of the DataCaptureConfig interface.
39+
*
40+
* @return
41+
*/
42+
private static DataCaptureConfig load() {
43+
ServiceLoader<DataCaptureConfig> configs = ServiceLoader.load(DataCaptureConfig.class);
44+
Iterator<DataCaptureConfig> iterator = configs.iterator();
45+
if (!iterator.hasNext()) {
46+
logger.error("Failed to load data capture config");
47+
return null;
48+
}
49+
return iterator.next();
50+
}
51+
52+
/**
53+
* @return an implementation of the DataCaptureConfig interface, or null if one cannot be found
54+
*/
55+
public static DataCaptureConfig get() {
56+
if (dataCaptureConfig == null) {
57+
synchronized (ConfigProvider.class) {
58+
if (dataCaptureConfig == null) {
59+
dataCaptureConfig = load();
60+
}
61+
}
62+
}
63+
return dataCaptureConfig;
64+
}
65+
}
66+
}

javaagent-core/src/main/java/org/hypertrace/agent/core/instrumentation/utils/ContentTypeUtils.java

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,80 @@
1818

1919
import com.fasterxml.jackson.core.JsonProcessingException;
2020
import com.fasterxml.jackson.databind.ObjectMapper;
21+
import org.hypertrace.agent.core.config.DataCaptureConfig;
2122

2223
public class ContentTypeUtils {
24+
private final String[] collectableContentTypes;
2325

24-
private ContentTypeUtils() {}
26+
private static final String[] DEFAULT_CONTENT_TYPES =
27+
new String[] {"json", "graphql", "xml", "x-www-form-urlencoded"};
28+
29+
public static String[] getDefaultContentTypes() {
30+
return DEFAULT_CONTENT_TYPES;
31+
}
32+
33+
/** Loads the set of collectable content types from the DataCaptureConfig. */
34+
private ContentTypeUtils() {
35+
super();
36+
37+
DataCaptureConfig dataCaptureConfig = DataCaptureConfig.ConfigProvider.get();
38+
39+
if (dataCaptureConfig == null) {
40+
collectableContentTypes = DEFAULT_CONTENT_TYPES;
41+
} else {
42+
collectableContentTypes = dataCaptureConfig.getAllowedContentTypes();
43+
}
44+
}
2545

2646
private static final String CHARSET_EQUALS = "charset=";
2747
private static final String SEPARATOR = ";";
2848

49+
private static volatile ContentTypeUtils instance;
50+
51+
private static ContentTypeUtils getInstance() {
52+
if (instance == null) {
53+
return getInstanceSync();
54+
}
55+
return instance;
56+
}
57+
58+
private static synchronized ContentTypeUtils getInstanceSync() {
59+
if (instance == null) {
60+
instance = new ContentTypeUtils();
61+
}
62+
63+
return instance;
64+
}
65+
2966
/**
30-
* Returns true if the request/response with this content type should be captured.
67+
* Determines if the content type should be captured by the agent.
3168
*
32-
* @param contentType request or response content type
33-
* @return whether body with this content type should be captured or not
69+
* @param contentType
70+
* @return
3471
*/
35-
public static boolean shouldCapture(String contentType) {
72+
private boolean shouldCapture_(String contentType) {
3673
if (contentType == null) {
3774
return false;
3875
}
3976
contentType = contentType.toLowerCase();
40-
return contentType.contains("json")
41-
|| contentType.contains("graphql")
42-
|| contentType.contains("x-www-form-urlencoded");
77+
78+
for (String nextValidContentType : collectableContentTypes) {
79+
if (contentType.contains(nextValidContentType)) {
80+
return true;
81+
}
82+
}
83+
84+
return false;
85+
}
86+
87+
/**
88+
* Returns true if the request/response with this content type should be captured.
89+
*
90+
* @param contentType request or response content type
91+
* @return whether body with this content type should be captured or not
92+
*/
93+
public static boolean shouldCapture(String contentType) {
94+
return getInstance().shouldCapture_(contentType);
4395
}
4496

4597
public static String parseCharset(String contentType) {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright The Hypertrace Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.hypertrace.agent.otel.extensions.config;
18+
19+
import com.google.auto.service.AutoService;
20+
import com.google.protobuf.StringValue;
21+
import java.util.List;
22+
import org.hypertrace.agent.config.v1.Config;
23+
import org.hypertrace.agent.core.config.DataCaptureConfig;
24+
25+
/**
26+
* An implementation of the DataCaptureConfig interface, which can provide the names of the content
27+
* types that can be captured.
28+
*/
29+
@AutoService(DataCaptureConfig.class)
30+
public class DataCaptureConfigImpl implements DataCaptureConfig {
31+
32+
private final Config.AgentConfig agentConfig = HypertraceConfig.get();
33+
34+
private final Config.DataCapture dataCaptureConfig =
35+
(agentConfig != null) ? agentConfig.getDataCapture() : null;
36+
37+
private String[] allAllowedContentTypes;
38+
39+
public DataCaptureConfigImpl() {
40+
super();
41+
}
42+
43+
public String[] getAllowedContentTypes() {
44+
if (allAllowedContentTypes != null) {
45+
return allAllowedContentTypes;
46+
}
47+
48+
return loadContentTypes();
49+
}
50+
51+
/**
52+
* Determines the content types that should be captured.
53+
*
54+
* @return an array of the content types
55+
*/
56+
private synchronized String[] loadContentTypes() {
57+
if (allAllowedContentTypes == null) {
58+
if (dataCaptureConfig == null) {
59+
allAllowedContentTypes = new String[0];
60+
} else {
61+
List<StringValue> listOfTypes = dataCaptureConfig.getAllowedContentTypesList();
62+
String[] allAllowedContentTypes = new String[listOfTypes.size()];
63+
int idx = 0;
64+
for (StringValue nextStringValue : listOfTypes) {
65+
allAllowedContentTypes[idx] = nextStringValue.getValue();
66+
idx++;
67+
}
68+
69+
this.allAllowedContentTypes = allAllowedContentTypes;
70+
}
71+
}
72+
73+
return allAllowedContentTypes;
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright The Hypertrace Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.hypertrace.agent.otel.extensions.config;
18+
19+
import com.google.auto.service.AutoService;
20+
import io.opentelemetry.instrumentation.api.config.Config;
21+
import io.opentelemetry.javaagent.extension.AgentListener;
22+
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
23+
import org.hypertrace.agent.core.config.DataCaptureConfig.ConfigProvider;
24+
25+
/** An AgentListener implementation that initializes the DataCaptureConfig.ConfigProvider. */
26+
@AutoService(AgentListener.class)
27+
public class DataCaptureConfigInstaller implements AgentListener {
28+
29+
@Override
30+
public void beforeAgent(
31+
Config config, AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
32+
ConfigProvider.get();
33+
}
34+
}

otel-extensions/src/main/java/org/hypertrace/agent/otel/extensions/config/HypertraceConfig.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,47 @@
3030
import java.io.InputStream;
3131
import java.io.InputStreamReader;
3232
import java.io.Reader;
33+
import java.util.Arrays;
34+
import java.util.Collection;
35+
import java.util.HashSet;
36+
import java.util.List;
37+
import java.util.Set;
38+
import java.util.function.Supplier;
3339
import org.hypertrace.agent.config.v1.Config.AgentConfig;
3440
import org.hypertrace.agent.config.v1.Config.DataCapture;
3541
import org.hypertrace.agent.config.v1.Config.Message;
3642
import org.hypertrace.agent.config.v1.Config.MetricReporterType;
3743
import org.hypertrace.agent.config.v1.Config.PropagationFormat;
3844
import org.hypertrace.agent.config.v1.Config.Reporting;
3945
import org.hypertrace.agent.config.v1.Config.TraceReporterType;
46+
import org.hypertrace.agent.core.instrumentation.utils.ContentTypeUtils;
4047
import org.slf4j.Logger;
4148
import org.slf4j.LoggerFactory;
4249

4350
/** {@link HypertraceConfig} loads a yaml config from file. */
4451
public class HypertraceConfig {
4552

53+
// ---------------------------------------------------
54+
// Represents the default set of content types that can be
55+
// captured, if the content types are not specified in the config.
56+
// ---------------------------------------------------
57+
private static final List<StringValue> DEFAULT_CONTENT_TYPES = initDefaultContentTypes();
58+
4659
private HypertraceConfig() {}
4760

61+
private static List<StringValue> initDefaultContentTypes() {
62+
String[] defaultContentTypes = ContentTypeUtils.getDefaultContentTypes();
63+
64+
StringValue[] defaultContentTypeValues = new StringValue[defaultContentTypes.length];
65+
66+
for (int i = 0; i < defaultContentTypes.length; i++) {
67+
defaultContentTypeValues[i] =
68+
StringValue.newBuilder().setValue(defaultContentTypes[i]).build();
69+
}
70+
71+
return Arrays.asList(defaultContentTypeValues);
72+
}
73+
4874
private static final Logger log = LoggerFactory.getLogger(HypertraceConfig.class);
4975

5076
// volatile field in order to properly handle lazy initialization with double-checked locking
@@ -169,6 +195,10 @@ private static DataCapture.Builder setDefaultsToDataCapture(DataCapture.Builder
169195
builder.setBodyMaxSizeBytes(
170196
Int32Value.newBuilder().setValue(DEFAULT_BODY_MAX_SIZE_BYTES).build());
171197
}
198+
199+
Collection<StringValue> contentTypeList =
200+
applyListDefaults(builder.getAllowedContentTypesList(), () -> DEFAULT_CONTENT_TYPES);
201+
builder.clearAllowedContentTypes().addAllAllowedContentTypes(contentTypeList);
172202
return builder;
173203
}
174204

@@ -189,4 +219,27 @@ private static String convertYamlToJson(InputStream yaml) throws IOException {
189219
ObjectMapper jsonWriter = new ObjectMapper();
190220
return jsonWriter.writeValueAsString(obj);
191221
}
222+
223+
/**
224+
* Creates a collection of objects consisting of the set of objects specified in the
225+
* configuration, plus the default set of objects.
226+
*
227+
* @param originalList the set of objects specified in the configuration
228+
* @param defaultSupplier a lambda which provides the default set of objects.
229+
* @return a collection of objects, consisting of the union of originalList, with the list
230+
* provided by defaultSupplier
231+
* @param <T>
232+
*/
233+
private static <T> Collection<T> applyListDefaults(
234+
List<T> originalList, Supplier<List<T>> defaultSupplier) {
235+
Set<T> returnSet = new HashSet<>();
236+
237+
if (originalList != null) {
238+
returnSet.addAll(originalList);
239+
}
240+
241+
returnSet.addAll(defaultSupplier.get());
242+
243+
return returnSet;
244+
}
192245
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright The Hypertrace Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.hypertrace.agent.otel.extensions.config;
18+
19+
import static org.junit.jupiter.api.Assertions.*;
20+
21+
import java.util.Iterator;
22+
import java.util.ServiceLoader;
23+
import org.hypertrace.agent.core.config.DataCaptureConfig;
24+
import org.junit.jupiter.api.Test;
25+
26+
public class DataCaptureConfigTest {
27+
28+
@Test
29+
public void instantiateViaServiceLoaderApi() {
30+
final ServiceLoader<DataCaptureConfig> dataCaptureConfigs =
31+
ServiceLoader.load(DataCaptureConfig.class);
32+
final Iterator<DataCaptureConfig> iterator = dataCaptureConfigs.iterator();
33+
assertTrue(iterator.hasNext());
34+
final DataCaptureConfig dataCaptureConfig = iterator.next();
35+
assertNotNull(dataCaptureConfig);
36+
assertFalse(iterator.hasNext());
37+
}
38+
}

0 commit comments

Comments
 (0)