Skip to content

Commit afb5a73

Browse files
committed
support multiple yaml files (for profiles)
1 parent 003fd44 commit afb5a73

File tree

2 files changed

+299
-52
lines changed

2 files changed

+299
-52
lines changed

instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFile.java

Lines changed: 85 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,84 +8,117 @@
88
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration;
99
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
1010
import java.io.ByteArrayInputStream;
11-
import java.io.IOException;
12-
import java.io.InputStream;
1311
import java.nio.charset.StandardCharsets;
12+
import java.util.ArrayList;
13+
import java.util.HashMap;
14+
import java.util.List;
1415
import java.util.Map;
1516
import java.util.regex.Matcher;
1617
import java.util.regex.Pattern;
17-
import javax.annotation.Nullable;
1818
import org.snakeyaml.engine.v2.api.Dump;
1919
import org.snakeyaml.engine.v2.api.DumpSettings;
20-
import org.snakeyaml.engine.v2.api.Load;
21-
import org.snakeyaml.engine.v2.api.LoadSettings;
22-
import org.springframework.boot.env.OriginTrackedMapPropertySource;
2320
import org.springframework.core.env.ConfigurableEnvironment;
21+
import org.springframework.core.env.EnumerablePropertySource;
22+
import org.springframework.core.env.MutablePropertySources;
2423
import org.springframework.core.env.PropertySource;
2524

2625
class EmbeddedConfigFile {
2726

27+
static final Pattern ARRAY_PATTERN = Pattern.compile("(.+)\\[(\\d+)\\]");
28+
2829
private EmbeddedConfigFile() {
2930
// Utility class
3031
}
3132

32-
private static final Pattern PATTERN =
33-
Pattern.compile(
34-
"^Config resource 'class path resource \\[(.+)]' via location 'optional:classpath:/'$");
33+
static OpenTelemetryConfigurationModel extractModel(ConfigurableEnvironment environment) {
34+
MutablePropertySources propertySources = environment.getPropertySources();
3535

36-
static OpenTelemetryConfigurationModel extractModel(ConfigurableEnvironment environment)
37-
throws IOException {
38-
for (PropertySource<?> propertySource : environment.getPropertySources()) {
39-
if (propertySource instanceof OriginTrackedMapPropertySource) {
40-
return getModel(environment, (OriginTrackedMapPropertySource) propertySource);
36+
Map<String, Object> props = new HashMap<>();
37+
for (PropertySource<?> propertySource : propertySources) {
38+
if (propertySource instanceof EnumerablePropertySource<?>) {
39+
for (String propertyName :
40+
((EnumerablePropertySource<?>) propertySource).getPropertyNames()) {
41+
if (propertyName.startsWith("otel.")) {
42+
props.put(
43+
propertyName.substring("otel.".length()), environment.getProperty(propertyName));
44+
}
45+
}
4146
}
4247
}
43-
throw new IllegalStateException("No application.yaml file found.");
44-
}
4548

46-
private static OpenTelemetryConfigurationModel getModel(
47-
ConfigurableEnvironment environment, OriginTrackedMapPropertySource source)
48-
throws IOException {
49-
Matcher matcher = PATTERN.matcher(source.getName());
50-
if (matcher.matches()) {
51-
String file = matcher.group(1);
52-
53-
try (InputStream resourceAsStream =
54-
environment.getClass().getClassLoader().getResourceAsStream(file)) {
55-
if (resourceAsStream != null) {
56-
return extractOtelConfigFile(resourceAsStream);
57-
} else {
58-
throw new IllegalStateException("Unable to load " + file + " in the classpath.");
59-
}
60-
}
49+
Map<String, Object> nestedProps = convertFlatPropsToNested(props);
50+
if (nestedProps.isEmpty()) {
51+
throw new IllegalStateException("No application.yaml file found.");
6152
} else {
62-
throw new IllegalStateException(
63-
"No OpenTelemetry configuration found in the application.yaml file.");
53+
Dump dump = new Dump(DumpSettings.builder().build());
54+
return DeclarativeConfiguration.parse(
55+
new ByteArrayInputStream(
56+
dump.dumpToString(nestedProps).getBytes(StandardCharsets.UTF_8)));
6457
}
6558
}
6659

67-
@Nullable
60+
/**
61+
* Convert flat property map to nested structure. e.g. "otel.instrumentation.java.list[0]" = "one"
62+
* and "otel.instrumentation.java.list[1]" = "two" becomes: {otel: {instrumentation: {java: {list:
63+
* ["one", "two"]}}}}
64+
*/
6865
@SuppressWarnings("unchecked")
69-
private static String parseOtelNode(InputStream in) {
70-
Load load = new Load(LoadSettings.builder().build());
71-
Dump dump = new Dump(DumpSettings.builder().build());
72-
for (Object o : load.loadAllFromInputStream(in)) {
73-
Map<String, Object> data = (Map<String, Object>) o;
74-
Map<String, Map<String, Object>> otel = (Map<String, Map<String, Object>>) data.get("otel");
75-
if (otel != null) {
76-
return dump.dumpToString(otel);
77-
}
78-
}
79-
throw new IllegalStateException("No 'otel' configuration found in the YAML file.");
80-
}
66+
static Map<String, Object> convertFlatPropsToNested(Map<String, Object> flatProps) {
67+
Map<String, Object> result = new HashMap<>();
8168

82-
private static OpenTelemetryConfigurationModel extractOtelConfigFile(InputStream content) {
83-
String node = parseOtelNode(content);
84-
if (node == null || node.isEmpty()) {
85-
throw new IllegalStateException("otel node is empty or null in the YAML file.");
69+
for (Map.Entry<String, Object> entry : flatProps.entrySet()) {
70+
String key = entry.getKey();
71+
Object value = entry.getValue();
72+
73+
// Split the key by dots
74+
String[] parts = key.split("\\.");
75+
Map<String, Object> current = result;
76+
77+
for (int i = 0; i < parts.length; i++) {
78+
String part = parts[i];
79+
boolean isLast = (i == parts.length - 1);
80+
81+
// Check if this part contains an array index like "list[0]"
82+
Matcher matcher = ARRAY_PATTERN.matcher(part);
83+
if (matcher.matches()) {
84+
String arrayName = matcher.group(1);
85+
int index = Integer.parseInt(matcher.group(2));
86+
87+
// Get or create the list
88+
if (!current.containsKey(arrayName)) {
89+
current.put(arrayName, new ArrayList<>());
90+
}
91+
List<Object> list = (List<Object>) current.get(arrayName);
92+
93+
// Ensure the list is large enough
94+
while (list.size() <= index) {
95+
list.add(null);
96+
}
97+
98+
if (isLast) {
99+
list.set(index, value);
100+
} else {
101+
// Need to create a nested map at this index
102+
if (list.get(index) == null) {
103+
list.set(index, new HashMap<String, Object>());
104+
}
105+
current = (Map<String, Object>) list.get(index);
106+
}
107+
} else {
108+
// Regular property (not an array)
109+
if (isLast) {
110+
current.put(part, value);
111+
} else {
112+
// Need to create a nested map
113+
if (!current.containsKey(part)) {
114+
current.put(part, new HashMap<String, Object>());
115+
}
116+
current = (Map<String, Object>) current.get(part);
117+
}
118+
}
119+
}
86120
}
87121

88-
return DeclarativeConfiguration.parse(
89-
new ByteArrayInputStream(node.getBytes(StandardCharsets.UTF_8)));
122+
return result;
90123
}
91124
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.autoconfigure;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
import org.junit.jupiter.api.Test;
14+
15+
class EmbeddedConfigFileTest {
16+
17+
@Test
18+
void convertFlatPropsToNested_simpleProperties() {
19+
Map<String, Object> flatProps = new HashMap<>();
20+
flatProps.put("resource.service.name", "my-service");
21+
flatProps.put("traces.exporter", "otlp");
22+
23+
Map<String, Object> result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps);
24+
25+
assertThat(result)
26+
.containsOnlyKeys("resource", "traces")
27+
.satisfies(
28+
map -> {
29+
assertThat(map.get("resource"))
30+
.isInstanceOf(Map.class)
31+
.satisfies(
32+
resource -> {
33+
@SuppressWarnings("unchecked")
34+
Map<String, Object> resourceMap = (Map<String, Object>) resource;
35+
assertThat(resourceMap)
36+
.containsOnlyKeys("service")
37+
.satisfies(
38+
m -> {
39+
@SuppressWarnings("unchecked")
40+
Map<String, Object> serviceMap =
41+
(Map<String, Object>) m.get("service");
42+
assertThat(serviceMap).containsEntry("name", "my-service");
43+
});
44+
});
45+
assertThat(map.get("traces"))
46+
.isInstanceOf(Map.class)
47+
.satisfies(
48+
traces -> {
49+
@SuppressWarnings("unchecked")
50+
Map<String, Object> tracesMap = (Map<String, Object>) traces;
51+
assertThat(tracesMap).containsEntry("exporter", "otlp");
52+
});
53+
});
54+
}
55+
56+
@Test
57+
void convertFlatPropsToNested_arrayProperties() {
58+
Map<String, Object> flatProps = new HashMap<>();
59+
flatProps.put("instrumentation.java.list[0]", "one");
60+
flatProps.put("instrumentation.java.list[1]", "two");
61+
flatProps.put("instrumentation.java.list[2]", "three");
62+
63+
Map<String, Object> result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps);
64+
65+
assertThat(result)
66+
.containsOnlyKeys("instrumentation")
67+
.satisfies(
68+
map -> {
69+
@SuppressWarnings("unchecked")
70+
Map<String, Object> instrumentation =
71+
(Map<String, Object>) map.get("instrumentation");
72+
assertThat(instrumentation)
73+
.containsOnlyKeys("java")
74+
.satisfies(
75+
m -> {
76+
@SuppressWarnings("unchecked")
77+
Map<String, Object> javaMap = (Map<String, Object>) m.get("java");
78+
assertThat(javaMap)
79+
.containsOnlyKeys("list")
80+
.satisfies(
81+
java -> {
82+
@SuppressWarnings("unchecked")
83+
List<Object> list = (List<Object>) java.get("list");
84+
assertThat(list).containsExactly("one", "two", "three");
85+
});
86+
});
87+
});
88+
}
89+
90+
@Test
91+
void convertFlatPropsToNested_mixedPropertiesAndArrays() {
92+
Map<String, Object> flatProps = new HashMap<>();
93+
flatProps.put("resource.service.name", "test-service");
94+
flatProps.put("resource.attributes[0]", "key1=value1");
95+
flatProps.put("resource.attributes[1]", "key2=value2");
96+
flatProps.put("traces.exporter", "otlp");
97+
98+
Map<String, Object> result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps);
99+
100+
assertThat(result)
101+
.containsOnlyKeys("resource", "traces")
102+
.satisfies(
103+
map -> {
104+
@SuppressWarnings("unchecked")
105+
Map<String, Object> resource = (Map<String, Object>) map.get("resource");
106+
assertThat(resource)
107+
.containsKeys("service", "attributes")
108+
.satisfies(
109+
r -> {
110+
@SuppressWarnings("unchecked")
111+
Map<String, Object> service = (Map<String, Object>) r.get("service");
112+
assertThat(service).containsEntry("name", "test-service");
113+
114+
@SuppressWarnings("unchecked")
115+
List<Object> attributes = (List<Object>) r.get("attributes");
116+
assertThat(attributes).containsExactly("key1=value1", "key2=value2");
117+
});
118+
119+
@SuppressWarnings("unchecked")
120+
Map<String, Object> traces = (Map<String, Object>) map.get("traces");
121+
assertThat(traces).containsEntry("exporter", "otlp");
122+
});
123+
}
124+
125+
@Test
126+
void convertFlatPropsToNested_emptyMap() {
127+
Map<String, Object> flatProps = new HashMap<>();
128+
129+
Map<String, Object> result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps);
130+
131+
assertThat(result).isEmpty();
132+
}
133+
134+
@Test
135+
void convertFlatPropsToNested_singleLevelProperty() {
136+
Map<String, Object> flatProps = new HashMap<>();
137+
flatProps.put("enabled", "true");
138+
139+
Map<String, Object> result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps);
140+
141+
assertThat(result).containsEntry("enabled", "true");
142+
}
143+
144+
@Test
145+
void convertFlatPropsToNested_arrayWithGaps() {
146+
Map<String, Object> flatProps = new HashMap<>();
147+
flatProps.put("list[0]", "first");
148+
flatProps.put("list[2]", "third");
149+
150+
Map<String, Object> result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps);
151+
152+
assertThat(result)
153+
.containsOnlyKeys("list")
154+
.satisfies(
155+
map -> {
156+
@SuppressWarnings("unchecked")
157+
List<Object> list = (List<Object>) map.get("list");
158+
assertThat(list).hasSize(3).containsExactly("first", null, "third");
159+
});
160+
}
161+
162+
@Test
163+
void convertFlatPropsToNested_deeplyNestedProperties() {
164+
Map<String, Object> flatProps = new HashMap<>();
165+
flatProps.put("a.b.c.d.e", "deep-value");
166+
167+
Map<String, Object> result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps);
168+
169+
assertThat(result).containsOnlyKeys("a");
170+
@SuppressWarnings("unchecked")
171+
Map<String, Object> a = (Map<String, Object>) result.get("a");
172+
assertThat(a).containsOnlyKeys("b");
173+
@SuppressWarnings("unchecked")
174+
Map<String, Object> b = (Map<String, Object>) a.get("b");
175+
assertThat(b).containsOnlyKeys("c");
176+
@SuppressWarnings("unchecked")
177+
Map<String, Object> c = (Map<String, Object>) b.get("c");
178+
assertThat(c).containsOnlyKeys("d");
179+
@SuppressWarnings("unchecked")
180+
Map<String, Object> d = (Map<String, Object>) c.get("d");
181+
assertThat(d).containsEntry("e", "deep-value");
182+
}
183+
184+
@Test
185+
void convertFlatPropsToNested_nestedArrays() {
186+
Map<String, Object> flatProps = new HashMap<>();
187+
flatProps.put("outer[0].inner[0]", "value1");
188+
flatProps.put("outer[0].inner[1]", "value2");
189+
flatProps.put("outer[1].inner[0]", "value3");
190+
191+
Map<String, Object> result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps);
192+
193+
assertThat(result)
194+
.containsOnlyKeys("outer")
195+
.satisfies(
196+
map -> {
197+
@SuppressWarnings("unchecked")
198+
List<Object> outer = (List<Object>) map.get("outer");
199+
assertThat(outer).hasSize(2);
200+
201+
@SuppressWarnings("unchecked")
202+
Map<String, Object> firstElement = (Map<String, Object>) outer.get(0);
203+
@SuppressWarnings("unchecked")
204+
List<Object> firstInner = (List<Object>) firstElement.get("inner");
205+
assertThat(firstInner).containsExactly("value1", "value2");
206+
207+
@SuppressWarnings("unchecked")
208+
Map<String, Object> secondElement = (Map<String, Object>) outer.get(1);
209+
@SuppressWarnings("unchecked")
210+
List<Object> secondInner = (List<Object>) secondElement.get("inner");
211+
assertThat(secondInner).containsExactly("value3");
212+
});
213+
}
214+
}

0 commit comments

Comments
 (0)