diff --git a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/AttributeProvider.java b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/AttributeProvider.java
new file mode 100644
index 000000000000..0e74368ff3fd
--- /dev/null
+++ b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/AttributeProvider.java
@@ -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.
+ *
+ *
An example of how to use this interface can be found in {@link ManifestResourceProvider}.
+ */
+interface AttributeProvider {
+ Optional readData();
+
+ void registerAttributes(Builder builder);
+
+ interface Builder {
+ @CanIgnoreReturnValue
+ Builder add(AttributeKey key, Function> getter);
+ }
+}
diff --git a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/AttributeResourceProvider.java b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/AttributeResourceProvider.java
new file mode 100644
index 000000000000..e8ab69d2d3c5
--- /dev/null
+++ b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/AttributeResourceProvider.java
@@ -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.
+ *
+ * An example of how to use this interface can be found in {@link ManifestResourceProvider}.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public abstract class AttributeResourceProvider implements ConditionalResourceProvider {
+
+ private final AttributeProvider attributeProvider;
+
+ public class AttributeBuilder implements AttributeProvider.Builder {
+
+ private AttributeBuilder() {}
+
+ @CanIgnoreReturnValue
+ @Override
+ public AttributeBuilder add(AttributeKey key, Function> getter) {
+ attributeGetters.put((AttributeKey) key, Objects.requireNonNull((Function) getter));
+ return this;
+ }
+ }
+
+ private static final ThreadLocal existingResource = new ThreadLocal<>();
+
+ private final Map, Function>> attributeGetters =
+ new HashMap<>();
+
+ public AttributeResourceProvider(AttributeProvider attributeProvider) {
+ this.attributeProvider = attributeProvider;
+ attributeProvider.registerAttributes(new AttributeBuilder());
+ }
+
+ @Override
+ public final boolean shouldApply(ConfigProperties config, Resource existing) {
+ existingResource.set(existing);
+
+ Map 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 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 void putAttribute(AttributesBuilder builder, AttributeKey key, T value) {
+ builder.put(key, value);
+ }
+
+ private static Map getResourceAttributes(ConfigProperties config) {
+ return config.getMap("otel.resource.attributes");
+ }
+
+ private static boolean shouldUpdate(
+ ConfigProperties config,
+ Resource existing,
+ AttributeKey> key,
+ Map 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;
+ }
+}
diff --git a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarPathFinder.java b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarPathFinder.java
new file mode 100644
index 000000000000..0c25176a6701
--- /dev/null
+++ b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarPathFinder.java
@@ -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 getProcessHandleArguments;
+ private final Function getSystemProperty;
+ private final Predicate fileExists;
+
+ private static class DetectionResult {
+ private final Optional jarPath;
+
+ private DetectionResult(Optional jarPath) {
+ this.jarPath = jarPath;
+ }
+ }
+
+ private static Optional detectionResult = Optional.empty();
+
+ public JarPathFinder() {
+ this(ProcessArguments::getProcessArguments, System::getProperty, Files::isRegularFile);
+ }
+
+ // visible for tests
+ JarPathFinder(
+ Supplier getProcessHandleArguments,
+ Function getSystemProperty,
+ Predicate fileExists) {
+ this.getProcessHandleArguments = getProcessHandleArguments;
+ this.getSystemProperty = getSystemProperty;
+ this.fileExists = fileExists;
+ }
+
+ // visible for testing
+ static void resetForTest() {
+ detectionResult = Optional.empty();
+ }
+
+ Optional 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;
+ }
+}
diff --git a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetector.java b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetector.java
index f77b7e8abd46..51b379f2926a 100644
--- a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetector.java
+++ b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetector.java
@@ -14,16 +14,9 @@
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 getProcessHandleArguments;
- private final Function getSystemProperty;
- private final Predicate 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 getProcessHandleArguments,
- Function getSystemProperty,
- Predicate 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 boolean shouldApply(ConfigProperties config, Resource existing) {
&& "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(".");
diff --git a/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ManifestResourceProvider.java b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ManifestResourceProvider.java
new file mode 100644
index 000000000000..461af3cd99c7
--- /dev/null
+++ b/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ManifestResourceProvider.java
@@ -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 service.name
and
+ * service.version
from META-INF/MANIFEST.MF.
+ */
+@AutoService(ResourceProvider.class)
+public final class ManifestResourceProvider extends AttributeResourceProvider {
+
+ 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> manifestReader) {
+ super(
+ new AttributeProvider() {
+ @Override
+ public Optional readData() {
+ return jarPathFinder.getJarPath().flatMap(manifestReader);
+ }
+
+ @Override
+ public void registerAttributes(Builder 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 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;
+ }
+}
diff --git a/instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetectorTest.java b/instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetectorTest.java
index d9b6e8b9afe0..dd9562d572cd 100644
--- a/instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetectorTest.java
+++ b/instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/JarServiceNameDetectorTest.java
@@ -16,6 +16,7 @@
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.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 getProperty = prop -> null;
+ Predicate 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 getProperty, Predicate 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 @@ void createResource_noJarFileInArgs() {
@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 @@ void createResource_processHandleJar() {
@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 @@ void createResource_processHandleJarWithoutExtension() {
.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 @@ void createResource_sunCommandLine(String commandLine, Path jarPath) {
Predicate 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 @@ void createResource_sunCommandLineProblematicArgs() {
Predicate 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 @@ public Stream extends Arguments> provideArguments(ExtensionContext context) {
}
}
- private static boolean failPath(Path file) {
+ static boolean failPath(Path file) {
throw new AssertionError("Unexpected call to Files.isRegularFile()");
}
}
diff --git a/instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/ManifestResourceProviderTest.java b/instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/ManifestResourceProviderTest.java
new file mode 100644
index 000000000000..d789dbb0ffff
--- /dev/null
+++ b/instrumentation/resources/library/src/test/java/io/opentelemetry/instrumentation/resources/ManifestResourceProviderTest.java
@@ -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 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);
+ }
+}
diff --git a/instrumentation/resources/library/src/test/resources/MANIFEST.MF b/instrumentation/resources/library/src/test/resources/MANIFEST.MF
new file mode 100644
index 000000000000..28f63e3a3517
--- /dev/null
+++ b/instrumentation/resources/library/src/test/resources/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Implementation-Title: demo
+Implementation-Version: 0.0.1-SNAPSHOT
diff --git a/instrumentation/resources/library/src/test/resources/empty-MANIFEST.MF b/instrumentation/resources/library/src/test/resources/empty-MANIFEST.MF
new file mode 100644
index 000000000000..9d885be53412
--- /dev/null
+++ b/instrumentation/resources/library/src/test/resources/empty-MANIFEST.MF
@@ -0,0 +1 @@
+Manifest-Version: 1.0
diff --git a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/QuarkusSmokeTest.groovy b/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/QuarkusSmokeTest.groovy
index 3eb71b6e1b21..9279197eefb2 100644
--- a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/QuarkusSmokeTest.groovy
+++ b/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/QuarkusSmokeTest.groovy
@@ -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 traces = waitForTraces()
+ TraceInspector traces = new TraceInspector(waitForTraces())
+
+ then: "Expected span names"
+ traces.countSpansByName('GET /hello') == 1
- then:
- countSpansByName(traces, 'GET /hello') == 1
+ and: "telemetry.distro.version is set"
+ traces.countFilteredResourceAttributes("telemetry.distro.version", currentAgentVersion) == 1
- [currentAgentVersion] as Set == findResourceAttribute(traces, "telemetry.distro.version")
- .map { it.stringValue }
- .collect(toSet())
+ and: "service.name is detected from manifest"
+ traces.countFilteredResourceAttributes(ResourceAttributes.SERVICE_NAME.key, "smoke-test-quarkus-images") == 1
cleanup:
stopTarget()