Skip to content

Commit

Permalink
Support OpenTelemetry End User attributes added as Span attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
michalvavrik committed May 5, 2024
1 parent 251439e commit 3f05406
Show file tree
Hide file tree
Showing 21 changed files with 1,239 additions and 12 deletions.
17 changes: 17 additions & 0 deletions docs/src/main/asciidoc/opentelemetry.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,23 @@ public class CustomConfiguration {
}
----

==== End User attributes

When enabled, Quarkus adds OpenTelemetry End User attributes as Span attributes.

Check warning on line 403 in docs/src/main/asciidoc/opentelemetry.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/opentelemetry.adoc", "range": {"start": {"line": 403, "column": 13}}}, "severity": "INFO"}
The attributes are only added when authentication has already happened on a best-efforts basis.

[source,application.properties]
----
quarkus.otel.traces.eusp.enabled=true <1>
quarkus.http.auth.proactive=true <2>
----
<1> Enable the End User Attributes feature so that the `SecurityIdentity` principal and roles are added as Span attributes.
The End User attributes are personally identifiable information, therefore make sure you want to export them before you enable this feature.

Check warning on line 412 in docs/src/main/asciidoc/opentelemetry.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'verify' rather than 'make sure' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'verify' rather than 'make sure' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/opentelemetry.adoc", "range": {"start": {"line": 412, "column": 27}}}, "severity": "WARNING"}
<2> Optionally enable proactive authentication.
The best possible results are achieved when proactive authentication is enabled because the authentication happens sooner.

IMPORTANT: This feature is not supported when a custom xref:security-customization.adoc#jaxrs-security-context[Jakarta REST SecurityContexts] is used.

[[sampler]]
=== Sampler
A https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling[sampler] decides whether a trace should be discarded or forwarded, effectively managing noise and reducing overhead by limiting the number of collected traces sent to the collector.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package io.quarkus.opentelemetry.deployment.tracing;

import static io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig.SecurityEvents.SecurityEventType.ALL;
import static io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig.SecurityEvents.SecurityEventType.AUTHENTICATION_SUCCESS;
import static io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig.SecurityEvents.SecurityEventType.AUTHORIZATION_FAILURE;
import static io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig.SecurityEvents.SecurityEventType.AUTHORIZATION_SUCCESS;

import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -11,6 +16,7 @@
import java.util.Set;
import java.util.function.BooleanSupplier;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.spi.EventContext;

import org.jboss.jandex.AnnotationInstance;
Expand All @@ -21,14 +27,21 @@
import org.jboss.jandex.MethodInfo;
import org.jboss.logging.Logger;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.IdGenerator;
import io.opentelemetry.sdk.trace.ReadWriteSpan;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.quarkus.arc.Unremovable;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
import io.quarkus.arc.deployment.ObserverRegistrationPhaseBuildItem;
import io.quarkus.arc.deployment.ObserverRegistrationPhaseBuildItem.ObserverConfiguratorBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
Expand All @@ -43,6 +56,8 @@
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ApplicationInfoBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig;
Expand Down Expand Up @@ -198,6 +213,110 @@ void registerSecurityEventObserver(Capabilities capabilities, OTelBuildConfig bu
}
}

/**
* Generates {@link SpanProcessor} that adds end-user attributes to new Spans.
*
* Generates:
*
* <code>
* import io.quarkus.arc.Unremovable;
* import io.quarkus.opentelemetry.runtime.tracing.security.SecurityEventUtil;
* import jakarta.enterprise.context.Dependent;
*
* import io.opentelemetry.context.Context;
* import io.opentelemetry.sdk.trace.ReadWriteSpan;
* import io.opentelemetry.sdk.trace.ReadableSpan;
* import io.opentelemetry.sdk.trace.SpanProcessor;
*
* &#064;Dependent
* &#064;Unremovable
* public class EndUserSpanProcessor implements SpanProcessor {
*
* &#064;Override
* public void onStart(Context parentContext, ReadWriteSpan span) {
* SecurityEventUtil.addEndUserAttributes(span);
* }
*
* &#064;Override
* public boolean isStartRequired() {
* return Boolean.TRUE;
* }
*
* &#064;Override
* public void onEnd(ReadableSpan span) {
* }
*
* &#064;Override
* public boolean isEndRequired() {
* return Boolean.FALSE;
* }
*
* }
* </code>
*
*/
@BuildStep(onlyIf = EndUserAttributesEnabled.class)
void generateEndUserAttributesSpanProcessor(BuildProducer<GeneratedBeanBuildItem> generatedBeans,
Capabilities capabilities) {
if (capabilities.isPresent(Capability.SECURITY)) {
ClassOutput classOutput = new GeneratedBeanGizmoAdaptor(generatedBeans);

try (ClassCreator classCreator = ClassCreator.builder().classOutput(classOutput)
.className("io.quarkus.opentelemetry.runtime.tracing.security.EndUserSpanProcessor")
.interfaces(SpanProcessor.class.getName())
.build()) {
classCreator.addAnnotation(Dependent.class);
classCreator.addAnnotation(Unremovable.class);

try (var methodCreator = classCreator.getMethodCreator("onStart", void.class, Context.class,
ReadWriteSpan.class)) {
methodCreator.setModifiers(Modifier.PUBLIC);
methodCreator.addAnnotation(Override.class.getName(), RetentionPolicy.CLASS);

// SecurityEventUtil.addEndUserAttributes(span);
methodCreator.invokeStaticMethod(
MethodDescriptor.ofMethod(SecurityEventUtil.class, "addEndUserAttributes",
void.class, Span.class),
methodCreator.getMethodParam(1));

methodCreator.returnVoid();
}

try (var methodCreator = classCreator.getMethodCreator("isStartRequired", boolean.class)) {
methodCreator.setModifiers(Modifier.PUBLIC);
methodCreator.addAnnotation(Override.class.getName(), RetentionPolicy.CLASS);
methodCreator.returnBoolean(true);
}

try (var methodCreator = classCreator.getMethodCreator("onEnd", void.class, ReadableSpan.class)) {
methodCreator.setModifiers(Modifier.PUBLIC);
methodCreator.addAnnotation(Override.class.getName(), RetentionPolicy.CLASS);
methodCreator.returnVoid();
}

try (var methodCreator = classCreator.getMethodCreator("isEndRequired", boolean.class)) {
methodCreator.setModifiers(Modifier.PUBLIC);
methodCreator.addAnnotation(Override.class.getName(), RetentionPolicy.CLASS);
methodCreator.returnBoolean(false);
}
}
}
}

@BuildStep(onlyIf = EndUserAttributesEnabled.class)
void registerEndUserAttributesEventObserver(Capabilities capabilities,
ObserverRegistrationPhaseBuildItem observerRegistrationPhase,
BuildProducer<ObserverConfiguratorBuildItem> observerProducer) {
if (capabilities.isPresent(Capability.SECURITY)) {
observerProducer
.produce(createEventObserver(observerRegistrationPhase, AUTHENTICATION_SUCCESS, "addEndUserAttributes"));
observerProducer
.produce(createEventObserver(observerRegistrationPhase, AUTHORIZATION_SUCCESS, "updateEndUserAttributes"));
observerProducer
.produce(createEventObserver(observerRegistrationPhase, AUTHORIZATION_FAILURE, "updateEndUserAttributes"));
}
}

private static ObserverConfiguratorBuildItem createEventObserver(
ObserverRegistrationPhaseBuildItem observerRegistrationPhase, SecurityEventType eventType, String utilMethodName) {
return new ObserverConfiguratorBuildItem(observerRegistrationPhase.getContext()
Expand Down Expand Up @@ -232,4 +351,18 @@ public boolean getAsBoolean() {
return enabled;
}
}

static final class EndUserAttributesEnabled implements BooleanSupplier {

private final boolean enabled;

EndUserAttributesEnabled(OTelBuildConfig config) {
this.enabled = config.traces().addEndUserAttributes();
}

@Override
public boolean getAsBoolean() {
return enabled;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

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

/**
* Tracing build time configuration
Expand Down Expand Up @@ -51,4 +52,15 @@ public interface TracesBuildConfig {
*/
@WithDefault(SamplerType.Constants.PARENT_BASED_ALWAYS_ON)
String sampler();

/**
* If OpenTelemetry End User attributes should be added as Span attributes on a best-efforts basis.
*
* @see <a href="https://opentelemetry.io/docs/specs/semconv/attributes-registry/enduser/">OpenTelemetry End User
* attributes</a>
*/
@WithName("eusp.enabled")
@WithDefault("false")
boolean addEndUserAttributes();

}
Loading

0 comments on commit 3f05406

Please sign in to comment.