Skip to content

Commit e73118b

Browse files
authored
Add Spring Boot service version finder / ResourceProvider (#9480)
1 parent 6e7f955 commit e73118b

File tree

8 files changed

+212
-56
lines changed

8 files changed

+212
-56
lines changed

instrumentation/spring/spring-boot-resources/library/src/main/java/io/opentelemetry/instrumentation/spring/resources/SpringBootServiceNameDetector.java

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,8 @@
1616
import io.opentelemetry.semconv.ResourceAttributes;
1717
import java.io.IOException;
1818
import java.io.InputStream;
19-
import java.lang.reflect.Method;
20-
import java.nio.file.Files;
21-
import java.nio.file.Paths;
2219
import java.util.Map;
2320
import java.util.Objects;
24-
import java.util.Optional;
2521
import java.util.Properties;
2622
import java.util.function.Function;
2723
import java.util.function.Supplier;
@@ -286,54 +282,4 @@ private String loadFromClasspath(String filename, Function<InputStream, String>
286282
return null;
287283
}
288284
}
289-
290-
// Exists for testing
291-
static class SystemHelper {
292-
private final ClassLoader classLoader;
293-
private final boolean addBootInfPrefix;
294-
295-
SystemHelper() {
296-
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
297-
classLoader =
298-
contextClassLoader != null ? contextClassLoader : ClassLoader.getSystemClassLoader();
299-
addBootInfPrefix = classLoader.getResource("BOOT-INF/classes/") != null;
300-
if (addBootInfPrefix) {
301-
logger.log(FINER, "Detected presence of BOOT-INF/classes/");
302-
}
303-
}
304-
305-
String getenv(String name) {
306-
return System.getenv(name);
307-
}
308-
309-
String getProperty(String key) {
310-
return System.getProperty(key);
311-
}
312-
313-
InputStream openClasspathResource(String filename) {
314-
String path = addBootInfPrefix ? "BOOT-INF/classes/" + filename : filename;
315-
return classLoader.getResourceAsStream(path);
316-
}
317-
318-
InputStream openFile(String filename) throws Exception {
319-
return Files.newInputStream(Paths.get(filename));
320-
}
321-
322-
/**
323-
* Attempts to use ProcessHandle to get the full commandline of the current process (including
324-
* the main method arguments). Will only succeed on java 9+.
325-
*/
326-
@SuppressWarnings("unchecked")
327-
String[] attemptGetCommandLineArgsViaReflection() throws Exception {
328-
Class<?> clazz = Class.forName("java.lang.ProcessHandle");
329-
Method currentMethod = clazz.getDeclaredMethod("current");
330-
Method infoMethod = clazz.getDeclaredMethod("info");
331-
Object currentInstance = currentMethod.invoke(null);
332-
Object info = infoMethod.invoke(currentInstance);
333-
Class<?> infoClass = Class.forName("java.lang.ProcessHandle$Info");
334-
Method argumentsMethod = infoClass.getMethod("arguments");
335-
Optional<String[]> optionalArgs = (Optional<String[]>) argumentsMethod.invoke(info);
336-
return optionalArgs.orElse(new String[0]);
337-
}
338-
}
339285
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.resources;
7+
8+
import static java.util.logging.Level.FINE;
9+
10+
import com.google.auto.service.AutoService;
11+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
12+
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
13+
import io.opentelemetry.sdk.resources.Resource;
14+
import io.opentelemetry.semconv.ResourceAttributes;
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.util.Optional;
18+
import java.util.Properties;
19+
import java.util.logging.Logger;
20+
21+
@AutoService(ResourceProvider.class)
22+
public class SpringBootServiceVersionDetector implements ResourceProvider {
23+
24+
private static final Logger logger =
25+
Logger.getLogger(SpringBootServiceVersionDetector.class.getName());
26+
27+
private final SystemHelper system;
28+
29+
public SpringBootServiceVersionDetector() {
30+
this.system = new SystemHelper();
31+
}
32+
33+
// Exists for testing
34+
SpringBootServiceVersionDetector(SystemHelper system) {
35+
this.system = system;
36+
}
37+
38+
@Override
39+
public Resource createResource(ConfigProperties config) {
40+
return getServiceVersionFromBuildInfo()
41+
.map(
42+
version -> {
43+
logger.log(FINE, "Auto-detected Spring Boot service version: {0}", version);
44+
return Resource.builder().put(ResourceAttributes.SERVICE_VERSION, version).build();
45+
})
46+
.orElseGet(Resource::empty);
47+
}
48+
49+
private Optional<String> getServiceVersionFromBuildInfo() {
50+
try (InputStream in = system.openClasspathResource("META-INF", "build-info.properties")) {
51+
return in != null ? getServiceVersionPropertyFromStream(in) : Optional.empty();
52+
} catch (Exception e) {
53+
return Optional.empty();
54+
}
55+
}
56+
57+
private static Optional<String> getServiceVersionPropertyFromStream(InputStream in) {
58+
Properties properties = new Properties();
59+
try {
60+
// Note: load() uses ISO 8859-1 encoding, same as spring uses by default for property files
61+
properties.load(in);
62+
return Optional.ofNullable(properties.getProperty("build.version"));
63+
} catch (IOException e) {
64+
return Optional.empty();
65+
}
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.resources;
7+
8+
import java.io.InputStream;
9+
import java.lang.reflect.Method;
10+
import java.nio.file.Files;
11+
import java.nio.file.Paths;
12+
import java.util.Optional;
13+
import java.util.logging.Level;
14+
import java.util.logging.Logger;
15+
16+
class SystemHelper {
17+
private static final Logger logger = Logger.getLogger(SystemHelper.class.getName());
18+
19+
private final ClassLoader classLoader;
20+
private final boolean addBootInfPrefix;
21+
22+
SystemHelper() {
23+
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
24+
classLoader =
25+
contextClassLoader != null ? contextClassLoader : ClassLoader.getSystemClassLoader();
26+
addBootInfPrefix = classLoader.getResource("BOOT-INF/classes/") != null;
27+
if (addBootInfPrefix) {
28+
logger.log(Level.FINER, "Detected presence of BOOT-INF/classes/");
29+
}
30+
}
31+
32+
String getenv(String name) {
33+
return System.getenv(name);
34+
}
35+
36+
String getProperty(String key) {
37+
return System.getProperty(key);
38+
}
39+
40+
InputStream openClasspathResource(String filename) {
41+
String path = addBootInfPrefix ? "BOOT-INF/classes/" + filename : filename;
42+
return classLoader.getResourceAsStream(path);
43+
}
44+
45+
InputStream openClasspathResource(String directory, String filename) {
46+
String path = directory + "/" + filename;
47+
return classLoader.getResourceAsStream(path);
48+
}
49+
50+
InputStream openFile(String filename) throws Exception {
51+
return Files.newInputStream(Paths.get(filename));
52+
}
53+
54+
/**
55+
* Attempts to use ProcessHandle to get the full commandline of the current process (including the
56+
* main method arguments). Will only succeed on java 9+.
57+
*/
58+
@SuppressWarnings("unchecked")
59+
String[] attemptGetCommandLineArgsViaReflection() throws Exception {
60+
Class<?> clazz = Class.forName("java.lang.ProcessHandle");
61+
Method currentMethod = clazz.getDeclaredMethod("current");
62+
Method infoMethod = clazz.getDeclaredMethod("info");
63+
Object currentInstance = currentMethod.invoke(null);
64+
Object info = infoMethod.invoke(currentInstance);
65+
Class<?> infoClass = Class.forName("java.lang.ProcessHandle$Info");
66+
Method argumentsMethod = infoClass.getMethod("arguments");
67+
Optional<String[]> optionalArgs = (Optional<String[]>) argumentsMethod.invoke(info);
68+
return optionalArgs.orElse(new String[0]);
69+
}
70+
}

instrumentation/spring/spring-boot-resources/library/src/test/java/io/opentelemetry/instrumentation/spring/resources/SpringBootServiceNameDetectorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class SpringBootServiceNameDetectorTest {
3333
static final String PROPS = "application.properties";
3434
static final String APPLICATION_YML = "application.yml";
3535
@Mock ConfigProperties config;
36-
@Mock SpringBootServiceNameDetector.SystemHelper system;
36+
@Mock SystemHelper system;
3737

3838
@Test
3939
void findByEnvVar() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.spring.resources;
7+
8+
import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_VERSION;
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
import static org.mockito.Mockito.when;
11+
12+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
13+
import io.opentelemetry.sdk.resources.Resource;
14+
import java.io.InputStream;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.extension.ExtendWith;
17+
import org.mockito.Mock;
18+
import org.mockito.junit.jupiter.MockitoExtension;
19+
20+
@ExtendWith(MockitoExtension.class)
21+
class SpringBootServiceVersionDetectorTest {
22+
23+
static final String BUILD_PROPS = "build-info.properties";
24+
static final String META_INFO = "META-INF";
25+
26+
@Mock ConfigProperties config;
27+
@Mock SystemHelper system;
28+
29+
@Test
30+
void givenBuildVersionIsPresentInBuildInfProperties_thenReturnBuildVersion() {
31+
when(system.openClasspathResource(META_INFO, BUILD_PROPS))
32+
.thenReturn(openClasspathResource(META_INFO + "/" + BUILD_PROPS));
33+
34+
SpringBootServiceVersionDetector guesser = new SpringBootServiceVersionDetector(system);
35+
Resource result = guesser.createResource(config);
36+
assertThat(result.getAttribute(SERVICE_VERSION)).isEqualTo("0.0.2");
37+
}
38+
39+
@Test
40+
void givenBuildVersionFileNotPresent_thenReturnEmptyResource() {
41+
when(system.openClasspathResource(META_INFO, BUILD_PROPS)).thenReturn(null);
42+
43+
SpringBootServiceVersionDetector guesser = new SpringBootServiceVersionDetector(system);
44+
Resource result = guesser.createResource(config);
45+
assertThat(result).isEqualTo(Resource.empty());
46+
}
47+
48+
@Test
49+
void givenBuildVersionFileIsPresentButBuildVersionPropertyNotPresent_thenReturnEmptyResource() {
50+
when(system.openClasspathResource(META_INFO, BUILD_PROPS))
51+
.thenReturn(openClasspathResource(BUILD_PROPS));
52+
53+
SpringBootServiceVersionDetector guesser = new SpringBootServiceVersionDetector(system);
54+
Resource result = guesser.createResource(config);
55+
assertThat(result).isEqualTo(Resource.empty());
56+
}
57+
58+
private InputStream openClasspathResource(String resource) {
59+
return getClass().getClassLoader().getResourceAsStream(resource);
60+
}
61+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
build.artifact=something
2+
build.name=some-name
3+
build.version=0.0.2
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build.artifact=something
2+
build.name=some-name

smoke-tests/src/test/groovy/io/opentelemetry/smoketest/SpringBootSmokeTest.groovy

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import static java.util.stream.Collectors.toSet
2121
class SpringBootSmokeTest extends SmokeTest {
2222

2323
protected String getTargetImage(String jdk) {
24-
"ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-spring-boot:jdk$jdk-20230321.4484174638"
24+
"ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-spring-boot:jdk$jdk-20230920.6251727205"
2525
}
2626

2727
@Override
@@ -94,6 +94,13 @@ class SpringBootSmokeTest extends SmokeTest {
9494
serviceName.isPresent()
9595
serviceName.get() == "otel-spring-test-app"
9696

97+
then: "service version is autodetected"
98+
def serviceVersion = findResourceAttribute(traces, "service.version")
99+
.map { it.stringValue }
100+
.findAny()
101+
serviceVersion.isPresent()
102+
serviceVersion.get() == "1.31.0-alpha-SNAPSHOT"
103+
97104
cleanup:
98105
stopTarget()
99106

0 commit comments

Comments
 (0)