Add Spring Boot service version finder / ResourceProvider (#9480)
This commit is contained in:
parent
6e7f955d67
commit
e73118b434
|
@ -16,12 +16,8 @@ import io.opentelemetry.sdk.resources.Resource;
|
|||
import io.opentelemetry.semconv.ResourceAttributes;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -286,54 +282,4 @@ public class SpringBootServiceNameDetector implements ConditionalResourceProvide
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Exists for testing
|
||||
static class SystemHelper {
|
||||
private final ClassLoader classLoader;
|
||||
private final boolean addBootInfPrefix;
|
||||
|
||||
SystemHelper() {
|
||||
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
classLoader =
|
||||
contextClassLoader != null ? contextClassLoader : ClassLoader.getSystemClassLoader();
|
||||
addBootInfPrefix = classLoader.getResource("BOOT-INF/classes/") != null;
|
||||
if (addBootInfPrefix) {
|
||||
logger.log(FINER, "Detected presence of BOOT-INF/classes/");
|
||||
}
|
||||
}
|
||||
|
||||
String getenv(String name) {
|
||||
return System.getenv(name);
|
||||
}
|
||||
|
||||
String getProperty(String key) {
|
||||
return System.getProperty(key);
|
||||
}
|
||||
|
||||
InputStream openClasspathResource(String filename) {
|
||||
String path = addBootInfPrefix ? "BOOT-INF/classes/" + filename : filename;
|
||||
return classLoader.getResourceAsStream(path);
|
||||
}
|
||||
|
||||
InputStream openFile(String filename) throws Exception {
|
||||
return Files.newInputStream(Paths.get(filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to use ProcessHandle to get the full commandline of the current process (including
|
||||
* the main method arguments). Will only succeed on java 9+.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
String[] attemptGetCommandLineArgsViaReflection() throws Exception {
|
||||
Class<?> clazz = Class.forName("java.lang.ProcessHandle");
|
||||
Method currentMethod = clazz.getDeclaredMethod("current");
|
||||
Method infoMethod = clazz.getDeclaredMethod("info");
|
||||
Object currentInstance = currentMethod.invoke(null);
|
||||
Object info = infoMethod.invoke(currentInstance);
|
||||
Class<?> infoClass = Class.forName("java.lang.ProcessHandle$Info");
|
||||
Method argumentsMethod = infoClass.getMethod("arguments");
|
||||
Optional<String[]> optionalArgs = (Optional<String[]>) argumentsMethod.invoke(info);
|
||||
return optionalArgs.orElse(new String[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.spring.resources;
|
||||
|
||||
import static java.util.logging.Level.FINE;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import io.opentelemetry.semconv.ResourceAttributes;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@AutoService(ResourceProvider.class)
|
||||
public class SpringBootServiceVersionDetector implements ResourceProvider {
|
||||
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(SpringBootServiceVersionDetector.class.getName());
|
||||
|
||||
private final SystemHelper system;
|
||||
|
||||
public SpringBootServiceVersionDetector() {
|
||||
this.system = new SystemHelper();
|
||||
}
|
||||
|
||||
// Exists for testing
|
||||
SpringBootServiceVersionDetector(SystemHelper system) {
|
||||
this.system = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource createResource(ConfigProperties config) {
|
||||
return getServiceVersionFromBuildInfo()
|
||||
.map(
|
||||
version -> {
|
||||
logger.log(FINE, "Auto-detected Spring Boot service version: {0}", version);
|
||||
return Resource.builder().put(ResourceAttributes.SERVICE_VERSION, version).build();
|
||||
})
|
||||
.orElseGet(Resource::empty);
|
||||
}
|
||||
|
||||
private Optional<String> getServiceVersionFromBuildInfo() {
|
||||
try (InputStream in = system.openClasspathResource("META-INF", "build-info.properties")) {
|
||||
return in != null ? getServiceVersionPropertyFromStream(in) : Optional.empty();
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<String> getServiceVersionPropertyFromStream(InputStream in) {
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
// Note: load() uses ISO 8859-1 encoding, same as spring uses by default for property files
|
||||
properties.load(in);
|
||||
return Optional.ofNullable(properties.getProperty("build.version"));
|
||||
} catch (IOException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.spring.resources;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class SystemHelper {
|
||||
private static final Logger logger = Logger.getLogger(SystemHelper.class.getName());
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
private final boolean addBootInfPrefix;
|
||||
|
||||
SystemHelper() {
|
||||
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
classLoader =
|
||||
contextClassLoader != null ? contextClassLoader : ClassLoader.getSystemClassLoader();
|
||||
addBootInfPrefix = classLoader.getResource("BOOT-INF/classes/") != null;
|
||||
if (addBootInfPrefix) {
|
||||
logger.log(Level.FINER, "Detected presence of BOOT-INF/classes/");
|
||||
}
|
||||
}
|
||||
|
||||
String getenv(String name) {
|
||||
return System.getenv(name);
|
||||
}
|
||||
|
||||
String getProperty(String key) {
|
||||
return System.getProperty(key);
|
||||
}
|
||||
|
||||
InputStream openClasspathResource(String filename) {
|
||||
String path = addBootInfPrefix ? "BOOT-INF/classes/" + filename : filename;
|
||||
return classLoader.getResourceAsStream(path);
|
||||
}
|
||||
|
||||
InputStream openClasspathResource(String directory, String filename) {
|
||||
String path = directory + "/" + filename;
|
||||
return classLoader.getResourceAsStream(path);
|
||||
}
|
||||
|
||||
InputStream openFile(String filename) throws Exception {
|
||||
return Files.newInputStream(Paths.get(filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to use ProcessHandle to get the full commandline of the current process (including the
|
||||
* main method arguments). Will only succeed on java 9+.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
String[] attemptGetCommandLineArgsViaReflection() throws Exception {
|
||||
Class<?> clazz = Class.forName("java.lang.ProcessHandle");
|
||||
Method currentMethod = clazz.getDeclaredMethod("current");
|
||||
Method infoMethod = clazz.getDeclaredMethod("info");
|
||||
Object currentInstance = currentMethod.invoke(null);
|
||||
Object info = infoMethod.invoke(currentInstance);
|
||||
Class<?> infoClass = Class.forName("java.lang.ProcessHandle$Info");
|
||||
Method argumentsMethod = infoClass.getMethod("arguments");
|
||||
Optional<String[]> optionalArgs = (Optional<String[]>) argumentsMethod.invoke(info);
|
||||
return optionalArgs.orElse(new String[0]);
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ class SpringBootServiceNameDetectorTest {
|
|||
static final String PROPS = "application.properties";
|
||||
static final String APPLICATION_YML = "application.yml";
|
||||
@Mock ConfigProperties config;
|
||||
@Mock SpringBootServiceNameDetector.SystemHelper system;
|
||||
@Mock SystemHelper system;
|
||||
|
||||
@Test
|
||||
void findByEnvVar() {
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.spring.resources;
|
||||
|
||||
import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_VERSION;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import java.io.InputStream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SpringBootServiceVersionDetectorTest {
|
||||
|
||||
static final String BUILD_PROPS = "build-info.properties";
|
||||
static final String META_INFO = "META-INF";
|
||||
|
||||
@Mock ConfigProperties config;
|
||||
@Mock SystemHelper system;
|
||||
|
||||
@Test
|
||||
void givenBuildVersionIsPresentInBuildInfProperties_thenReturnBuildVersion() {
|
||||
when(system.openClasspathResource(META_INFO, BUILD_PROPS))
|
||||
.thenReturn(openClasspathResource(META_INFO + "/" + BUILD_PROPS));
|
||||
|
||||
SpringBootServiceVersionDetector guesser = new SpringBootServiceVersionDetector(system);
|
||||
Resource result = guesser.createResource(config);
|
||||
assertThat(result.getAttribute(SERVICE_VERSION)).isEqualTo("0.0.2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenBuildVersionFileNotPresent_thenReturnEmptyResource() {
|
||||
when(system.openClasspathResource(META_INFO, BUILD_PROPS)).thenReturn(null);
|
||||
|
||||
SpringBootServiceVersionDetector guesser = new SpringBootServiceVersionDetector(system);
|
||||
Resource result = guesser.createResource(config);
|
||||
assertThat(result).isEqualTo(Resource.empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenBuildVersionFileIsPresentButBuildVersionPropertyNotPresent_thenReturnEmptyResource() {
|
||||
when(system.openClasspathResource(META_INFO, BUILD_PROPS))
|
||||
.thenReturn(openClasspathResource(BUILD_PROPS));
|
||||
|
||||
SpringBootServiceVersionDetector guesser = new SpringBootServiceVersionDetector(system);
|
||||
Resource result = guesser.createResource(config);
|
||||
assertThat(result).isEqualTo(Resource.empty());
|
||||
}
|
||||
|
||||
private InputStream openClasspathResource(String resource) {
|
||||
return getClass().getClassLoader().getResourceAsStream(resource);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
build.artifact=something
|
||||
build.name=some-name
|
||||
build.version=0.0.2
|
|
@ -0,0 +1,2 @@
|
|||
build.artifact=something
|
||||
build.name=some-name
|
|
@ -21,7 +21,7 @@ import static java.util.stream.Collectors.toSet
|
|||
class SpringBootSmokeTest extends SmokeTest {
|
||||
|
||||
protected String getTargetImage(String jdk) {
|
||||
"ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-spring-boot:jdk$jdk-20230321.4484174638"
|
||||
"ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-spring-boot:jdk$jdk-20230920.6251727205"
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -94,6 +94,13 @@ class SpringBootSmokeTest extends SmokeTest {
|
|||
serviceName.isPresent()
|
||||
serviceName.get() == "otel-spring-test-app"
|
||||
|
||||
then: "service version is autodetected"
|
||||
def serviceVersion = findResourceAttribute(traces, "service.version")
|
||||
.map { it.stringValue }
|
||||
.findAny()
|
||||
serviceVersion.isPresent()
|
||||
serviceVersion.get() == "1.31.0-alpha-SNAPSHOT"
|
||||
|
||||
cleanup:
|
||||
stopTarget()
|
||||
|
||||
|
|
Loading…
Reference in New Issue