Skip to content

Commit

Permalink
Merge pull request #38377 from brunobat/new-http-semconv-otel
Browse files Browse the repository at this point in the history
Migration path for the new HTTP semantic conventions
  • Loading branch information
brunobat authored Jan 25, 2024
2 parents e29a90c + 23249f3 commit b0199e8
Show file tree
Hide file tree
Showing 24 changed files with 459 additions and 126 deletions.
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/opentelemetry.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/au
with the `quarkus.*` prefix.
- Extensions and the libraries they provide, are directly instrumented in Quarkus. The *use of the https://opentelemetry.io/docs/instrumentation/java/automatic/[OpenTelemetry Agent] is not needed nor recommended* due to context propagation issues between imperative and reactive libraries.
- If you come from the legacy OpenTracing extension, there is a xref:telemetry-opentracing-to-otel-tutorial.adoc[guide to help with the migration].
- Current Semantic Conventions for HTTP will soon change and the current conventions are deprecated for removal soon. Please move to the new conventions by seetinh the new property `quarkus.otel.semconv-stability.opt-in` to `http`, for the new conventions or `http/dup` to produce duplicated old and new conventions. Please check the <<configuration-reference>> for more details and full set of changes at the https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/migration-guide.md#summary-of-changes[HTTP semantic convention stability migration guide].
====

== Prerequisites
Expand Down
32 changes: 32 additions & 0 deletions extensions/opentelemetry/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,38 @@
</extensions>

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<!-- Default test with legacy http semantic conventions plus and
execution with the new ones. We can have 1 execution again, after otel instrumentaion 2.x
Run tests: mvn surefire:test@arquillian -Dtest=MyTest -Dmaven.surefire.debug -->
<execution>
<id>new-http-semconv</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemPropertyVariables>
<quarkus.otel.semconv-stability.opt-in>http</quarkus.otel.semconv-stability.opt-in>
</systemPropertyVariables>
</configuration>
</execution>
<execution>
<id>http-dup-semconv</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemPropertyVariables>
<quarkus.otel.semconv-stability.opt-in>http/dup</quarkus.otel.semconv-stability.opt-in>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem.SPI_ROOT;
import static io.quarkus.opentelemetry.runtime.OpenTelemetryRecorder.OPEN_TELEMETRY_DRIVER;
import static io.quarkus.opentelemetry.runtime.OpenTelemetryUtil.*;
import static java.util.stream.Collectors.toList;

import java.io.IOException;
Expand Down Expand Up @@ -93,6 +94,8 @@ public boolean test(AnnotationInstance annotationInstance) {
private static final DotName WITH_SPAN_INTERCEPTOR = DotName.createSimple(WithSpanInterceptor.class.getName());
private static final DotName ADD_SPAN_ATTRIBUTES_INTERCEPTOR = DotName
.createSimple(AddingSpanAttributesInterceptor.class.getName());
private static final String QUARKUS_OTEL_SEMCONV_STABILITY_OPT_IN = "quarkus.otel.semconv-stability.opt-in";
private static final String OTEL_SEMCONV_STABILITY_OPT_IN = "otel.semconv-stability.opt-in";

@BuildStep
AdditionalBeanBuildItem ensureProducerIsRetained() {
Expand All @@ -109,6 +112,15 @@ AdditionalBeanBuildItem ensureProducerIsRetained() {
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
SyntheticBeanBuildItem openTelemetryBean(OpenTelemetryRecorder recorder, OTelRuntimeConfig oTelRuntimeConfig) {

final String semconvStability = ConfigProvider.getConfig()
.getConfigValue(QUARKUS_OTEL_SEMCONV_STABILITY_OPT_IN)
.getValue();
if (semconvStability != null && !semconvStability.isEmpty()) {
// yes, they ignore config supplier on this.
System.setProperty(OTEL_SEMCONV_STABILITY_OPT_IN, semconvStability);
}

return SyntheticBeanBuildItem.configure(OpenTelemetry.class)
.defaultBean()
.setRuntimeInit()
Expand Down Expand Up @@ -270,7 +282,11 @@ void setupVertx(InstrumentationRecorder recorder, BeanContainerBuildItem beanCon
|| capabilities.isPresent(Capability.REACTIVE_MYSQL_CLIENT)
|| capabilities.isPresent(Capability.REACTIVE_ORACLE_CLIENT)
|| capabilities.isPresent(Capability.REACTIVE_PG_CLIENT);
recorder.setupVertxTracer(beanContainerBuildItem.getValue(), sqlClientAvailable);
recorder.setupVertxTracer(beanContainerBuildItem.getValue(),
sqlClientAvailable,
ConfigProvider.getConfig()
.getConfigValue(QUARKUS_OTEL_SEMCONV_STABILITY_OPT_IN)
.getValue());
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.opentelemetry.deployment.OpenTelemetryEnabled;
import io.quarkus.opentelemetry.runtime.scheduler.OpenTelemetryJobInstrumenter;
import io.quarkus.opentelemetry.runtime.tracing.intrumentation.scheduler.OpenTelemetryJobInstrumenter;

public class OpenTelemetrySchedulerProcessor {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.quarkus.opentelemetry.TextMapPropagatorCustomizer;
import io.quarkus.opentelemetry.deployment.common.TestSpanExporter;
import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider;
import io.quarkus.opentelemetry.runtime.propagation.TextMapPropagatorCustomizer;
import io.quarkus.test.QuarkusUnitTest;

public class OpenTelemetryTextMapPropagatorCustomizerTest {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package io.quarkus.opentelemetry.deployment.common;

import static io.opentelemetry.api.common.AttributeType.LONG;
import static io.opentelemetry.api.common.AttributeType.STRING;
import static io.opentelemetry.semconv.SemanticAttributes.HTTP_TARGET;
import static io.opentelemetry.semconv.SemanticAttributes.URL_PATH;
import static io.opentelemetry.semconv.SemanticAttributes.URL_QUERY;
import static io.quarkus.opentelemetry.runtime.config.runtime.SemconvStabilityType.HTTP;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.util.HashMap;
import java.util.Map;

import org.jboss.logging.Logger;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.quarkus.opentelemetry.runtime.OpenTelemetryUtil;
import io.quarkus.opentelemetry.runtime.config.runtime.SemconvStabilityType;

public class SemconvResolver {

public static final SemconvStabilityType SEMCONV_STABILITY_TYPE;
private static final Map<String, String> conventionsMapper = new HashMap<>();
private static final Logger log = Logger.getLogger(SemconvResolver.class);

static {
SEMCONV_STABILITY_TYPE = OpenTelemetryUtil.getSemconvStabilityOptin(
System.getProperty("quarkus.otel.semconv-stability.opt-in", "stable"));

log.info("Using semantic convention stability type: " + SEMCONV_STABILITY_TYPE);

conventionsMapper.put("http.method", "http.request.method");
conventionsMapper.put("http.status_code", "http.response.status_code");
conventionsMapper.put("http.request_content_length", "http.request.body.size");
conventionsMapper.put("http.response_content_length", "http.request.body.size");
conventionsMapper.put("net.protocol.name", "network.protocol.name");
conventionsMapper.put("net.protocol.version", "network.protocol.version");
// net.sock.family removed
conventionsMapper.put("net.sock.peer.addr", "network.peer.address");
conventionsMapper.put("net.sock.peer.port", "network.peer.port");
// net.sock.peer.name removed
// New: http.request.method_original
// New: error.type
conventionsMapper.put("http.url", "url.full");
conventionsMapper.put("http.resend_count", "http.request.resend_count");
conventionsMapper.put("net.peer.name", "server.address");
conventionsMapper.put("net.peer.port", "server.port");
// http.target split into url.path and url.query
conventionsMapper.put("http.scheme", "url.scheme");
conventionsMapper.put("http.client_ip", "client.address");
conventionsMapper.put("net.host.name", "server.address");
conventionsMapper.put("net.host.port", "server.port");

}

SemconvResolver() {
// empty
}

public static void assertTarget(final SpanData server, final String path, final String query) {
switch (SEMCONV_STABILITY_TYPE) {
case HTTP:
assertEquals(path, server.getAttributes().get(URL_PATH));
assertEquals(query, server.getAttributes().get(URL_QUERY));
break;
case HTTP_DUP:
assertEquals(path, server.getAttributes().get(URL_PATH));
assertEquals(query, server.getAttributes().get(URL_QUERY));
assertEquals("" + path + (query == null ? "" : "?" + query), server.getAttributes().get(HTTP_TARGET));
break;
case HTTP_OLD:
assertEquals("" + path + (query == null ? "" : "?" + query), server.getAttributes().get(HTTP_TARGET));
break;
default:
throw new IllegalArgumentException("Unsupported semantic convention stability type: " + SEMCONV_STABILITY_TYPE);
}
}

public static <T> void assertSemanticAttribute(final SpanData spanData, final T expected,
final AttributeKey<T> attribute) {
switch (SEMCONV_STABILITY_TYPE) {
case HTTP:
assertEquals(expected, getNewAttribute(spanData, attribute));
break;
case HTTP_DUP:
assertEquals(expected, getNewAttribute(spanData, attribute)); // assert new semantic convention
assertEquals(expected, spanData.getAttributes().get(attribute)); // assert old semantic convention
break;
case HTTP_OLD:
assertEquals(expected, spanData.getAttributes().get(attribute)); // assert old semantic convention
break;
default:
throw new IllegalArgumentException("Unsupported semantic convention stability type: " + SEMCONV_STABILITY_TYPE);
}
}

public static <T> void assertNotNullSemanticAttribute(final SpanData spanData,
final AttributeKey<T> attribute) {
switch (SemconvResolver.SEMCONV_STABILITY_TYPE) {
case HTTP:
assertNotNull(getNewAttribute(spanData, attribute));
break;
case HTTP_DUP:
assertNotNull(getNewAttribute(spanData, attribute)); // assert new semantic convention
assertNotNull(spanData.getAttributes().get(attribute)); // assert old semantic convention
break;
case HTTP_OLD:
assertNotNull(spanData.getAttributes().get(attribute)); // assert old semantic convention
break;
default:
throw new IllegalArgumentException(
"Unsupported semantic convention stability type: " + SemconvResolver.SEMCONV_STABILITY_TYPE);
}
}

@SuppressWarnings("unchecked")
private static <T> T getNewAttribute(final SpanData data, final AttributeKey<T> legacyAttributeKey) {
if (legacyAttributeKey.getType().equals(LONG)) {
return (T) data.getAttributes().get(resolveLong((AttributeKey<Long>) legacyAttributeKey));
} else if (legacyAttributeKey.getType().equals(STRING)) {
return (T) data.getAttributes().get(resolveString((AttributeKey<String>) legacyAttributeKey));
} else {
throw new IllegalArgumentException(
"Unsupported attribute: " + legacyAttributeKey.getKey() +
" with type: " + legacyAttributeKey.getKey().getClass());
}
}

private static AttributeKey<String> resolveString(final AttributeKey<String> legacyKey) {
return AttributeKey.stringKey(conventionsMapper.get(legacyKey.getKey()));
}

private static AttributeKey<Long> resolveLong(final AttributeKey<Long> legacyKey) {
return AttributeKey.longKey(conventionsMapper.get(legacyKey.getKey()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static io.opentelemetry.semconv.SemanticAttributes.HTTP_METHOD;
import static io.opentelemetry.semconv.SemanticAttributes.HTTP_ROUTE;
import static io.opentelemetry.semconv.SemanticAttributes.HTTP_STATUS_CODE;
import static io.quarkus.opentelemetry.deployment.common.SemconvResolver.assertSemanticAttribute;
import static io.quarkus.opentelemetry.deployment.common.TestSpanExporter.getSpanByKindAndParentId;
import static java.net.HttpURLConnection.HTTP_OK;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -45,6 +46,7 @@
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.quarkus.opentelemetry.deployment.common.SemconvResolver;
import io.quarkus.opentelemetry.deployment.common.TestSpanExporter;
import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider;
import io.quarkus.test.QuarkusUnitTest;
Expand All @@ -56,7 +58,7 @@ public class GraphQLOpenTelemetryTest {
static QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(HelloResource.class, CustomCDIBean.class, TestSpanExporterProvider.class,
TestSpanExporter.class)
TestSpanExporter.class, SemconvResolver.class)
.addAsResource(new StringAsset("smallrye.graphql.allowGet=true"), "application.properties")
.addAsResource(new StringAsset("smallrye.graphql.printDataFetcherException=true"), "application.properties")
.addAsResource(new StringAsset("smallrye.graphql.events.enabled=true"), "application.properties")
Expand Down Expand Up @@ -279,10 +281,9 @@ private void assertTimeForSpans(List<SpanData> spans) {
private SpanData assertHttpSpan(List<SpanData> spans) {
final SpanData server = getSpanByKindAndParentId(spans, SpanKind.SERVER, "0000000000000000");
assertEquals("POST /graphql", server.getName());
assertEquals(HTTP_OK, server.getAttributes().get(HTTP_STATUS_CODE));
assertEquals("POST", server.getAttributes().get(HTTP_METHOD));
assertSemanticAttribute(server, (long) HTTP_OK, HTTP_STATUS_CODE);
assertSemanticAttribute(server, "POST", HTTP_METHOD);
assertEquals("/graphql", server.getAttributes().get(HTTP_ROUTE));

return server;
}

Expand Down
Loading

0 comments on commit b0199e8

Please sign in to comment.