diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameTest.java new file mode 100644 index 0000000000000..363cb7864c049 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryServiceNameTest.java @@ -0,0 +1,68 @@ +package io.quarkus.opentelemetry.deployment; + +import static io.opentelemetry.api.trace.SpanKind.SERVER; +import static io.quarkus.opentelemetry.deployment.common.TestSpanExporter.getSpanByKindAndParentId; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.smallrye.config.SmallRyeConfig; + +public class OpenTelemetryServiceNameTest { + + private static final String SERVICE_NAME = "FrankBullitt"; + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(TestSpanExporter.class) + .addClass(TestSpanExporterProvider.class) + .addAsResource("resource-config/application.properties", "application.properties") + .addAsResource( + "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) + .overrideRuntimeConfigKey("quarkus.otel.service.name", SERVICE_NAME); + + @Inject + SmallRyeConfig config; + @Inject + TestSpanExporter spanExporter; + + @Test + void testSvcNameHasPriorityOverAppNameAndResourceAttr() { + RestAssured.when() + .get("/hello").then() + .statusCode(200) + .body(is("hello")); + + List spans = spanExporter.getFinishedSpanItems(1); + + final SpanData server = getSpanByKindAndParentId(spans, SERVER, "0000000000000000"); + assertEquals("GET /hello", server.getName()); + assertEquals(SERVICE_NAME, server.getResource().getAttribute(AttributeKey.stringKey("service.name"))); + } + + @Path("/hello") + public static class HelloResource { + @GET + public String hello() { + return "hello"; + } + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryProducer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryProducer.java index 4df1f69659ba9..d1ad23b1dd432 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryProducer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryProducer.java @@ -32,6 +32,7 @@ import io.quarkus.opentelemetry.runtime.tracing.DropTargetsSampler; import io.quarkus.opentelemetry.runtime.tracing.TracerRecorder; import io.quarkus.opentelemetry.runtime.tracing.TracerUtil; +import io.quarkus.runtime.ApplicationConfig; import io.smallrye.config.ConfigValue; import io.smallrye.config.NameIterator; import io.smallrye.config.SmallRyeConfig; @@ -58,6 +59,9 @@ public class OpenTelemetryProducer { @Inject OTelRuntimeConfig oTelRuntimeConfig; + @Inject + ApplicationConfig appConfig; + @Produces @Singleton @DefaultBean @@ -85,11 +89,20 @@ public Resource apply(Resource existingResource, ConfigProperties configProperti if (oTelBuildConfig.traces().enabled().orElse(TRUE)) { Resource consolidatedResource = existingResource.merge( Resource.create(delayedAttributes.get())); // from cdi + + // if user explicitly set 'otel.service.name', make sure we don't override it with defaults + // inside resource customizer + String serviceName = oTelRuntimeConfig + .serviceName() + .filter(sn -> !sn.equals(appConfig.name.orElse("unset"))) + .orElse(null); + // Merge resource instances with env attributes Resource resource = resources.stream() .reduce(Resource.empty(), Resource::merge) - .merge(TracerUtil - .mapResourceAttributes(oTelRuntimeConfig.resourceAttributes().orElse(emptyList()))); // from properties + .merge(TracerUtil.mapResourceAttributes( + oTelRuntimeConfig.resourceAttributes().orElse(emptyList()), + serviceName)); // from properties return consolidatedResource.merge(resource); } else { return Resource.builder().build(); diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java index e90f5790c6b3d..3c9a6ff394bfc 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtil.java @@ -1,5 +1,7 @@ package io.quarkus.opentelemetry.runtime.tracing; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; + import java.util.List; import io.opentelemetry.api.common.Attributes; @@ -8,15 +10,25 @@ import io.quarkus.opentelemetry.runtime.OpenTelemetryUtil; public class TracerUtil { + private TracerUtil() { } - public static Resource mapResourceAttributes(List resourceAttributes) { + public static Resource mapResourceAttributes(List resourceAttributes, String serviceName) { if (resourceAttributes.isEmpty()) { return Resource.empty(); } AttributesBuilder attributesBuilder = Attributes.builder(); - OpenTelemetryUtil.convertKeyValueListToMap(resourceAttributes).forEach(attributesBuilder::put); + var attrNameToValue = OpenTelemetryUtil.convertKeyValueListToMap(resourceAttributes); + + // override both default (app name) and explicitly set resource attribute + // it needs to be done manually because OpenTelemetry correctly sets 'otel.service.name' + // to existing (incoming) resource, but customizer output replaces originally set service name + if (serviceName != null) { + attrNameToValue.put(SERVICE_NAME.getKey(), serviceName); + } + + attrNameToValue.forEach(attributesBuilder::put); return Resource.create(attributesBuilder.build()); } } diff --git a/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtilTest.java b/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtilTest.java index 436496ce75939..c3b27767970ef 100644 --- a/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtilTest.java +++ b/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/tracing/TracerUtilTest.java @@ -19,7 +19,7 @@ public void testMapResourceAttributes() { "service.namespace=mynamespace", "service.version=1.0", "deployment.environment=production"); - Resource resource = TracerUtil.mapResourceAttributes(resourceAttributes); + Resource resource = TracerUtil.mapResourceAttributes(resourceAttributes, null); Attributes attributes = resource.getAttributes(); Assertions.assertThat(attributes.size()).isEqualTo(4); Assertions.assertThat(attributes.get(ResourceAttributes.SERVICE_NAME)).isEqualTo("myservice");