Make spring boot service name detector handle BOOT-INF/classes (#8101)

When spring boot application is packaged in one jar
`application.properties` and `application.yml` are under
`BOOT-INF/classes/`.
This commit is contained in:
Lauri Tulmin 2023-03-28 13:32:40 +03:00 committed by GitHub
parent ea617bc9a8
commit d24d7986ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 100 additions and 10 deletions

View File

@ -265,6 +265,18 @@ public class SpringBootServiceNameDetector implements ConditionalResourceProvide
// 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(Level.FINER, "Detected presence of BOOT-INF/classes/");
}
}
String getenv(String name) {
return System.getenv(name);
@ -275,7 +287,8 @@ public class SpringBootServiceNameDetector implements ConditionalResourceProvide
}
InputStream openClasspathResource(String filename) {
return ClassLoader.getSystemClassLoader().getResourceAsStream(filename);
String path = addBootInfPrefix ? "BOOT-INF/classes/" + filename : filename;
return classLoader.getResourceAsStream(path);
}
InputStream openFile(String filename) throws Exception {

View File

@ -14,6 +14,7 @@ import static org.mockito.Mockito.when;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.resources.Resource;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
@ -45,7 +46,7 @@ class SpringBootServiceNameDetectorTest {
@Test
void classpathApplicationProperties() {
when(system.openClasspathResource(PROPS)).thenCallRealMethod();
when(system.openClasspathResource(PROPS)).thenReturn(openClasspathResource(PROPS));
SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
Resource result = guesser.createResource(config);
expectServiceName(result, "dog-store");
@ -67,7 +68,8 @@ class SpringBootServiceNameDetectorTest {
@Test
void classpathApplicationYaml() {
when(system.openClasspathResource(APPLICATION_YML)).thenCallRealMethod();
when(system.openClasspathResource(APPLICATION_YML))
.thenReturn(openClasspathResource(APPLICATION_YML));
SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
Resource result = guesser.createResource(config);
expectServiceName(result, "cat-store");
@ -156,4 +158,8 @@ class SpringBootServiceNameDetectorTest {
byte[] allBytes = Files.readAllBytes(path);
return new String(allBytes, UTF_8);
}
private InputStream openClasspathResource(String resource) {
return getClass().getClassLoader().getResourceAsStream(resource);
}
}

View File

@ -0,0 +1,12 @@
plugins {
id("otel.java-conventions")
}
dependencies {
testCompileOnly("com.google.auto.service:auto-service-annotations")
testImplementation("org.mockito:mockito-junit-jupiter")
testImplementation(project(":instrumentation:spring:spring-boot-resources:library"))
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
testImplementation(project(path = ":smoke-tests:images:spring-boot", configuration = "springBootJar"))
}

View File

@ -0,0 +1,33 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.resources;
import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.resources.Resource;
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 TestBootInfClassesResource {
@Mock ConfigProperties config;
@Test
void testServiceName() {
// verify that the test app, that is added as a dependency to this project, has the expected
// layout
assertThat(getClass().getResource("/application.properties")).isNull();
assertThat(getClass().getResource("/BOOT-INF/classes/application.properties")).isNotNull();
SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector();
Resource result = guesser.createResource(config);
assertThat(result.getAttribute(SERVICE_NAME)).isEqualTo("otel-spring-test-app");
}
}

View File

@ -456,6 +456,7 @@ hideFromDependabot(":instrumentation:spark-2.3:javaagent")
hideFromDependabot(":instrumentation:spring:spring-batch-3.0:javaagent")
hideFromDependabot(":instrumentation:spring:spring-boot-actuator-autoconfigure-2.0:javaagent")
hideFromDependabot(":instrumentation:spring:spring-boot-resources:library")
hideFromDependabot(":instrumentation:spring:spring-boot-resources:testing")
hideFromDependabot(":instrumentation:spring:spring-core-2.0:javaagent")
hideFromDependabot(":instrumentation:spring:spring-data:spring-data-1.8:javaagent")
hideFromDependabot(":instrumentation:spring:spring-data:spring-data-3.0:testing")

View File

@ -51,6 +51,13 @@ abstract class SmokeTest extends Specification {
return Collections.emptyMap()
}
/**
* Subclasses can override this method to disable setting default service name
*/
protected boolean getSetServiceName() {
return true
}
/**
* Subclasses can override this method to provide additional files to copy to target container
*/
@ -77,7 +84,7 @@ abstract class SmokeTest extends Specification {
def startTarget(String jdk, String serverVersion, boolean windows) {
def targetImage = getTargetImage(jdk, serverVersion, windows)
return containerManager.startTarget(targetImage, agentPath, jvmArgsEnvVarName, extraEnv, extraResources, extraPorts, getWaitStrategy(), getCommand())
return containerManager.startTarget(targetImage, agentPath, jvmArgsEnvVarName, extraEnv, setServiceName, extraResources, extraPorts, getWaitStrategy(), getCommand())
}
protected abstract String getTargetImage(String jdk)

View File

@ -21,7 +21,12 @@ 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-20211213.1570880324"
"ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-spring-boot:jdk$jdk-20230321.4484174638"
}
@Override
protected boolean getSetServiceName() {
return false
}
@Override
@ -82,10 +87,17 @@ class SpringBootSmokeTest extends SmokeTest {
metrics.hasMetricsNamed("process.runtime.jvm.memory.committed")
metrics.hasMetricsNamed("process.runtime.jvm.memory.limit")
then: "service name is autodetected"
def serviceName = findResourceAttribute(traces, "service.name")
.map { it.stringValue }
.findAny()
serviceName.isPresent()
serviceName.get() == "otel-spring-test-app"
cleanup:
stopTarget()
where:
jdk << [8, 11, 17]
jdk << [8, 11, 17, 19]
}
}

View File

@ -17,7 +17,8 @@ public abstract class AbstractTestContainerManager implements TestContainerManag
private boolean started = false;
protected Map<String, String> getAgentEnvironment(String jvmArgsEnvVarName) {
protected Map<String, String> getAgentEnvironment(
String jvmArgsEnvVarName, boolean setServiceName) {
Map<String, String> environment = new HashMap<>();
// while modern JVMs understand linux container memory limits, they do not understand windows
// container memory limits yet, so we need to explicitly set max heap in order to prevent the
@ -27,7 +28,9 @@ public abstract class AbstractTestContainerManager implements TestContainerManag
environment.put("OTEL_BSP_SCHEDULE_DELAY", "10ms");
environment.put("OTEL_METRIC_EXPORT_INTERVAL", "1000");
environment.put("OTEL_EXPORTER_OTLP_ENDPOINT", "http://" + BACKEND_ALIAS + ":8080");
environment.put("OTEL_RESOURCE_ATTRIBUTES", "service.name=smoke-test");
if (setServiceName) {
environment.put("OTEL_RESOURCE_ATTRIBUTES", "service.name=smoke-test");
}
environment.put("OTEL_JAVAAGENT_DEBUG", "true");
return environment;
}

View File

@ -74,6 +74,7 @@ public class LinuxTestContainerManager extends AbstractTestContainerManager {
String agentPath,
String jvmArgsEnvVarName,
Map<String, String> extraEnv,
boolean setServiceName,
List<ResourceMapping> extraResources,
List<Integer> extraPorts,
TargetWaitStrategy waitStrategy,
@ -94,7 +95,7 @@ public class LinuxTestContainerManager extends AbstractTestContainerManager {
.withLogConsumer(new Slf4jLogConsumer(appLogger))
.withCopyFileToContainer(
MountableFile.forHostPath(agentPath), "/" + TARGET_AGENT_FILENAME)
.withEnv(getAgentEnvironment(jvmArgsEnvVarName))
.withEnv(getAgentEnvironment(jvmArgsEnvVarName, setServiceName))
.withEnv(extraEnv);
for (ResourceMapping resource : extraResources) {

View File

@ -23,6 +23,7 @@ public interface TestContainerManager {
String agentPath,
String jvmArgsEnvVarName,
Map<String, String> extraEnv,
boolean setServiceName,
List<ResourceMapping> extraResources,
List<Integer> extraPorts,
TargetWaitStrategy waitStrategy,

View File

@ -133,6 +133,7 @@ public class WindowsTestContainerManager extends AbstractTestContainerManager {
String agentPath,
String jvmArgsEnvVarName,
Map<String, String> extraEnv,
boolean setServiceName,
List<ResourceMapping> extraResources,
List<Integer> extraPorts,
TargetWaitStrategy waitStrategy,
@ -148,7 +149,7 @@ public class WindowsTestContainerManager extends AbstractTestContainerManager {
}
List<String> environment = new ArrayList<>();
getAgentEnvironment(jvmArgsEnvVarName)
getAgentEnvironment(jvmArgsEnvVarName, setServiceName)
.forEach((key, value) -> environment.add(key + "=" + value));
extraEnv.forEach((key, value) -> environment.add(key + "=" + value));