From 780b2345fe735d4af08682a191241cb4b39736e5 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Thu, 6 Oct 2022 17:01:20 +0200 Subject: [PATCH] Auto-detect service name based on the jar name --- .../resources/library/build.gradle.kts | 6 +- .../resources/ContainerResource.java | 3 - .../resources/JarServiceNameProvider.java | 122 ++++++++++++++++++ .../resources/ProcessArguments.java | 15 +++ .../instrumentation/resources/ProcessPid.java | 2 - .../resources/ProcessResource.java | 2 - .../instrumentation/resources/ProcessPid.java | 2 - .../resources/ProcessArguments.java | 15 +++ .../resources/JarServiceNameProviderTest.java | 111 ++++++++++++++++ .../SpringBootServiceNameGuesser.java | 4 +- 10 files changed, 268 insertions(+), 14 deletions(-) create mode 100644 instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarServiceNameProvider.java create mode 100644 instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessArguments.java create mode 100644 instrumentation/resources/library/src/main/java9/io/opentelemetry/instrumentation/resources/ProcessArguments.java create mode 100644 instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/JarServiceNameProviderTest.java diff --git a/instrumentation/resources/library/build.gradle.kts b/instrumentation/resources/library/build.gradle.kts index 50ddeea7d86d..812ebe5eb078 100644 --- a/instrumentation/resources/library/build.gradle.kts +++ b/instrumentation/resources/library/build.gradle.kts @@ -1,19 +1,17 @@ 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") + testImplementation("com.google.auto.service:auto-service-annotations") testImplementation("org.junit.jupiter:junit-jupiter-api") } diff --git a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ContainerResource.java b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ContainerResource.java index df988e7e0ac2..d6ec69df45e6 100644 --- a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ContainerResource.java +++ b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ContainerResource.java @@ -18,7 +18,6 @@ import java.util.logging.Logger; import java.util.stream.Stream; import javax.annotation.Nullable; -import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; /** Factory for {@link Resource} retrieving Container ID information. */ public final class ContainerResource { @@ -27,7 +26,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)); @@ -56,7 +54,6 @@ public static Resource get() { * * @return containerId */ - @IgnoreJRERequirement @Nullable private static String extractContainerId(Path cgroupFilePath) { if (!Files.exists(cgroupFilePath) || !Files.isReadable(cgroupFilePath)) { diff --git a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarServiceNameProvider.java b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarServiceNameProvider.java new file mode 100644 index 000000000000..f9d8ab981da1 --- /dev/null +++ b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarServiceNameProvider.java @@ -0,0 +1,122 @@ +/* + * 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 guess the application name from the jar name. */ +@AutoService(ResourceProvider.class) +public final class JarServiceNameProvider implements ConditionalResourceProvider { + + private static final Logger logger = Logger.getLogger(JarServiceNameProvider.class.getName()); + + private final Supplier getProcessHandleArguments; + private final Function getSystemProperty; + private final Predicate fileExists; + + @SuppressWarnings("unused") // SPI + public JarServiceNameProvider() { + this(ProcessArguments::getProcessArguments, System::getProperty, Files::exists); + } + + // visible for tests + JarServiceNameProvider( + Supplier getProcessHandleArguments, + Function getSystemProperty, + Predicate 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 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 SpringBootServiceNameGuesser + return 1000; + } +} diff --git a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessArguments.java b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessArguments.java new file mode 100644 index 000000000000..c835f876965c --- /dev/null +++ b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessArguments.java @@ -0,0 +1,15 @@ +/* + * 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() {} +} diff --git a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessPid.java b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessPid.java index e6cdaae1e9ca..7277a9ead7a1 100644 --- a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessPid.java +++ b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessPid.java @@ -6,13 +6,11 @@ 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. diff --git a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessResource.java b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessResource.java index 4d2700555a3a..9ff1848182c0 100644 --- a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessResource.java +++ b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessResource.java @@ -12,7 +12,6 @@ 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 @@ static Resource buildResource() { } } - @IgnoreJRERequirement private static Resource doBuildResource() { AttributesBuilder attributes = Attributes.builder(); diff --git a/instrumentation/resources/library/src/main/java11/io/opentelemetry/instrumentation/resources/ProcessPid.java b/instrumentation/resources/library/src/main/java11/io/opentelemetry/instrumentation/resources/ProcessPid.java index 17202bd598be..9c03842da002 100644 --- a/instrumentation/resources/library/src/main/java11/io/opentelemetry/instrumentation/resources/ProcessPid.java +++ b/instrumentation/resources/library/src/main/java11/io/opentelemetry/instrumentation/resources/ProcessPid.java @@ -6,13 +6,11 @@ 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(); } diff --git a/instrumentation/resources/library/src/main/java9/io/opentelemetry/instrumentation/resources/ProcessArguments.java b/instrumentation/resources/library/src/main/java9/io/opentelemetry/instrumentation/resources/ProcessArguments.java new file mode 100644 index 000000000000..b32f6c361524 --- /dev/null +++ b/instrumentation/resources/library/src/main/java9/io/opentelemetry/instrumentation/resources/ProcessArguments.java @@ -0,0 +1,15 @@ +/* + * 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() {} +} diff --git a/instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/JarServiceNameProviderTest.java b/instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/JarServiceNameProviderTest.java new file mode 100644 index 000000000000..a036db921b3a --- /dev/null +++ b/instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/JarServiceNameProviderTest.java @@ -0,0 +1,111 @@ +/* + * 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.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 JarServiceNameProviderTest { + + @Mock ConfigProperties config; + + @Test + void createResource_empty() { + JarServiceNameProvider serviceNameProvider = + new JarServiceNameProvider(() -> new String[0], prop -> null, file -> false); + + Resource resource = serviceNameProvider.createResource(config); + + assertThat(resource.getAttributes()).isEmpty(); + } + + @Test + void createResource_noJarFileInArgs() { + String[] args = new String[] {"-Dtest=42", "-Xmx666m", "-jar"}; + JarServiceNameProvider serviceNameProvider = + new JarServiceNameProvider(() -> args, prop -> null, file -> false); + + Resource resource = serviceNameProvider.createResource(config); + + assertThat(resource.getAttributes()).isEmpty(); + } + + @Test + void createResource_processHandleJar() { + String[] args = + new String[] {"-Dtest=42", "-Xmx666m", "-jar", "/path/to/app/my-service.jar", "abc", "def"}; + JarServiceNameProvider serviceNameProvider = + new JarServiceNameProvider(() -> args, prop -> null, file -> false); + + Resource resource = serviceNameProvider.createResource(config); + + assertThat(resource.getAttributes()) + .hasSize(1) + .containsEntry(ResourceAttributes.SERVICE_NAME, "my-service"); + } + + @Test + void createResource_processHandleJarWithoutExtension() { + String[] args = new String[] {"-Dtest=42", "-Xmx666m", "-jar", "/path/to/app/my-service"}; + JarServiceNameProvider serviceNameProvider = + new JarServiceNameProvider(() -> args, prop -> null, file -> false); + + 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, String jarPath) { + Function getProperty = + key -> "sun.java.command".equals(key) ? commandLine : null; + Predicate fileExists = file -> jarPath.equals(file.toString()); + + JarServiceNameProvider serviceNameProvider = + new JarServiceNameProvider(() -> 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 provideArguments(ExtensionContext context) { + return Stream.of( + arguments("/path/to/my-service.jar", "/path/to/my-service.jar"), + arguments( + "/path to app/with spaces/my-service.jar 1 2 3", + "/path to app/with spaces/my-service.jar"), + arguments( + "/path to app/with spaces/my-service 1 2 3", "/path to app/with spaces/my-service")); + } + } +} diff --git a/instrumentation/spring/spring-boot-resources/library/src/main/java/io/opentelemetry/instrumentation/spring/resources/SpringBootServiceNameGuesser.java b/instrumentation/spring/spring-boot-resources/library/src/main/java/io/opentelemetry/instrumentation/spring/resources/SpringBootServiceNameGuesser.java index 1f26b2c85e99..1281d3c024fb 100644 --- a/instrumentation/spring/spring-boot-resources/library/src/main/java/io/opentelemetry/instrumentation/spring/resources/SpringBootServiceNameGuesser.java +++ b/instrumentation/spring/spring-boot-resources/library/src/main/java/io/opentelemetry/instrumentation/spring/resources/SpringBootServiceNameGuesser.java @@ -5,6 +5,8 @@ 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; @@ -90,7 +92,7 @@ public Resource createResource(ConfigProperties config) { .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);