Manifest resource detector (#10621)
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
parent
2701c4dc68
commit
5df8a5a0a0
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.resources;
|
||||
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import io.opentelemetry.api.common.AttributeKey;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An easier alternative to {@link io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider}, which
|
||||
* avoids some common pitfalls and boilerplate.
|
||||
*
|
||||
* <p>An example of how to use this interface can be found in {@link ManifestResourceProvider}.
|
||||
*/
|
||||
interface AttributeProvider<D> {
|
||||
Optional<D> readData();
|
||||
|
||||
void registerAttributes(Builder<D> builder);
|
||||
|
||||
interface Builder<D> {
|
||||
@CanIgnoreReturnValue
|
||||
<T> Builder<D> add(AttributeKey<T> key, Function<D, Optional<T>> getter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.resources;
|
||||
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import io.opentelemetry.api.common.AttributeKey;
|
||||
import io.opentelemetry.api.common.Attributes;
|
||||
import io.opentelemetry.api.common.AttributesBuilder;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.ConditionalResourceProvider;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import io.opentelemetry.semconv.ResourceAttributes;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An easier alternative to {@link io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider}, which
|
||||
* avoids some common pitfalls and boilerplate.
|
||||
*
|
||||
* <p>An example of how to use this interface can be found in {@link ManifestResourceProvider}.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public abstract class AttributeResourceProvider<D> implements ConditionalResourceProvider {
|
||||
|
||||
private final AttributeProvider<D> attributeProvider;
|
||||
|
||||
public class AttributeBuilder implements AttributeProvider.Builder<D> {
|
||||
|
||||
private AttributeBuilder() {}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
@Override
|
||||
public <T> AttributeBuilder add(AttributeKey<T> key, Function<D, Optional<T>> getter) {
|
||||
attributeGetters.put((AttributeKey) key, Objects.requireNonNull((Function) getter));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static final ThreadLocal<Resource> existingResource = new ThreadLocal<>();
|
||||
|
||||
private final Map<AttributeKey<Object>, Function<D, Optional<?>>> attributeGetters =
|
||||
new HashMap<>();
|
||||
|
||||
public AttributeResourceProvider(AttributeProvider<D> attributeProvider) {
|
||||
this.attributeProvider = attributeProvider;
|
||||
attributeProvider.registerAttributes(new AttributeBuilder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean shouldApply(ConfigProperties config, Resource existing) {
|
||||
existingResource.set(existing);
|
||||
|
||||
Map<String, String> resourceAttributes = getResourceAttributes(config);
|
||||
return attributeGetters.keySet().stream()
|
||||
.allMatch(key -> shouldUpdate(config, existing, key, resourceAttributes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Resource createResource(ConfigProperties config) {
|
||||
return attributeProvider
|
||||
.readData()
|
||||
.map(
|
||||
data -> {
|
||||
// what should we do here?
|
||||
// we don't have access to the existing resource
|
||||
// if the resource provider produces a single key, we can rely on shouldApply
|
||||
// i.e. this method won't be called if the key is already present
|
||||
// the thread local is a hack to work around this
|
||||
Resource existing =
|
||||
Objects.requireNonNull(existingResource.get(), "call shouldApply first");
|
||||
Map<String, String> resourceAttributes = getResourceAttributes(config);
|
||||
AttributesBuilder builder = Attributes.builder();
|
||||
attributeGetters.entrySet().stream()
|
||||
.filter(e -> shouldUpdate(config, existing, e.getKey(), resourceAttributes))
|
||||
.forEach(
|
||||
e ->
|
||||
e.getValue()
|
||||
.apply(data)
|
||||
.ifPresent(value -> putAttribute(builder, e.getKey(), value)));
|
||||
return Resource.create(builder.build());
|
||||
})
|
||||
.orElse(Resource.empty());
|
||||
}
|
||||
|
||||
private static <T> void putAttribute(AttributesBuilder builder, AttributeKey<T> key, T value) {
|
||||
builder.put(key, value);
|
||||
}
|
||||
|
||||
private static Map<String, String> getResourceAttributes(ConfigProperties config) {
|
||||
return config.getMap("otel.resource.attributes");
|
||||
}
|
||||
|
||||
private static boolean shouldUpdate(
|
||||
ConfigProperties config,
|
||||
Resource existing,
|
||||
AttributeKey<?> key,
|
||||
Map<String, String> resourceAttributes) {
|
||||
if (resourceAttributes.containsKey(key.getKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object value = existing.getAttribute(key);
|
||||
|
||||
if (key.equals(ResourceAttributes.SERVICE_NAME)) {
|
||||
return config.getString("otel.service.name") == null && "unknown_service:java".equals(value);
|
||||
}
|
||||
|
||||
return value == null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.resources;
|
||||
|
||||
import java.nio.file.Files;
|
||||
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 javax.annotation.Nullable;
|
||||
|
||||
class JarPathFinder {
|
||||
private final Supplier<String[]> getProcessHandleArguments;
|
||||
private final Function<String, String> getSystemProperty;
|
||||
private final Predicate<Path> fileExists;
|
||||
|
||||
private static class DetectionResult {
|
||||
private final Optional<Path> jarPath;
|
||||
|
||||
private DetectionResult(Optional<Path> jarPath) {
|
||||
this.jarPath = jarPath;
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<DetectionResult> detectionResult = Optional.empty();
|
||||
|
||||
public JarPathFinder() {
|
||||
this(ProcessArguments::getProcessArguments, System::getProperty, Files::isRegularFile);
|
||||
}
|
||||
|
||||
// visible for tests
|
||||
JarPathFinder(
|
||||
Supplier<String[]> getProcessHandleArguments,
|
||||
Function<String, String> getSystemProperty,
|
||||
Predicate<Path> fileExists) {
|
||||
this.getProcessHandleArguments = getProcessHandleArguments;
|
||||
this.getSystemProperty = getSystemProperty;
|
||||
this.fileExists = fileExists;
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
static void resetForTest() {
|
||||
detectionResult = Optional.empty();
|
||||
}
|
||||
|
||||
Optional<Path> getJarPath() {
|
||||
if (!detectionResult.isPresent()) {
|
||||
detectionResult = Optional.of(new DetectionResult(Optional.ofNullable(detectJarPath())));
|
||||
}
|
||||
return detectionResult.get().jarPath;
|
||||
}
|
||||
|
||||
private Path detectJarPath() {
|
||||
Path jarPath = getJarPathFromProcessHandle();
|
||||
if (jarPath != null) {
|
||||
return jarPath;
|
||||
}
|
||||
return getJarPathFromSunCommandLine();
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
|
@ -14,16 +14,9 @@ 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.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.
|
||||
|
@ -33,37 +26,30 @@ 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;
|
||||
private final JarPathFinder jarPathFinder;
|
||||
|
||||
@SuppressWarnings("unused") // SPI
|
||||
public JarServiceNameDetector() {
|
||||
this(ProcessArguments::getProcessArguments, System::getProperty, Files::isRegularFile);
|
||||
this(new JarPathFinder());
|
||||
}
|
||||
|
||||
// visible for tests
|
||||
JarServiceNameDetector(
|
||||
Supplier<String[]> getProcessHandleArguments,
|
||||
Function<String, String> getSystemProperty,
|
||||
Predicate<Path> fileExists) {
|
||||
this.getProcessHandleArguments = getProcessHandleArguments;
|
||||
this.getSystemProperty = getSystemProperty;
|
||||
this.fileExists = fileExists;
|
||||
JarServiceNameDetector(JarPathFinder jarPathFinder) {
|
||||
this.jarPathFinder = jarPathFinder;
|
||||
}
|
||||
|
||||
@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 jarPathFinder
|
||||
.getJarPath()
|
||||
.map(
|
||||
jarPath -> {
|
||||
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));
|
||||
})
|
||||
.orElseGet(Resource::empty);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -75,52 +61,6 @@ public final class JarServiceNameDetector implements ConditionalResourceProvider
|
|||
&& "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(".");
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.resources;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
|
||||
import io.opentelemetry.semconv.ResourceAttributes;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* A {@link ResourceProvider} that will attempt to detect the <code>service.name</code> and <code>
|
||||
* service.version</code> from META-INF/MANIFEST.MF.
|
||||
*/
|
||||
@AutoService(ResourceProvider.class)
|
||||
public final class ManifestResourceProvider extends AttributeResourceProvider<Manifest> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ManifestResourceProvider.class.getName());
|
||||
|
||||
@SuppressWarnings("unused") // SPI
|
||||
public ManifestResourceProvider() {
|
||||
this(new JarPathFinder(), ManifestResourceProvider::readManifest);
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
ManifestResourceProvider(
|
||||
JarPathFinder jarPathFinder, Function<Path, Optional<Manifest>> manifestReader) {
|
||||
super(
|
||||
new AttributeProvider<Manifest>() {
|
||||
@Override
|
||||
public Optional<Manifest> readData() {
|
||||
return jarPathFinder.getJarPath().flatMap(manifestReader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAttributes(Builder<Manifest> builder) {
|
||||
builder
|
||||
.add(
|
||||
ResourceAttributes.SERVICE_NAME,
|
||||
manifest -> {
|
||||
String serviceName =
|
||||
manifest.getMainAttributes().getValue("Implementation-Title");
|
||||
return Optional.ofNullable(serviceName);
|
||||
})
|
||||
.add(
|
||||
ResourceAttributes.SERVICE_VERSION,
|
||||
manifest -> {
|
||||
String serviceVersion =
|
||||
manifest.getMainAttributes().getValue("Implementation-Version");
|
||||
return Optional.ofNullable(serviceVersion);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Optional<Manifest> readManifest(Path jarPath) {
|
||||
try (InputStream s =
|
||||
new URL(String.format("jar:%s!/META-INF/MANIFEST.MF", jarPath.toUri())).openStream()) {
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.read(s);
|
||||
return Optional.of(manifest);
|
||||
} catch (Exception e) {
|
||||
logger.log(WARNING, "Error reading manifest", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
// make it run later than ManifestResourceProvider and SpringBootServiceNameDetector
|
||||
return 300;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ 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.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
|
@ -27,26 +28,39 @@ import org.mockito.Mock;
|
|||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
// todo split JarFileDetectorTest and JarServiceNameDetectorTest
|
||||
class JarServiceNameDetectorTest {
|
||||
|
||||
@Mock ConfigProperties config;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
JarPathFinder.resetForTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createResource_empty() {
|
||||
JarServiceNameDetector serviceNameProvider =
|
||||
new JarServiceNameDetector(
|
||||
() -> new String[0], prop -> null, JarServiceNameDetectorTest::failPath);
|
||||
String[] processArgs = new String[0];
|
||||
Function<String, String> getProperty = prop -> null;
|
||||
Predicate<Path> fileExists = JarServiceNameDetectorTest::failPath;
|
||||
JarServiceNameDetector serviceNameProvider = getDetector(processArgs, getProperty, fileExists);
|
||||
|
||||
Resource resource = serviceNameProvider.createResource(config);
|
||||
|
||||
assertThat(resource.getAttributes()).isEmpty();
|
||||
}
|
||||
|
||||
private static JarServiceNameDetector getDetector(
|
||||
String[] processArgs, Function<String, String> getProperty, Predicate<Path> fileExists) {
|
||||
return new JarServiceNameDetector(
|
||||
new JarPathFinder(() -> processArgs, getProperty, fileExists));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createResource_noJarFileInArgs() {
|
||||
String[] args = new String[] {"-Dtest=42", "-Xmx666m", "-jar"};
|
||||
JarServiceNameDetector serviceNameProvider =
|
||||
new JarServiceNameDetector(() -> args, prop -> null, JarServiceNameDetectorTest::failPath);
|
||||
getDetector(args, prop -> null, JarServiceNameDetectorTest::failPath);
|
||||
|
||||
Resource resource = serviceNameProvider.createResource(config);
|
||||
|
||||
|
@ -55,10 +69,8 @@ class JarServiceNameDetectorTest {
|
|||
|
||||
@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);
|
||||
getDetector(getArgs("my-service.jar"), prop -> null, JarServiceNameDetectorTest::failPath);
|
||||
|
||||
Resource resource = serviceNameProvider.createResource(config);
|
||||
|
||||
|
@ -69,10 +81,8 @@ class JarServiceNameDetectorTest {
|
|||
|
||||
@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);
|
||||
getDetector(getArgs("my-service"), prop -> null, JarServiceNameDetectorTest::failPath);
|
||||
|
||||
Resource resource = serviceNameProvider.createResource(config);
|
||||
|
||||
|
@ -81,6 +91,11 @@ class JarServiceNameDetectorTest {
|
|||
.containsEntry(ResourceAttributes.SERVICE_NAME, "my-service");
|
||||
}
|
||||
|
||||
static String[] getArgs(String jarName) {
|
||||
String path = Paths.get("path", "to", "app", jarName).toString();
|
||||
return new String[] {"-Dtest=42", "-Xmx666m", "-jar", path, "abc", "def"};
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(SunCommandLineProvider.class)
|
||||
void createResource_sunCommandLine(String commandLine, Path jarPath) {
|
||||
|
@ -89,7 +104,7 @@ class JarServiceNameDetectorTest {
|
|||
Predicate<Path> fileExists = jarPath::equals;
|
||||
|
||||
JarServiceNameDetector serviceNameProvider =
|
||||
new JarServiceNameDetector(() -> new String[0], getProperty, fileExists);
|
||||
getDetector(new String[0], getProperty, fileExists);
|
||||
|
||||
Resource resource = serviceNameProvider.createResource(config);
|
||||
|
||||
|
@ -107,7 +122,7 @@ class JarServiceNameDetectorTest {
|
|||
Predicate<Path> fileExists = path -> false;
|
||||
|
||||
JarServiceNameDetector serviceNameProvider =
|
||||
new JarServiceNameDetector(() -> new String[0], getProperty, fileExists);
|
||||
getDetector(new String[0], getProperty, fileExists);
|
||||
|
||||
Resource resource = serviceNameProvider.createResource(config);
|
||||
|
||||
|
@ -128,7 +143,7 @@ class JarServiceNameDetectorTest {
|
|||
}
|
||||
}
|
||||
|
||||
private static boolean failPath(Path file) {
|
||||
static boolean failPath(Path file) {
|
||||
throw new AssertionError("Unexpected call to Files.isRegularFile()");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.resources;
|
||||
|
||||
import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME;
|
||||
import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_VERSION;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
|
||||
class ManifestResourceProviderTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
JarPathFinder.resetForTest();
|
||||
}
|
||||
|
||||
private static class TestCase {
|
||||
private final String name;
|
||||
private final String expectedName;
|
||||
private final String expectedVersion;
|
||||
private final InputStream input;
|
||||
|
||||
public TestCase(String name, String expectedName, String expectedVersion, InputStream input) {
|
||||
this.name = name;
|
||||
this.expectedName = expectedName;
|
||||
this.expectedVersion = expectedVersion;
|
||||
this.input = input;
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
Collection<DynamicTest> createResource() {
|
||||
ConfigProperties config = DefaultConfigProperties.createFromMap(Collections.emptyMap());
|
||||
|
||||
return Stream.of(
|
||||
new TestCase("name ok", "demo", "0.0.1-SNAPSHOT", openClasspathResource("MANIFEST.MF")),
|
||||
new TestCase("name - no resource", null, null, null),
|
||||
new TestCase(
|
||||
"name - empty resource", null, null, openClasspathResource("empty-MANIFEST.MF")))
|
||||
.map(
|
||||
t ->
|
||||
DynamicTest.dynamicTest(
|
||||
t.name,
|
||||
() -> {
|
||||
ManifestResourceProvider provider =
|
||||
new ManifestResourceProvider(
|
||||
new JarPathFinder(
|
||||
() -> JarServiceNameDetectorTest.getArgs("app.jar"),
|
||||
prop -> null,
|
||||
JarServiceNameDetectorTest::failPath),
|
||||
p -> {
|
||||
try {
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.read(t.input);
|
||||
return Optional.of(manifest);
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
provider.shouldApply(config, Resource.getDefault());
|
||||
|
||||
Resource resource = provider.createResource(config);
|
||||
assertThat(resource.getAttribute(SERVICE_NAME)).isEqualTo(t.expectedName);
|
||||
assertThat(resource.getAttribute(SERVICE_VERSION))
|
||||
.isEqualTo(t.expectedVersion);
|
||||
}))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static InputStream openClasspathResource(String resource) {
|
||||
return ManifestResourceProviderTest.class.getClassLoader().getResourceAsStream(resource);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
Manifest-Version: 1.0
|
||||
Implementation-Title: demo
|
||||
Implementation-Version: 0.0.1-SNAPSHOT
|
|
@ -0,0 +1 @@
|
|||
Manifest-Version: 1.0
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
package io.opentelemetry.smoketest
|
||||
|
||||
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest
|
||||
import io.opentelemetry.semconv.ResourceAttributes
|
||||
import spock.lang.IgnoreIf
|
||||
import spock.lang.Unroll
|
||||
|
||||
|
@ -14,7 +14,6 @@ import java.util.jar.Attributes
|
|||
import java.util.jar.JarFile
|
||||
|
||||
import static io.opentelemetry.smoketest.TestContainerManager.useWindowsContainers
|
||||
import static java.util.stream.Collectors.toSet
|
||||
|
||||
@IgnoreIf({ useWindowsContainers() })
|
||||
class QuarkusSmokeTest extends SmokeTest {
|
||||
|
@ -28,6 +27,11 @@ class QuarkusSmokeTest extends SmokeTest {
|
|||
return new TargetWaitStrategy.Log(Duration.ofMinutes(1), ".*Listening on.*")
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean getSetServiceName() {
|
||||
return false
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "quarkus smoke test on JDK #jdk"(int jdk) {
|
||||
setup:
|
||||
|
@ -37,14 +41,16 @@ class QuarkusSmokeTest extends SmokeTest {
|
|||
|
||||
when:
|
||||
client().get("/hello").aggregate().join()
|
||||
Collection<ExportTraceServiceRequest> traces = waitForTraces()
|
||||
TraceInspector traces = new TraceInspector(waitForTraces())
|
||||
|
||||
then:
|
||||
countSpansByName(traces, 'GET /hello') == 1
|
||||
then: "Expected span names"
|
||||
traces.countSpansByName('GET /hello') == 1
|
||||
|
||||
[currentAgentVersion] as Set == findResourceAttribute(traces, "telemetry.distro.version")
|
||||
.map { it.stringValue }
|
||||
.collect(toSet())
|
||||
and: "telemetry.distro.version is set"
|
||||
traces.countFilteredResourceAttributes("telemetry.distro.version", currentAgentVersion) == 1
|
||||
|
||||
and: "service.name is detected from manifest"
|
||||
traces.countFilteredResourceAttributes(ResourceAttributes.SERVICE_NAME.key, "smoke-test-quarkus-images") == 1
|
||||
|
||||
cleanup:
|
||||
stopTarget()
|
||||
|
|
Loading…
Reference in New Issue