Auto-detect service name based on the jar name
MODIFY
instrumentation/resources/library/build.gradle.kts
instrumentation/resources/library/build.gradle.kts
plugins {
id("otel.library-instrumentation")
- id("otel.animalsniffer-conventions")
}
-val mrJarVersions = listOf(11)
+val mrJarVersions = listOf(9, 11)
dependencies {
implementation("io.opentelemetry:opentelemetry-sdk-common")
implementation("io.opentelemetry:opentelemetry-semconv")
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
- compileOnly("org.codehaus.mojo:animal-sniffer-annotations")
-
annotationProcessor("com.google.auto.service:auto-service")
compileOnly("com.google.auto.service:auto-service-annotations")
+ testCompileOnly("com.google.auto.service:auto-service-annotations")
testImplementation("org.junit.jupiter:junit-jupiter-api")
}

MODIFY
instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ContainerResource.java
instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ContainerResource.java
@@ -16,7 +16,6 @@ import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
-import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
/** Factory for {@link Resource} retrieving Container ID information. */
public final class ContainerResource {
@@ -25,7 +24,6 @@ public final class ContainerResource {
private static final String UNIQUE_HOST_NAME_FILE_NAME = "/proc/self/cgroup";
private static final Resource INSTANCE = buildSingleton(UNIQUE_HOST_NAME_FILE_NAME);
- @IgnoreJRERequirement
private static Resource buildSingleton(String uniqueHostNameFileName) {
// can't initialize this statically without running afoul of animalSniffer on paths
return buildResource(Paths.get(uniqueHostNameFileName));
@@ -52,7 +50,6 @@ public final class ContainerResource {
*
* @return containerId
*/
- @IgnoreJRERequirement
private static Optional<String> extractContainerId(Path cgroupFilePath) {
if (!Files.exists(cgroupFilePath) || !Files.isReadable(cgroupFilePath)) {
return Optional.empty();

ADD
instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetector.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.resources;
+
+import static java.util.logging.Level.FINE;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
+import io.opentelemetry.sdk.autoconfigure.spi.internal.ConditionalResourceProvider;
+import io.opentelemetry.sdk.resources.Resource;
+import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * A {@link ResourceProvider} that will attempt to detect the application name from the jar name.
+ */
+@AutoService(ResourceProvider.class)
+public final class JarServiceNameDetector implements ConditionalResourceProvider {
+
+ private static final Logger logger = Logger.getLogger(JarServiceNameDetector.class.getName());
+
+ private final Supplier<String[]> getProcessHandleArguments;
+ private final Function<String, String> getSystemProperty;
+ private final Predicate<Path> fileExists;
+
+ @SuppressWarnings("unused") // SPI
+ public JarServiceNameDetector() {
+ this(ProcessArguments::getProcessArguments, System::getProperty, Files::isRegularFile);
+ }
+
+ // visible for tests
+ JarServiceNameDetector(
+ Supplier<String[]> getProcessHandleArguments,
+ Function<String, String> getSystemProperty,
+ Predicate<Path> fileExists) {
+ this.getProcessHandleArguments = getProcessHandleArguments;
+ this.getSystemProperty = getSystemProperty;
+ this.fileExists = fileExists;
+ }
+
+ @Override
+ public Resource createResource(ConfigProperties config) {
+ Path jarPath = getJarPathFromProcessHandle();
+ if (jarPath == null) {
+ jarPath = getJarPathFromSunCommandLine();
+ }
+ if (jarPath == null) {
+ return Resource.empty();
+ }
+ String serviceName = getServiceName(jarPath);
+ logger.log(FINE, "Auto-detected service name from the jar file name: {0}", serviceName);
+ return Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, serviceName));
+ }
+
+ @Override
+ public boolean shouldApply(ConfigProperties config, Resource existing) {
+ String serviceName = config.getString("otel.service.name");
+ Map<String, String> resourceAttributes = config.getMap("otel.resource.attributes");
+ return serviceName == null
+ && !resourceAttributes.containsKey(ResourceAttributes.SERVICE_NAME.getKey())
+ && "unknown_service:java".equals(existing.getAttribute(ResourceAttributes.SERVICE_NAME));
+ }
+
+ @Nullable
+ private Path getJarPathFromProcessHandle() {
+ String[] javaArgs = getProcessHandleArguments.get();
+ for (int i = 0; i < javaArgs.length; ++i) {
+ if ("-jar".equals(javaArgs[i]) && (i < javaArgs.length - 1)) {
+ return Paths.get(javaArgs[i + 1]);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private Path getJarPathFromSunCommandLine() {
+ // the jar file is the first argument in the command line string
+ String programArguments = getSystemProperty.apply("sun.java.command");
+ if (programArguments == null) {
+ return null;
+ }
+
+ // Take the path until the first space. If the path doesn't exist extend it up to the next
+ // space. Repeat until a path that exists is found or input runs out.
+ int next = 0;
+ while (true) {
+ int nextSpace = programArguments.indexOf(' ', next);
+ if (nextSpace == -1) {
+ Path candidate = Paths.get(programArguments);
+ return fileExists.test(candidate) ? candidate : null;
+ }
+ Path candidate = Paths.get(programArguments.substring(0, nextSpace));
+ next = nextSpace + 1;
+ if (fileExists.test(candidate)) {
+ return candidate;
+ }
+ }
+ }
+
+ private static String getServiceName(Path jarPath) {
+ String jarName = jarPath.getFileName().toString();
+ int dotIndex = jarName.lastIndexOf(".");
+ return dotIndex == -1 ? jarName : jarName.substring(0, dotIndex);
+ }
+
+ @Override
+ public int order() {
+ // make it run later than the SpringBootServiceNameDetector
+ return 1000;
+ }
+}

ADD
instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessArguments.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.resources;
+
+final class ProcessArguments {
+
+ static String[] getProcessArguments() {
+ return new String[0];
+ }
+
+ private ProcessArguments() {}
+}

MODIFY
instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessPid.java
instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessPid.java
package io.opentelemetry.instrumentation.resources;
import java.lang.management.ManagementFactory;
-import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
final class ProcessPid {
private ProcessPid() {}
- @IgnoreJRERequirement
static long getPid() {
// While this is not strictly defined, almost all commonly used JVMs format this as
// pid@hostname.

MODIFY
instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessResource.java
instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessResource.java
@@ -12,7 +12,6 @@ import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
-import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
/** Factory of a {@link Resource} which provides information about the current running process. */
public final class ProcessResource {
@@ -38,7 +37,6 @@ public final class ProcessResource {
}
}
- @IgnoreJRERequirement
private static Resource doBuildResource() {
AttributesBuilder attributes = Attributes.builder();

MODIFY
instrumentation/resources/library/src/main/java11/io/opentelemetry/instrumentation/resources/ProcessPid.java
instrumentation/resources/library/src/main/java11/io/opentelemetry/instrumentation/resources/ProcessPid.java
package io.opentelemetry.instrumentation.resources;
import java.lang.management.ManagementFactory;
-import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
final class ProcessPid {
private ProcessPid() {}
- @IgnoreJRERequirement
static long getPid() {
return ManagementFactory.getRuntimeMXBean().getPid();
}

ADD
instrumentation/resources/library/src/main/java9/io/opentelemetry/instrumentation/resources/ProcessArguments.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.resources;
+
+final class ProcessArguments {
+
+ static String[] getProcessArguments() {
+ return ProcessHandle.current().info().arguments().orElseGet(() -> new String[0]);
+ }
+
+ private ProcessArguments() {}
+}

ADD
instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetectorTest.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.resources;
+
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import io.opentelemetry.sdk.resources.Resource;
+import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class JarServiceNameDetectorTest {
+
+ @Mock ConfigProperties config;
+
+ @Test
+ void createResource_empty() {
+ JarServiceNameDetector serviceNameProvider =
+ new JarServiceNameDetector(
+ () -> new String[0], prop -> null, JarServiceNameDetectorTest::failPath);
+
+ Resource resource = serviceNameProvider.createResource(config);
+
+ assertThat(resource.getAttributes()).isEmpty();
+ }
+
+ @Test
+ void createResource_noJarFileInArgs() {
+ String[] args = new String[] {"-Dtest=42", "-Xmx666m", "-jar"};
+ JarServiceNameDetector serviceNameProvider =
+ new JarServiceNameDetector(() -> args, prop -> null, JarServiceNameDetectorTest::failPath);
+
+ Resource resource = serviceNameProvider.createResource(config);
+
+ assertThat(resource.getAttributes()).isEmpty();
+ }
+
+ @Test
+ void createResource_processHandleJar() {
+ String path = Paths.get("path", "to", "app", "my-service.jar").toString();
+ String[] args = new String[] {"-Dtest=42", "-Xmx666m", "-jar", path, "abc", "def"};
+ JarServiceNameDetector serviceNameProvider =
+ new JarServiceNameDetector(() -> args, prop -> null, JarServiceNameDetectorTest::failPath);
+
+ Resource resource = serviceNameProvider.createResource(config);
+
+ assertThat(resource.getAttributes())
+ .hasSize(1)
+ .containsEntry(ResourceAttributes.SERVICE_NAME, "my-service");
+ }
+
+ @Test
+ void createResource_processHandleJarWithoutExtension() {
+ String path = Paths.get("path", "to", "app", "my-service.jar").toString();
+ String[] args = new String[] {"-Dtest=42", "-Xmx666m", "-jar", path};
+ JarServiceNameDetector serviceNameProvider =
+ new JarServiceNameDetector(() -> args, prop -> null, JarServiceNameDetectorTest::failPath);
+
+ Resource resource = serviceNameProvider.createResource(config);
+
+ assertThat(resource.getAttributes())
+ .hasSize(1)
+ .containsEntry(ResourceAttributes.SERVICE_NAME, "my-service");
+ }
+
+ @ParameterizedTest
+ @ArgumentsSource(SunCommandLineProvider.class)
+ void createResource_sunCommandLine(String commandLine, Path jarPath) {
+ Function<String, String> getProperty =
+ key -> "sun.java.command".equals(key) ? commandLine : null;
+ Predicate<Path> fileExists = jarPath::equals;
+
+ JarServiceNameDetector serviceNameProvider =
+ new JarServiceNameDetector(() -> new String[0], getProperty, fileExists);
+
+ Resource resource = serviceNameProvider.createResource(config);
+
+ assertThat(resource.getAttributes())
+ .hasSize(1)
+ .containsEntry(ResourceAttributes.SERVICE_NAME, "my-service");
+ }
+
+ static final class SunCommandLineProvider implements ArgumentsProvider {
+
+ @Override
+ public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
+ Path path = Paths.get("path", "to", "my-service.jar");
+ Path pathWithSpaces = Paths.get("path to app", "with spaces", "my-service.jar");
+ Path pathWithoutExtension = Paths.get("path to app", "with spaces", "my-service");
+ return Stream.of(
+ arguments(path.toString(), path),
+ arguments(pathWithSpaces + " 1 2 3", pathWithSpaces),
+ arguments(pathWithoutExtension + " 1 2 3", pathWithoutExtension));
+ }
+ }
+
+ private static boolean failPath(Path file) {
+ throw new AssertionError("Unexpected call to Files.isRegularFile()");
+ }
+}

RENAME
instrumentation/spring/spring-boot-resources/library/src/main/java/io/opentelemetry/instrumentation/spring/resources/SpringBootServiceNameGuesser.java
instrumentation/spring/spring-boot-resources/library/src/main/java/io/opentelemetry/instrumentation/spring/resources/SpringBootServiceNameDetector.java
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;
@@ -48,22 +50,22 @@ import org.yaml.snakeyaml.Yaml;
* </ul>
*/
@AutoService(ResourceProvider.class)
-public class SpringBootServiceNameGuesser implements ConditionalResourceProvider {
+public class SpringBootServiceNameDetector implements ConditionalResourceProvider {
private static final Logger logger =
- Logger.getLogger(SpringBootServiceNameGuesser.class.getName());
+ Logger.getLogger(SpringBootServiceNameDetector.class.getName());
private static final String COMMANDLINE_ARG_PREFIX = "--spring.application.name=";
private static final Pattern COMMANDLINE_PATTERN =
Pattern.compile("--spring\\.application\\.name=([a-zA-Z.\\-_]+)");
private final SystemHelper system;
@SuppressWarnings("unused")
- public SpringBootServiceNameGuesser() {
+ public SpringBootServiceNameDetector() {
this(new SystemHelper());
}
// Exists for testing
- SpringBootServiceNameGuesser(SystemHelper system) {
+ SpringBootServiceNameDetector(SystemHelper system) {
this.system = system;
}
@@ -90,7 +92,7 @@ public class SpringBootServiceNameGuesser implements ConditionalResourceProvider
.findFirst()
.map(
serviceName -> {
- logger.log(Level.FINER, "Guessed Spring Boot service name: {0}", serviceName);
+ logger.log(FINE, "Auto-detected Spring Boot service name: {0}", serviceName);
return Resource.builder().put(ResourceAttributes.SERVICE_NAME, serviceName).build();
})
.orElseGet(Resource::empty);
@@ -152,7 +154,7 @@ public class SpringBootServiceNameGuesser implements ConditionalResourceProvider
@Nullable
private String findByClasspathApplicationYaml() {
String result =
- loadFromClasspath("application.yml", SpringBootServiceNameGuesser::parseNameFromYaml);
+ loadFromClasspath("application.yml", SpringBootServiceNameDetector::parseNameFromYaml);
logger.log(Level.FINER, "Checking application.yml in classpath: {0}", result);
return result;
}
@@ -235,7 +237,7 @@ public class SpringBootServiceNameGuesser implements ConditionalResourceProvider
@Nullable
private String readNameFromAppProperties() {
return loadFromClasspath(
- "application.properties", SpringBootServiceNameGuesser::getAppNamePropertyFromStream);
+ "application.properties", SpringBootServiceNameDetector::getAppNamePropertyFromStream);
}
@Nullable

RENAME
instrumentation/spring/spring-boot-resources/library/src/test/java/io/opentelemetry/instrumentation/spring/resources/SpringBootServiceNameGuesserTest.java
instrumentation/spring/spring-boot-resources/library/src/test/java/io/opentelemetry/instrumentation/spring/resources/SpringBootServiceNameDetectorTest.java
@@ -25,19 +25,19 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
-class SpringBootServiceNameGuesserTest {
+class SpringBootServiceNameDetectorTest {
static final String PROPS = "application.properties";
static final String APPLICATION_YML = "application.yml";
@Mock ConfigProperties config;
- @Mock SpringBootServiceNameGuesser.SystemHelper system;
+ @Mock SpringBootServiceNameDetector.SystemHelper system;
@Test
void findByEnvVar() {
String expected = "fur-city";
when(system.getenv("SPRING_APPLICATION_NAME")).thenReturn(expected);
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
Resource result = guesser.createResource(config);
expectServiceName(result, expected);
@@ -46,7 +46,7 @@ class SpringBootServiceNameGuesserTest {
@Test
void classpathApplicationProperties() {
when(system.openClasspathResource(PROPS)).thenCallRealMethod();
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
Resource result = guesser.createResource(config);
expectServiceName(result, "dog-store");
}
@@ -57,7 +57,7 @@ class SpringBootServiceNameGuesserTest {
try {
writeString(propsPath, "spring.application.name=fish-tank\n");
when(system.openFile(PROPS)).thenCallRealMethod();
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
Resource result = guesser.createResource(config);
expectServiceName(result, "fish-tank");
} finally {
@@ -68,7 +68,7 @@ class SpringBootServiceNameGuesserTest {
@Test
void classpathApplicationYaml() {
when(system.openClasspathResource(APPLICATION_YML)).thenCallRealMethod();
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
Resource result = guesser.createResource(config);
expectServiceName(result, "cat-store");
}
@@ -81,7 +81,7 @@ class SpringBootServiceNameGuesserTest {
String content = readString(Paths.get(url.toURI()));
writeString(yamlPath, content);
when(system.openFile(APPLICATION_YML)).thenCallRealMethod();
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
Resource result = guesser.createResource(config);
expectServiceName(result, "cat-store");
} finally {
@@ -99,7 +99,7 @@ class SpringBootServiceNameGuesserTest {
"--spring.application.name=tiger-town",
"--quiet=never"
});
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
Resource result = guesser.createResource(config);
expectServiceName(result, "tiger-town");
}
@@ -108,20 +108,20 @@ class SpringBootServiceNameGuesserTest {
void getFromCommandlineArgsWithSystemProperty() throws Exception {
when(system.getProperty("sun.java.command"))
.thenReturn("/bin/java sweet-spring.jar --spring.application.name=bullpen --quiet=never");
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
Resource result = guesser.createResource(config);
expectServiceName(result, "bullpen");
}
@Test
void shouldApply() {
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
assertThat(guesser.shouldApply(config, Resource.getDefault())).isTrue();
}
@Test
void shouldNotApplyWhenResourceHasServiceName() {
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
Resource resource =
Resource.getDefault().merge(Resource.create(Attributes.of(SERVICE_NAME, "test-service")));
assertThat(guesser.shouldApply(config, resource)).isFalse();
@@ -129,14 +129,14 @@ class SpringBootServiceNameGuesserTest {
@Test
void shouldNotApplyIfConfigHasServiceName() {
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
when(config.getString("otel.service.name")).thenReturn("test-service");
assertThat(guesser.shouldApply(config, Resource.getDefault())).isFalse();
}
@Test
void shouldNotApplyIfConfigHasServiceNameResourceAttribute() {
- SpringBootServiceNameGuesser guesser = new SpringBootServiceNameGuesser(system);
+ SpringBootServiceNameDetector guesser = new SpringBootServiceNameDetector(system);
when(config.getMap("otel.resource.attributes"))
.thenReturn(singletonMap(SERVICE_NAME.getKey(), "test-service"));
assertThat(guesser.shouldApply(config, Resource.getDefault())).isFalse();

Auto-detect service name based on the jar name (#6817)

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>