Skip to content

Commit

Permalink
EndUserSpanProcessor integration
Browse files Browse the repository at this point in the history
  • Loading branch information
A.Moscatelli authored and amoscatelli committed Jul 27, 2023
1 parent acaa257 commit 8c53459
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.ExecutionTime;
Expand All @@ -23,6 +25,7 @@
import io.quarkus.opentelemetry.runtime.config.build.exporter.OtlpExporterBuildConfig;
import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig;
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig;
import io.quarkus.opentelemetry.runtime.exporter.otlp.EndUserSpanProcessor;
import io.quarkus.opentelemetry.runtime.exporter.otlp.LateBoundBatchSpanProcessor;
import io.quarkus.opentelemetry.runtime.exporter.otlp.OtlpRecorder;
import io.quarkus.vertx.core.deployment.CoreVertxBuildItem;
Expand All @@ -42,6 +45,17 @@ public boolean getAsBoolean() {
}
}

@BuildStep
void createEndUserSpanProcessor(
BuildProducer<AdditionalBeanBuildItem> buildProducer,
OTelBuildConfig otelBuildConfig) {
if (otelBuildConfig.traces().eusp().enabled().orElse(Boolean.FALSE)) {
buildProducer.produce(
AdditionalBeanBuildItem.unremovableOf(
EndUserSpanProcessor.class));
}
}

@SuppressWarnings("deprecation")
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkus.opentelemetry.runtime.config.build;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.smallrye.config.WithDefault;

/**
* Tracing build time configuration
*/
@ConfigGroup
public interface EndUserSpanProcessorConfig {

/**
* Enable the {@link io.quarkus.opentelemetry.runtime.exporter.otlp.EndUserSpanProcessor}.
* <p>
* The {@link io.quarkus.opentelemetry.runtime.exporter.otlp.EndUserSpanProcessor} adds
* the {@link io.opentelemetry.semconv.trace.attributes.SemanticAttributes.ENDUSER_ID}
* and {@link io.opentelemetry.semconv.trace.attributes.SemanticAttributes.ENDUSER_ROLE} to the Span.
*/
@WithDefault("false")
Optional<Boolean> enabled();

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,9 @@ public interface TracesBuildConfig {
*/
@WithDefault(SamplerType.Constants.PARENT_BASED_ALWAYS_ON)
String sampler();

/**
* EndUser SpanProcessor configurations.
*/
EndUserSpanProcessorConfig eusp();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.quarkus.opentelemetry.runtime.exporter.otlp;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.control.ActivateRequestContext;
import jakarta.inject.Inject;

import org.eclipse.microprofile.context.ManagedExecutor;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.ReadWriteSpan;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.quarkus.security.identity.SecurityIdentity;

@ApplicationScoped
public class EndUserSpanProcessor implements SpanProcessor {

@Inject
protected SecurityIdentity securityIdentity;

@Inject
protected ManagedExecutor managedExecutor;

@Override
@ActivateRequestContext
public void onStart(Context parentContext, ReadWriteSpan span) {
managedExecutor.execute(
() -> span.setAllAttributes(
securityIdentity.isAnonymous()
? Attributes.empty()
: Attributes.of(
SemanticAttributes.ENDUSER_ID,
securityIdentity.getPrincipal().getName(),
SemanticAttributes.ENDUSER_ROLE,
securityIdentity.getRoles().toString())));
}

@Override
public boolean isStartRequired() {
return Boolean.TRUE;
}

@Override
public void onEnd(ReadableSpan span) {
}

@Override
public boolean isEndRequired() {
return Boolean.FALSE;
}

}
24 changes: 24 additions & 0 deletions integration-tests/opentelemetry/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
<groupId>io.smallrye.reactive</groupId>
<artifactId>smallrye-mutiny-vertx-web-client</artifactId>
</dependency>

<!-- Security -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>

<!-- Needed for InMemorySpanExporter to verify captured traces -->
<dependency>
Expand All @@ -53,6 +59,11 @@
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
Expand Down Expand Up @@ -117,6 +128,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ quarkus.application.version=999-SNAPSHOT
quarkus.otel.bsp.schedule.delay=100
quarkus.otel.bsp.export.timeout=5s

quarkus.otel.traces.eusp.enabled=true

pingpong/mp-rest/url=${test.url}
simple/mp-rest/url=${test.url}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.quarkus.it.opentelemetry;

import static io.restassured.RestAssured.given;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.List;

import jakarta.inject.Inject;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.quarkus.it.opentelemetry.util.EndUserResource;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;

@QuarkusTest
@TestHTTPEndpoint(EndUserResource.class)
public class EndUserTest {

@Inject
InMemorySpanExporter inMemorySpanExporter;

@BeforeEach
@AfterEach
void reset() {
inMemorySpanExporter.reset();
}

private List<SpanData> getSpans() {
return inMemorySpanExporter.getFinishedSpanItems();
}

@Test
@TestSecurity(user = "testUser", roles = { "admin", "user" })
public void testEndUserInjections() {
given()
.when().get()
.then()
.statusCode(200);
await().atMost(5, SECONDS).until(() -> getSpans().size() == 1);
SpanData spanData = getSpans().get(0);
Attributes attributes = spanData.getAttributes();
assertEquals(attributes.get(SemanticAttributes.ENDUSER_ID), "testUser");
assertEquals(attributes.get(SemanticAttributes.ENDUSER_ROLE), "[admin, user]");
}

@Test
@TestSecurity(user = "testUser", roles = { "admin", "user" })
public void testEndUserInjectionsAsync() {
given()
.when().get("/async")
.then()
.statusCode(200);
await().atMost(5, SECONDS).until(() -> getSpans().size() == 1);
SpanData spanData = getSpans().get(0);
Attributes attributes = spanData.getAttributes();
assertEquals(attributes.get(SemanticAttributes.ENDUSER_ID), "testUser");
assertEquals(attributes.get(SemanticAttributes.ENDUSER_ROLE), "[admin, user]");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.quarkus.it.opentelemetry.util;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

import org.junit.jupiter.api.Assertions;

import io.quarkus.opentelemetry.runtime.exporter.otlp.EndUserSpanProcessor;
import io.smallrye.mutiny.Uni;

@Path("/otel/enduser")
@RequestScoped
public class EndUserResource {

@Inject
EndUserSpanProcessor endUserSpanProcessor;

@GET
public Response verifyOTelInjections() {
verifyInjections();
return Response.ok().build();
}

@GET
@Path("/async")
public Uni<Response> verifyOTelInjectionsAsync() {
verifyInjections();
return Uni.createFrom().item(Response.ok().build());
}

private void verifyInjections() {
Assertions.assertNotNull(endUserSpanProcessor, "EndUserSpanProcessor cannot be injected");
}
}

0 comments on commit 8c53459

Please sign in to comment.