Skip to content

Commit de48d1b

Browse files
zeitlingerotelbot[bot]jaydeluca
authored
Spring starter declarative config (#14062)
Co-authored-by: otelbot <[email protected]> Co-authored-by: Jay DeLuca <[email protected]>
1 parent a8d9bba commit de48d1b

File tree

61 files changed

+1723
-297
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1723
-297
lines changed
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.22.0-SNAPSHOT.jar against opentelemetry-spring-boot-autoconfigure-2.21.0.jar
2-
No changes.
2+
=== UNCHANGED CLASS: PUBLIC io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration (not serializable)
3+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4+
--- REMOVED ANNOTATION: org.springframework.boot.context.properties.EnableConfigurationProperties
5+
--- REMOVED ELEMENT: value=io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties,io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties,io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties (-)

instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ dependencies {
6565
library("org.springframework.boot:spring-boot-starter-data-jdbc:$springBootVersion")
6666

6767
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
68+
implementation("io.opentelemetry:opentelemetry-sdk-extension-incubator")
6869
implementation(project(":sdk-autoconfigure-support"))
70+
implementation(project(":declarative-config-bridge"))
6971
compileOnly("io.opentelemetry:opentelemetry-extension-trace-propagators")
7072
compileOnly("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator")
7173
compileOnly("io.opentelemetry:opentelemetry-exporter-logging")
@@ -80,6 +82,7 @@ dependencies {
8082
testLibrary("org.springframework.boot:spring-boot-starter-test:$springBootVersion") {
8183
exclude("org.junit.vintage", "junit-vintage-engine")
8284
}
85+
8386
testImplementation("javax.servlet:javax.servlet-api:3.1.0")
8487
testImplementation("jakarta.servlet:jakarta.servlet-api:5.0.0")
8588
testRuntimeOnly("com.h2database:h2:1.4.197")
@@ -171,6 +174,17 @@ testing {
171174
}
172175
}
173176
}
177+
178+
val testDeclarativeConfig by registering(JvmTestSuite::class) {
179+
dependencies {
180+
implementation(project())
181+
implementation("io.opentelemetry:opentelemetry-sdk")
182+
implementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion") {
183+
exclude("org.junit.vintage", "junit-vintage-engine")
184+
}
185+
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
186+
}
187+
}
174188
}
175189
}
176190

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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 com.fasterxml.jackson.annotation.JsonSetter;
9+
import com.fasterxml.jackson.annotation.Nulls;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
12+
import java.util.ArrayList;
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
import java.util.Objects;
16+
import java.util.regex.Matcher;
17+
import java.util.regex.Pattern;
18+
import org.springframework.core.env.ConfigurableEnvironment;
19+
import org.springframework.core.env.EnumerablePropertySource;
20+
import org.springframework.core.env.MutablePropertySources;
21+
import org.springframework.core.env.PropertySource;
22+
23+
class EmbeddedConfigFile {
24+
25+
private static final Pattern ARRAY_PATTERN = Pattern.compile("(.+)\\[(\\d+)]$");
26+
27+
// the entire configuration is copied from
28+
// https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfiguration.java#L66-L79
29+
// which is not public
30+
private static final ObjectMapper MAPPER;
31+
32+
static {
33+
MAPPER =
34+
new ObjectMapper()
35+
// Create empty object instances for keys which are present but have null values
36+
.setDefaultSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
37+
// Boxed primitives which are present but have null values should be set to null, rather than
38+
// empty instances
39+
MAPPER.configOverride(String.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET));
40+
MAPPER.configOverride(Integer.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET));
41+
MAPPER.configOverride(Double.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET));
42+
MAPPER.configOverride(Boolean.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.SET));
43+
}
44+
45+
private EmbeddedConfigFile() {}
46+
47+
static OpenTelemetryConfigurationModel extractModel(ConfigurableEnvironment environment) {
48+
Map<String, String> props = extractSpringProperties(environment);
49+
return convertToOpenTelemetryConfigurationModel(props);
50+
}
51+
52+
private static Map<String, String> extractSpringProperties(ConfigurableEnvironment environment) {
53+
MutablePropertySources propertySources = environment.getPropertySources();
54+
55+
Map<String, String> props = new HashMap<>();
56+
for (PropertySource<?> propertySource : propertySources) {
57+
if (propertySource instanceof EnumerablePropertySource<?>) {
58+
for (String propertyName :
59+
((EnumerablePropertySource<?>) propertySource).getPropertyNames()) {
60+
if (propertyName.startsWith("otel.")) {
61+
String property = environment.getProperty(propertyName);
62+
if (Objects.equals(property, "")) {
63+
property = null; // spring returns empty string for yaml null
64+
}
65+
props.put(propertyName.substring("otel.".length()), property);
66+
}
67+
}
68+
}
69+
}
70+
71+
if (props.isEmpty()) {
72+
throw new IllegalStateException(
73+
"No properties found with prefix 'otel.' - this should not happen, because we checked "
74+
+ "'environment.getProperty(\"otel.file_format\", String.class) != null' earlier");
75+
}
76+
return props;
77+
}
78+
79+
static OpenTelemetryConfigurationModel convertToOpenTelemetryConfigurationModel(
80+
Map<String, String> flatProps) {
81+
Map<String, Object> nested = convertFlatPropsToNested(flatProps);
82+
83+
return getObjectMapper().convertValue(nested, OpenTelemetryConfigurationModel.class);
84+
}
85+
86+
static ObjectMapper getObjectMapper() {
87+
return MAPPER;
88+
}
89+
90+
/**
91+
* Convert flat property map to nested structure. e.g. "otel.instrumentation.java.list[0]" = "one"
92+
* and "otel.instrumentation.java.list[1]" = "two" becomes: {otel: {instrumentation: {java: {list:
93+
* ["one", "two"]}}}}
94+
*/
95+
@SuppressWarnings("unchecked")
96+
static Map<String, Object> convertFlatPropsToNested(Map<String, String> flatProps) {
97+
Map<String, Object> result = new HashMap<>();
98+
99+
for (Map.Entry<String, String> entry : flatProps.entrySet()) {
100+
String key = entry.getKey();
101+
String value = entry.getValue();
102+
103+
// Split the key by dots
104+
String[] parts = key.split("\\.");
105+
Map<String, Object> current = result;
106+
107+
for (int i = 0; i < parts.length; i++) {
108+
String part = parts[i];
109+
boolean isLast = (i == parts.length - 1);
110+
111+
// Check if this part contains an array index like "list[0]"
112+
Matcher matcher = ARRAY_PATTERN.matcher(part);
113+
if (matcher.matches()) {
114+
String arrayName = matcher.group(1);
115+
int index = Integer.parseInt(matcher.group(2));
116+
117+
ArrayList<Object> list =
118+
(ArrayList<Object>) current.computeIfAbsent(arrayName, k -> new ArrayList<>());
119+
120+
// Ensure the list is large enough
121+
list.ensureCapacity(index + 1);
122+
while (list.size() <= index) {
123+
list.add(null);
124+
}
125+
126+
if (isLast) {
127+
list.set(index, value);
128+
} else {
129+
// Need to create a nested map at this index
130+
current = (Map<String, Object>) list.get(index);
131+
if (current == null) {
132+
current = new HashMap<>();
133+
list.set(index, current);
134+
}
135+
}
136+
} else {
137+
// Regular property (not an array)
138+
if (isLast) {
139+
current.put(part, value);
140+
} else {
141+
// Need to create a nested map
142+
if (!current.containsKey(part)) {
143+
current.put(part, new HashMap<String, Object>());
144+
}
145+
current = (Map<String, Object>) current.get(part);
146+
}
147+
}
148+
}
149+
}
150+
return result;
151+
}
152+
}

0 commit comments

Comments
 (0)