Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EndUserSpanProcessor integration #34595

Merged
merged 1 commit into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
amoscatelli marked this conversation as resolved.
Show resolved Hide resolved
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();
}

}