Skip to content

Commit

Permalink
Auto-detect service name based on the jar name (open-telemetry#6817)
Browse files Browse the repository at this point in the history
Co-authored-by: Trask Stalnaker <[email protected]>
  • Loading branch information
2 people authored and LironKS committed Dec 4, 2022
1 parent 702a373 commit a1ed629
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 33 deletions.
6 changes: 2 additions & 4 deletions instrumentation/resources/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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")
testCompileOnly("com.google.auto.service:auto-service-annotations")

testImplementation("org.junit.jupiter:junit-jupiter-api")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
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 {
Expand All @@ -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));
Expand All @@ -52,7 +50,6 @@ public static Resource get() {
*
* @return containerId
*/
@IgnoreJRERequirement
private static Optional<String> extractContainerId(Path cgroupFilePath) {
if (!Files.exists(cgroupFilePath) || !Files.isReadable(cgroupFilePath)) {
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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;
}
}
Original file line number Diff line number Diff line change
@@ -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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -38,7 +37,6 @@ static Resource buildResource() {
}
}

@IgnoreJRERequirement
private static Resource doBuildResource() {
AttributesBuilder attributes = Attributes.builder();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* 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()");
}
}
Loading

0 comments on commit a1ed629

Please sign in to comment.