Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

detect service.version based on jar file name #10514

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.resources;

import io.opentelemetry.sdk.autoconfigure.spi.internal.ConditionalResourceProvider;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;

public abstract class JarResourceDetector implements ConditionalResourceProvider {
protected static final Logger logger = Logger.getLogger(JarServiceNameDetector.class.getName());
private static final Pattern JAR_FILE_VERSION_PATTERN = Pattern.compile("[-_]v?\\d.*");
private static final Pattern ANY_DIGIT = Pattern.compile("\\d");
protected final Supplier<String[]> getProcessHandleArguments;
protected final Function<String, String> getSystemProperty;
protected final Predicate<Path> fileExists;
private final Function<Supplier<Optional<String>>, Optional<String>> jarNameCacheLookup;

protected static final Function<Supplier<Optional<String>>, Optional<String>> CACHE_LOOKUP =
new Function<Supplier<Optional<String>>, Optional<String>>() {
private Optional<String> cachedValue = Optional.empty();

@Override
public Optional<String> apply(Supplier<Optional<String>> supplier) {
if (cachedValue.isPresent()) {
return cachedValue;
}
Optional<String> value = supplier.get();
cachedValue = value;
return value;
}
};

public JarResourceDetector(
Supplier<String[]> getProcessHandleArguments,
Function<String, String> getSystemProperty,
Predicate<Path> fileExists,
Function<Supplier<Optional<String>>, Optional<String>> jarNameCacheLookup) {
this.getProcessHandleArguments = getProcessHandleArguments;
this.getSystemProperty = getSystemProperty;
this.fileExists = fileExists;
this.jarNameCacheLookup = jarNameCacheLookup;
}

private Optional<String> getJarPath() {
Path jarPath = getJarPathFromProcessHandle();
if (jarPath == null) {
jarPath = getJarPathFromSunCommandLine();
}
return Optional.ofNullable(jarPath).map(p -> p.getFileName().toString());
}

protected Optional<NameAndVersion> getServiceNameAndVersion() {
return jarNameCacheLookup
.apply(this::getJarPath)
.flatMap(
jarName -> {
int dotIndex = jarName.lastIndexOf(".");
if (dotIndex == -1 || ANY_DIGIT.matcher(jarName.substring(dotIndex)).find()) {
// don't change if digit it extension, it's probably a version
return Optional.of(new NameAndVersion(jarName, Optional.empty()));
}

return Optional.of(
JarResourceDetector.getNameAndVersion(jarName.substring(0, dotIndex)));
});
}

private static NameAndVersion getNameAndVersion(String jarNameWithoutExtension) {
Matcher matcher = JAR_FILE_VERSION_PATTERN.matcher(jarNameWithoutExtension);
if (matcher.find()) {
int start = matcher.start();
String name = jarNameWithoutExtension.substring(0, start);
String version = jarNameWithoutExtension.substring(start + 1);
return new NameAndVersion(name, Optional.of(version));
}

return new NameAndVersion(jarNameWithoutExtension, Optional.empty());
}

@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) {
return pathIfExists(programArguments);
}
Path path = pathIfExists(programArguments.substring(0, nextSpace));
next = nextSpace + 1;
if (path != null) {
return path;
}
}
}

@Nullable
private Path pathIfExists(String programArguments) {
Path candidate;
try {
candidate = Paths.get(programArguments);
} catch (InvalidPathException e) {
return null;
}
return fileExists.test(candidate) ? candidate : null;
}

@Override
public int order() {
// make it run later than the SpringBootServiceNameDetector
return 1000;
}

protected static class NameAndVersion {
final String name;
final Optional<String> version;

NameAndVersion(String name, Optional<String> version) {
this.name = name;
this.version = version;
}

@Override
public String toString() {
return version
.map(v -> String.format("name: %s, version: %s", name, v))
.orElse(String.format("name: %s", name));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,52 @@
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.ResourceAttributes;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Optional;
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;
public final class JarServiceNameDetector extends JarResourceDetector {

@SuppressWarnings("unused") // SPI
public JarServiceNameDetector() {
this(ProcessArguments::getProcessArguments, System::getProperty, Files::isRegularFile);
this(
ProcessArguments::getProcessArguments,
System::getProperty,
Files::isRegularFile,
CACHE_LOOKUP);
}

// visible for tests
JarServiceNameDetector(
Supplier<String[]> getProcessHandleArguments,
Function<String, String> getSystemProperty,
Predicate<Path> fileExists) {
this.getProcessHandleArguments = getProcessHandleArguments;
this.getSystemProperty = getSystemProperty;
this.fileExists = fileExists;
Predicate<Path> fileExists,
Function<Supplier<Optional<String>>, Optional<String>> jarNameCacheLookup) {
super(getProcessHandleArguments, getSystemProperty, fileExists, jarNameCacheLookup);
}

@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));
return getServiceNameAndVersion()
.map(
nameAndVersion -> {
logger.log(
FINE, "Auto-detected service.name from the jar file: {0}", nameAndVersion.name);

return Resource.create(
Attributes.of(ResourceAttributes.SERVICE_NAME, nameAndVersion.name));
})
.orElse(Resource.empty());
}

@Override
Expand All @@ -74,62 +67,4 @@ public boolean shouldApply(ConfigProperties config, Resource existing) {
&& !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) {
return pathIfExists(programArguments);
}
Path path = pathIfExists(programArguments.substring(0, nextSpace));
next = nextSpace + 1;
if (path != null) {
return path;
}
}
}

@Nullable
private Path pathIfExists(String programArguments) {
Path candidate;
try {
candidate = Paths.get(programArguments);
} catch (InvalidPathException e) {
return null;
}
return fileExists.test(candidate) ? candidate : null;
}

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,67 @@
/*
* 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.resources.Resource;
import io.opentelemetry.semconv.ResourceAttributes;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
* A {@link ResourceProvider} that will attempt to detect the application version from the jar name.
*/
@AutoService(ResourceProvider.class)
public final class JarServiceVersionDetector extends JarResourceDetector {

@SuppressWarnings("unused") // SPI
public JarServiceVersionDetector() {
this(
ProcessArguments::getProcessArguments,
System::getProperty,
Files::isRegularFile,
CACHE_LOOKUP);
}

// visible for tests
JarServiceVersionDetector(
Supplier<String[]> getProcessHandleArguments,
Function<String, String> getSystemProperty,
Predicate<Path> fileExists,
Function<Supplier<Optional<String>>, Optional<String>> jarNameCacheLookup) {
super(getProcessHandleArguments, getSystemProperty, fileExists, jarNameCacheLookup);
}

@Override
public Resource createResource(ConfigProperties config) {
return getServiceNameAndVersion()
.flatMap(n -> n.version)
.map(
version -> {
logger.log(FINE, "Auto-detected service.version from the jar file: {0}", version);

return Resource.create(Attributes.of(ResourceAttributes.SERVICE_VERSION, version));
})
.orElse(Resource.empty());
}

@Override
public boolean shouldApply(ConfigProperties config, Resource existing) {
return !config
.getMap("otel.resource.attributes")
.containsKey(ResourceAttributes.SERVICE_VERSION.getKey())
&& existing.getAttribute(ResourceAttributes.SERVICE_VERSION) == null;
}
}
Loading
Loading