Skip to content

Commit

Permalink
Add Spring Boot service version finder / ResourceProvider (#9480)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hayanesh committed Sep 21, 2023
1 parent 6e7f955 commit e73118b
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@
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;
Expand Down Expand Up @@ -286,54 +282,4 @@ private String loadFromClasspath(String filename, Function<InputStream, String>
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]);
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build.artifact=something
build.name=some-name
build.version=0.0.2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build.artifact=something
build.name=some-name
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down

0 comments on commit e73118b

Please sign in to comment.