diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterProcessor.java index 519b1e33d8794..fc7a8519c52d0 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterProcessor.java @@ -6,6 +6,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; import io.quarkus.deployment.annotations.ExecutionTime; @@ -16,6 +17,7 @@ import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig; import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig; import io.quarkus.opentelemetry.runtime.exporter.otlp.OtlpExporterProvider; +import io.quarkus.opentelemetry.runtime.exporter.otlp.EndUserSpanProcessor; import io.quarkus.opentelemetry.runtime.exporter.otlp.OtlpRecorder; @BuildSteps(onlyIf = OtlpExporterProcessor.OtlpExporterEnabled.class) @@ -40,6 +42,18 @@ AdditionalBeanBuildItem createBatchSpanProcessor() { .setUnremovable().build(); } + @BuildStep + void createEndUserSpanProcessor( + BuildProducer buildProducer, + OTelBuildConfig otelBuildConfig + ) { + if (otelBuildConfig.traces().eusp().enabled().orElse(Boolean.FALSE)) { + buildProducer.produce( + AdditionalBeanBuildItem.unremovableOf( + EndUserSpanProcessor.class)); + } + } + @BuildStep @Record(ExecutionTime.RUNTIME_INIT) void installBatchSpanProcessorForOtlp( diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/EndUserSpanProcessorConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/EndUserSpanProcessorConfig.java new file mode 100644 index 0000000000000..5bf7be4f70530 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/EndUserSpanProcessorConfig.java @@ -0,0 +1,25 @@ +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 tracing with OpenTelemetry. + *

+ * This property is not available in the Open Telemetry SDK. It's Quarkus specific. + *

+ * Support for tracing will be enabled if OpenTelemetry support is enabled + * and either this value is true, or this value is unset. + */ + @WithDefault("false") + Optional enabled(); + +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/TracesBuildConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/TracesBuildConfig.java index 1c53611a14f1a..5b6ac3f012f69 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/TracesBuildConfig.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/TracesBuildConfig.java @@ -51,4 +51,9 @@ public interface TracesBuildConfig { */ @WithDefault(SamplerType.Constants.PARENT_BASED_ALWAYS_ON) String sampler(); + + /** + * EndUser SpanProcessor configurations. + */ + EndUserSpanProcessorConfig eusp(); } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/EndUserSpanProcessor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/EndUserSpanProcessor.java new file mode 100644 index 0000000000000..283afb00489df --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/EndUserSpanProcessor.java @@ -0,0 +1,55 @@ +package io.quarkus.opentelemetry.runtime.exporter.otlp; + +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; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.control.ActivateRequestContext; +import jakarta.inject.Inject; +import org.eclipse.microprofile.context.ManagedExecutor; + +@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; + } + +}