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 91df07b commit 0009e87
Show file tree
Hide file tree
Showing 10 changed files with 268 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
@@ -0,0 +1,66 @@
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.assertTrue;

import java.util.List;
import java.util.function.Predicate;

import jakarta.enterprise.inject.Instance;
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.quarkus.it.opentelemetry.util.EndUserResource;
import io.quarkus.opentelemetry.runtime.exporter.otlp.EndUserSpanProcessor;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.security.TestSecurity;

@TestHTTPEndpoint(EndUserResource.class)
@TestSecurity(user = "testUser", roles = { "admin", "user" })
public abstract class AbstractEndUserTest {

@Inject
InMemorySpanExporter inMemorySpanExporter;

@Inject
Instance<EndUserSpanProcessor> endUserSpanProcessor;

protected final Predicate<Instance<EndUserSpanProcessor>> injectionPredicate;

public AbstractEndUserTest(Predicate<Instance<EndUserSpanProcessor>> predicate) {
this.injectionPredicate = predicate;
}

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

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

protected abstract void evaluateAttributes(Attributes attributes);

@Test
protected void baseTest() {
assertTrue(this.injectionPredicate.test(endUserSpanProcessor));
given()
.when().get()
.then()
.statusCode(200);
await().atMost(5, SECONDS).until(() -> getSpans().size() == 1);
SpanData spanData = getSpans().get(0);
evaluateAttributes(spanData.getAttributes());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.it.opentelemetry;

import static org.junit.jupiter.api.Assertions.assertNull;

import jakarta.enterprise.inject.Instance;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class EndUserDisabledTest extends AbstractEndUserTest {

public EndUserDisabledTest() {
super(Instance::isUnsatisfied);
}

@Override
protected void evaluateAttributes(Attributes attributes) {
assertNull(attributes.get(SemanticAttributes.ENDUSER_ID));
assertNull(attributes.get(SemanticAttributes.ENDUSER_ROLE));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.quarkus.it.opentelemetry;

import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.enterprise.inject.Instance;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.quarkus.it.opentelemetry.util.EndUserProfile;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;

@QuarkusTest
@TestProfile(EndUserProfile.class)
public class EndUserEnabledTest extends AbstractEndUserTest {

public EndUserEnabledTest() {
super(Instance::isResolvable);
}

@Override
protected void evaluateAttributes(Attributes attributes) {
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,15 @@
package io.quarkus.it.opentelemetry.util;

import java.util.Map;

import io.quarkus.test.junit.QuarkusTestProfile;

public class EndUserProfile implements QuarkusTestProfile {

@Override
public Map<String, String> getConfigOverrides() {
return Map.of(
"quarkus.otel.traces.eusp.enabled", "true");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.it.opentelemetry.util;

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

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

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

}

0 comments on commit 0009e87

Please sign in to comment.