-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto-detect service name based on the jar name (open-telemetry#6817)
Co-authored-by: Trask Stalnaker <[email protected]>
- Loading branch information
Showing
11 changed files
with
296 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
...rary/src/main/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
...es/library/src/main/java/io/opentelemetry/instrumentation/resources/ProcessArguments.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
...s/library/src/main/java9/io/opentelemetry/instrumentation/resources/ProcessArguments.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() {} | ||
} |
118 changes: 118 additions & 0 deletions
118
.../src/test/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetectorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()"); | ||
} | ||
} |
Oops, something went wrong.