From 7cdb1d4a508ade4a47d084c0468deca4b8540079 Mon Sep 17 00:00:00 2001 From: Tim Quinn Date: Tue, 3 Dec 2024 15:34:35 -0600 Subject: [PATCH 1/3] Add telemetry filter helper feature so developer code can influence whether incoming or outgoing spans are automatically created --- .../HelidonTelemetryClientFilter.java | 28 +++++ .../HelidonTelemetryContainerFilter.java | 28 +++++ .../HelidonTelemetryClientFilterHelper.java | 33 ++++++ ...HelidonTelemetryContainerFilterHelper.java | 33 ++++++ .../telemetry/spi/package-info.java | 19 +++ .../telemetry/src/main/java/module-info.java | 10 +- tests/integration/mp-telemetry/pom.xml | 110 ++++++++++++++++++ ...ectorSuppressPersonalizedGreetingSpan.java | 29 +++++ .../mp/filterselectivity/GreetResource.java | 43 +++++++ .../src/main/resources/META-INF/beans.xml | 25 ++++ .../META-INF/microprofile-config.properties | 20 ++++ ....spi.HelidonTelemetryContainerFilterHelper | 1 + .../filterselectivity/TestSpanExporter.java | 95 +++++++++++++++ .../TestSpanExporterProvider.java | 38 ++++++ .../TestSpanSelectivity.java | 73 ++++++++++++ ...pi.traces.ConfigurableSpanExporterProvider | 17 +++ tests/integration/pom.xml | 1 + 17 files changed, 599 insertions(+), 4 deletions(-) create mode 100644 microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/HelidonTelemetryClientFilterHelper.java create mode 100644 microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/HelidonTelemetryContainerFilterHelper.java create mode 100644 microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/package-info.java create mode 100644 tests/integration/mp-telemetry/pom.xml create mode 100644 tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/FilterSelectorSuppressPersonalizedGreetingSpan.java create mode 100644 tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/GreetResource.java create mode 100644 tests/integration/mp-telemetry/src/main/resources/META-INF/beans.xml create mode 100644 tests/integration/mp-telemetry/src/main/resources/META-INF/microprofile-config.properties create mode 100644 tests/integration/mp-telemetry/src/main/resources/META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper create mode 100644 tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanExporter.java create mode 100644 tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanExporterProvider.java create mode 100644 tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanSelectivity.java create mode 100644 tests/integration/mp-telemetry/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider diff --git a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryClientFilter.java b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryClientFilter.java index f506661cccd..059ef4abbd3 100644 --- a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryClientFilter.java +++ b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryClientFilter.java @@ -17,8 +17,12 @@ import java.util.List; import java.util.Optional; +import java.util.ServiceLoader; import java.util.Set; +import io.helidon.common.HelidonServiceLoader; +import io.helidon.common.LazyValue; +import io.helidon.microprofile.telemetry.spi.HelidonTelemetryClientFilterHelper; import io.helidon.tracing.HeaderConsumer; import io.helidon.tracing.HeaderProvider; import io.helidon.tracing.Scope; @@ -55,6 +59,11 @@ class HelidonTelemetryClientFilter implements ClientRequestFilter, ClientRespons Response.Status.Family.CLIENT_ERROR, Response.Status.Family.SERVER_ERROR); + private static final LazyValue> HELPERS = LazyValue.create( + HelidonTelemetryClientFilter::helpers); + + private static final String HELPER_START_SPAN_PROPERTY = HelidonTelemetryClientFilterHelper.class.getName() + ".startSpan"; + private final io.helidon.tracing.Tracer helidonTracer; @Inject @@ -66,6 +75,16 @@ class HelidonTelemetryClientFilter implements ClientRequestFilter, ClientRespons @Override public void filter(ClientRequestContext clientRequestContext) { + boolean startSpan = HELPERS.get().stream().allMatch(h -> h.shouldStartSpan(clientRequestContext)); + clientRequestContext.setProperty(HELPER_START_SPAN_PROPERTY, startSpan); + if (!startSpan) { + if (LOGGER.isLoggable(System.Logger.Level.TRACE)) { + LOGGER.log(System.Logger.Level.TRACE, + "Client filter helper(s) voted to not start a span for " + clientRequestContext.getUri()); + } + return; + } + if (LOGGER.isLoggable(System.Logger.Level.TRACE)) { LOGGER.log(System.Logger.Level.TRACE, "Starting Span in a Client Request"); } @@ -103,6 +122,11 @@ public void filter(ClientRequestContext clientRequestContext) { @Override public void filter(ClientRequestContext clientRequestContext, ClientResponseContext clientResponseContext) { + Boolean startSpanObj = (Boolean) clientRequestContext.getProperty(HELPER_START_SPAN_PROPERTY); + if (startSpanObj != null && !startSpanObj) { + return; + } + if (LOGGER.isLoggable(System.Logger.Level.TRACE)) { LOGGER.log(System.Logger.Level.TRACE, "Closing Span in a Client Response"); } @@ -128,6 +152,10 @@ public void filter(ClientRequestContext clientRequestContext, ClientResponseCont clientRequestContext.removeProperty(SPAN_SCOPE); } + private static List helpers() { + return HelidonServiceLoader.create(ServiceLoader.load(HelidonTelemetryClientFilterHelper.class)).asList(); + } + private static class RequestContextHeaderInjector implements HeaderConsumer { private final MultivaluedMap requestHeaders; diff --git a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryContainerFilter.java b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryContainerFilter.java index f361d54b84e..17001e21c70 100644 --- a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryContainerFilter.java +++ b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryContainerFilter.java @@ -19,10 +19,14 @@ import java.util.LinkedList; import java.util.List; import java.util.Optional; +import java.util.ServiceLoader; import java.util.concurrent.atomic.AtomicBoolean; +import io.helidon.common.HelidonServiceLoader; +import io.helidon.common.LazyValue; import io.helidon.common.context.Contexts; import io.helidon.config.mp.MpConfig; +import io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper; import io.helidon.tracing.Scope; import io.helidon.tracing.Span; import io.helidon.tracing.SpanContext; @@ -63,6 +67,11 @@ class HelidonTelemetryContainerFilter implements ContainerRequestFilter, Contain private static final String SPAN_NAME_FULL_URL = "telemetry.span.full.url"; + private static final String HELPER_START_SPAN_PROPERTY = HelidonTelemetryContainerFilterHelper.class + ".startSpan"; + + private static final LazyValue> HELPERS = LazyValue.create( + HelidonTelemetryContainerFilter::helpers); + @Deprecated(forRemoval = true, since = "4.1") static final String SPAN_NAME_INCLUDES_METHOD = "telemetry.span.name-includes-method"; @@ -115,6 +124,16 @@ public void filter(ContainerRequestContext requestContext) { return; } + boolean startSpan = HELPERS.get().stream().allMatch(h -> h.shouldStartSpan(requestContext)); + requestContext.setProperty(HELPER_START_SPAN_PROPERTY, startSpan); + if (!startSpan) { + if (LOGGER.isLoggable(System.Logger.Level.TRACE)) { + LOGGER.log(System.Logger.Level.TRACE, + "Container filter helper(s) voted to not start a span for " + requestContext); + } + return; + } + if (LOGGER.isLoggable(System.Logger.Level.TRACE)) { LOGGER.log(System.Logger.Level.TRACE, "Starting Span in a Container Request"); } @@ -151,6 +170,11 @@ public void filter(final ContainerRequestContext request, final ContainerRespons return; } + Boolean startSpanObj = (Boolean) request.getProperty(HELPER_START_SPAN_PROPERTY); + if (startSpanObj != null && !startSpanObj) { + return; + } + if (LOGGER.isLoggable(System.Logger.Level.TRACE)) { LOGGER.log(System.Logger.Level.TRACE, "Closing Span in a Container Request"); } @@ -179,6 +203,10 @@ public void filter(final ContainerRequestContext request, final ContainerRespons } } + private static List helpers() { + return HelidonServiceLoader.create(ServiceLoader.load(HelidonTelemetryContainerFilterHelper.class)).asList(); + } + private String spanName(ContainerRequestContext requestContext, String route) { // @Deprecated(forRemoval = true) In 5.x remove the option of excluding the HTTP method from the REST span name. // Starting in 5.x this method should be: diff --git a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/HelidonTelemetryClientFilterHelper.java b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/HelidonTelemetryClientFilterHelper.java new file mode 100644 index 00000000000..cebcea0ef07 --- /dev/null +++ b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/HelidonTelemetryClientFilterHelper.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.microprofile.telemetry.spi; + +import jakarta.ws.rs.client.ClientRequestContext; + +/** + * Service-loaded type applied while the Helidon-provided client filter executes. + */ +public interface HelidonTelemetryClientFilterHelper { + + /** + * Invoked to see if this helper votes to create and start a new span for the outgoing client request reflected + * in the provided client request context. + * + * @param clientRequestContext the {@link jakarta.ws.rs.client.ClientRequestContext} passed to the filter + * @return true to vote to start a span; false to vote not to start a span + */ + boolean shouldStartSpan(ClientRequestContext clientRequestContext); +} diff --git a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/HelidonTelemetryContainerFilterHelper.java b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/HelidonTelemetryContainerFilterHelper.java new file mode 100644 index 00000000000..449a5ac9372 --- /dev/null +++ b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/HelidonTelemetryContainerFilterHelper.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.microprofile.telemetry.spi; + +import jakarta.ws.rs.container.ContainerRequestContext; + +/** + * Service-loaded type applied while the Helidon-provided container filter executes. + */ +public interface HelidonTelemetryContainerFilterHelper { + + /** + * Invoked to see if this helper votes to create and start a new span for the incoming + * request reflected in the provided container request context. + * + * @param containerRequestContext the {@link jakarta.ws.rs.container.ContainerRequestContext} passed to the filter + * @return true to vote to start a span; false to vote not to start a span + */ + boolean shouldStartSpan(ContainerRequestContext containerRequestContext); +} diff --git a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/package-info.java b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/package-info.java new file mode 100644 index 00000000000..a4e243a17c4 --- /dev/null +++ b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/spi/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * SPI interfaces open to developer implementation as needed. + */ +package io.helidon.microprofile.telemetry.spi; diff --git a/microprofile/telemetry/src/main/java/module-info.java b/microprofile/telemetry/src/main/java/module-info.java index f56c961d88a..031f52f9033 100644 --- a/microprofile/telemetry/src/main/java/module-info.java +++ b/microprofile/telemetry/src/main/java/module-info.java @@ -17,8 +17,6 @@ import io.helidon.common.features.api.Feature; import io.helidon.common.features.api.HelidonFlavor; import io.helidon.common.features.api.Preview; -import io.helidon.microprofile.telemetry.TelemetryAutoDiscoverable; -import io.helidon.microprofile.telemetry.TelemetryCdiExtension; /** * MicroProfile Telemetry support for Helidon. @@ -57,11 +55,15 @@ requires transitive jersey.common; exports io.helidon.microprofile.telemetry; + exports io.helidon.microprofile.telemetry.spi; + + uses io.helidon.microprofile.telemetry.spi.HelidonTelemetryClientFilterHelper; + uses io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper; provides jakarta.enterprise.inject.spi.Extension - with TelemetryCdiExtension; + with io.helidon.microprofile.telemetry.TelemetryCdiExtension; provides org.glassfish.jersey.internal.spi.AutoDiscoverable - with TelemetryAutoDiscoverable; + with io.helidon.microprofile.telemetry.TelemetryAutoDiscoverable; } \ No newline at end of file diff --git a/tests/integration/mp-telemetry/pom.xml b/tests/integration/mp-telemetry/pom.xml new file mode 100644 index 00000000000..aee91fbc023 --- /dev/null +++ b/tests/integration/mp-telemetry/pom.xml @@ -0,0 +1,110 @@ + + + + + 4.0.0 + + io.helidon.applications + helidon-mp + 4.2.0-SNAPSHOT + ../../../applications/mp/pom.xml + + io.helidon.tests.integration + helidon-tests-integration-mp-telemetry + Helidon Tests Integration MP Telemetry + + + + 100 + + + + + io.helidon.microprofile.bundles + helidon-microprofile-core + + + io.helidon.microprofile.telemetry + helidon-microprofile-telemetry + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + io.helidon.microprofile.testing + helidon-microprofile-testing-junit5 + test + + + io.helidon.common.testing + helidon-common-testing-junit5 + test + + + io.opentelemetry + opentelemetry-exporter-otlp + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + io.smallrye + jandex-maven-plugin + + + make-index + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + default-test + + + ${otel.bsp.schedule.delay} + + + + + + + + + diff --git a/tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/FilterSelectorSuppressPersonalizedGreetingSpan.java b/tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/FilterSelectorSuppressPersonalizedGreetingSpan.java new file mode 100644 index 00000000000..e53704a785d --- /dev/null +++ b/tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/FilterSelectorSuppressPersonalizedGreetingSpan.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.tests.integration.telemetry.mp.filterselectivity; + +import io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper; + +import jakarta.ws.rs.container.ContainerRequestContext; + +public class FilterSelectorSuppressPersonalizedGreetingSpan implements HelidonTelemetryContainerFilterHelper { + + @Override + public boolean shouldStartSpan(ContainerRequestContext containerRequestContext) { + // Suppress spans for the personalized greeting endpoint. + return containerRequestContext.getUriInfo().getPath().endsWith("greet"); + } +} diff --git a/tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/GreetResource.java b/tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/GreetResource.java new file mode 100644 index 00000000000..356f871957d --- /dev/null +++ b/tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/GreetResource.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.tests.integration.telemetry.mp.filterselectivity; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/greet") +public class GreetResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String getDefaultMessage() { + return createResponse("World"); + } + + @Path("/{name}") + @GET + @Produces(MediaType.TEXT_PLAIN) + public String getMessage(@PathParam("name") String name) { + return createResponse(name); + } + + private static String createResponse(String who) { + return "Hello " + who + "!"; + } +} diff --git a/tests/integration/mp-telemetry/src/main/resources/META-INF/beans.xml b/tests/integration/mp-telemetry/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..4ba37321310 --- /dev/null +++ b/tests/integration/mp-telemetry/src/main/resources/META-INF/beans.xml @@ -0,0 +1,25 @@ + + + + diff --git a/tests/integration/mp-telemetry/src/main/resources/META-INF/microprofile-config.properties b/tests/integration/mp-telemetry/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..b2e858082ce --- /dev/null +++ b/tests/integration/mp-telemetry/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1,20 @@ +# +# Copyright (c) 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Microprofile server properties +server.port=8080 +server.host=0.0.0.0 + diff --git a/tests/integration/mp-telemetry/src/main/resources/META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper b/tests/integration/mp-telemetry/src/main/resources/META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper new file mode 100644 index 00000000000..233ff0ee9eb --- /dev/null +++ b/tests/integration/mp-telemetry/src/main/resources/META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper @@ -0,0 +1 @@ +io.helidon.tests.integration.telemetry.mp.filterselectivity.FilterSelectorSuppressPersonalizedGreetingSpan \ No newline at end of file diff --git a/tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanExporter.java b/tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanExporter.java new file mode 100644 index 00000000000..764ca12b8b8 --- /dev/null +++ b/tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanExporter.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.tests.integration.telemetry.mp.filterselectivity; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import io.helidon.common.testing.junit5.MatcherWithRetry; + +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import jakarta.enterprise.context.ApplicationScoped; + +import static org.hamcrest.Matchers.iterableWithSize; + +// Partially inspired by the MP Telemetry TCK InMemorySpanExporter. +@ApplicationScoped +public class TestSpanExporter implements SpanExporter { + + private final List spanData = new CopyOnWriteArrayList<>(); + private final System.Logger LOGGER = System.getLogger(TestSpanExporter.class.getName()); + + private final int RETRY_COUNT = Integer.getInteger(TestSpanExporter.class.getName() + ".test.retryCount", 120); + private final int RETRY_DELAY_MS = Integer.getInteger(TestSpanExporter.class.getName() + ".test.retryDelayMs", 500); + + + private enum State {READY, STOPPED} + + private State state = State.READY; + + @Override + public CompletableResultCode export(Collection collection) { + if (state == State.STOPPED) { + return CompletableResultCode.ofFailure(); + } + spanData.addAll(collection); + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + state = State.STOPPED; + spanData.clear(); + return CompletableResultCode.ofSuccess(); + } + + List spanData(int expectedCount) { + long startTime = 0; + if (LOGGER.isLoggable(System.Logger.Level.DEBUG)) { + startTime = System.currentTimeMillis(); + } + var result = MatcherWithRetry.assertThatWithRetry("Expected span count", + () -> new ArrayList<>(spanData), + iterableWithSize(expectedCount), + RETRY_COUNT, + RETRY_DELAY_MS); + if (LOGGER.isLoggable(System.Logger.Level.DEBUG)) { + LOGGER.log(System.Logger.Level.DEBUG, "spanData waited " + + (System.currentTimeMillis() - startTime) + + " ms for expected spans to arrive."); + } + return result; + } + + List spanData(Duration delay) throws InterruptedException { + Thread.sleep(delay); + return new ArrayList<>(spanData); + } + + void clear() { + spanData.clear(); + } +} diff --git a/tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanExporterProvider.java b/tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanExporterProvider.java new file mode 100644 index 00000000000..2e8f21d0155 --- /dev/null +++ b/tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanExporterProvider.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.tests.integration.telemetry.mp.filterselectivity; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import jakarta.enterprise.inject.spi.CDI; + +public class TestSpanExporterProvider implements ConfigurableSpanExporterProvider { + + public TestSpanExporterProvider() { + System.err.println("provider ctor"); + } + + @Override + public SpanExporter createExporter(ConfigProperties configProperties) { + return CDI.current().select(TestSpanExporter.class).get(); + } + + @Override + public String getName() { + return "in-memory"; + } +} diff --git a/tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanSelectivity.java b/tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanSelectivity.java new file mode 100644 index 00000000000..2d775842ddf --- /dev/null +++ b/tests/integration/mp-telemetry/src/test/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/TestSpanSelectivity.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.tests.integration.telemetry.mp.filterselectivity; + +import java.time.Duration; +import java.util.List; + +import io.helidon.microprofile.testing.junit5.AddBean; +import io.helidon.microprofile.testing.junit5.AddConfig; +import io.helidon.microprofile.testing.junit5.AddConfigBlock; +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import io.opentelemetry.sdk.trace.data.SpanData; +import jakarta.inject.Inject; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; + +@HelidonTest +@AddBean(TestSpanExporter.class) +@AddConfig(key = "otel.sdk.disabled", value = "false") +@AddConfig(key = "otel.traces.exporter", value = "in-memory") + +class TestSpanSelectivity { + + @Inject + private WebTarget webTarget; + + @Inject + private TestSpanExporter testSpanExporter; + + @BeforeEach + void clearSpanData() { + testSpanExporter.clear(); + } + + @Test + void checkSpansForDefaultGreeting() { + Response response = webTarget.path("/greet").request(MediaType.TEXT_PLAIN).get(); + assertThat("Request status", response.getStatus(), is(200)); + + List spanData = testSpanExporter.spanData(2); // Automatic GET span plus the resource span + assertThat("Span data", spanData, hasSize(2)); + } + + @Test + void checkSpansForPersonalizedGreeting() throws InterruptedException { + Response response = webTarget.path("/greet/Joe").request(MediaType.TEXT_PLAIN).get(); + assertThat("Request status", response.getStatus(), is(200)); + + List spanData = testSpanExporter.spanData(Duration.ofSeconds(2)); + assertThat("Span data", spanData, hasSize(1)); + } +} diff --git a/tests/integration/mp-telemetry/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider b/tests/integration/mp-telemetry/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider new file mode 100644 index 00000000000..03282f6e36d --- /dev/null +++ b/tests/integration/mp-telemetry/src/test/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider @@ -0,0 +1,17 @@ +# +# Copyright (c) 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +io.helidon.tests.integration.telemetry.mp.filterselectivity.TestSpanExporterProvider diff --git a/tests/integration/pom.xml b/tests/integration/pom.xml index c342cceedb3..f796ecb171f 100644 --- a/tests/integration/pom.xml +++ b/tests/integration/pom.xml @@ -67,6 +67,7 @@ zipkin-mp-2.2 tls-revocation-config h2spec + mp-telemetry From edec17b4f322c4cf2533798c1844969cc808b6fc Mon Sep 17 00:00:00 2001 From: Tim Quinn Date: Wed, 4 Dec 2024 11:08:59 -0600 Subject: [PATCH 2/3] Add doc describing filter helpers; also fix some Javadoc warnings in related files --- .../main/asciidoc/includes/attributes.adoc | 1 + docs/src/main/asciidoc/mp/telemetry.adoc | 43 +++++++++++++++++++ .../io/helidon/docs/mp/TelemetrySnippets.java | 34 +++++++++++++++ .../restclient/RestclientMetricsSnippets.java | 4 +- .../mp/restclient/RestclientSnippets.java | 2 +- .../telemetry/TelemetryAutoDiscoverable.java | 6 +++ .../telemetry/TelemetryCdiExtension.java | 6 +++ 7 files changed, 93 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/includes/attributes.adoc b/docs/src/main/asciidoc/includes/attributes.adoc index 9cf1261c1b7..1511b3e59e4 100644 --- a/docs/src/main/asciidoc/includes/attributes.adoc +++ b/docs/src/main/asciidoc/includes/attributes.adoc @@ -225,6 +225,7 @@ endif::[] :scheduling-javadoc-base-url: {javadoc-base-url}/io.helidon.microprofile.scheduling :security-integration-jersey-base-url: {javadoc-base-url}/io.helidon.security.integration.jersey :security-integration-webserver-base-url: {javadoc-base-url}/io.helidon.webserver.security +:telemetry-javadoc-base-url: {javadoc-base-url}/io.helidon.microprofile.telemetry :tracing-javadoc-base-url: {javadoc-base-url}/io.helidon.tracing :webclient-javadoc-base-url: {javadoc-base-url}/io.helidon.webclient diff --git a/docs/src/main/asciidoc/mp/telemetry.adoc b/docs/src/main/asciidoc/mp/telemetry.adoc index 7251e88c06e..42ddd7a339f 100644 --- a/docs/src/main/asciidoc/mp/telemetry.adoc +++ b/docs/src/main/asciidoc/mp/telemetry.adoc @@ -204,6 +204,49 @@ include::{sourcedir}/mp/TelemetrySnippets.java[tag=snippet_6, indent=0] include::{rootdir}/includes/tracing/common-callbacks.adoc[tags=defs;detailed,leveloffset=+1] +=== Controlling Automatic Span Creation +By default, Helidon MP Telemetry creates a new child span for each incoming REST request and for each outgoing REST client request. You can selectively control if Helidon creates these automatic spans on a request-by-request basis by adding a very small amount of code to your project. + +==== Controlling Automatic Spans for Incoming REST Requests +To selectively suppress child span creation for incoming REST requests implement the link:{telemetry-javadoc-base-url}/io/helidon/microprofile/telemetry/spi/HelidonTelemetryContainerFilterHelper.html[HelidonTelemetryContainerFilterHelper interface]. + +When Helidon receives an incoming REST request it invokes the `shouldStartSpan` method on each such implementation, passing the link:{jakarta-jaxrs-javadoc-url}/jakarta.ws.rs/jakarta/ws/rs/container/containerrequestcontext[Jakarta REST container request context] for the request. If at least one implementation returns `false` then Helidon suppresses the automatic child span. If all implementations return `true` then Helidon creates the automatic child span. + +The following example shows how to allow automatic spans in the Helidon greet example app for requests for the default greeting but not for the personalized greeting or the `PUT` request to change the greeting message (because the update path ends with `greeting` not `greet`). + +.Example container helper for the Helidon MP Greeting app +[source,java] +---- +include::{sourcedir}/mp/TelemetrySnippets.java[tag=snippet_11, indent=0] +---- + +Also add a Java services file so Helidon will discover your helper. + +.`META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerHelper` +[source,properties] +---- +io.helidon.example.telemetry.CustomRestRequestFilterHelper +---- +==== Controlling Automatic Spans for Outgoing REST Client Requests +To selectively suppress child span creation for outgoing REST client requests implement the link:{telemetry-javadoc-base-url}/io/helidon/microprofile/telemetry/spi/HelidonTelemetryClientFilterHelper.html[HelidonTelemetryClientFilterHelper interface]. + +When your application sends an outgoing REST client request Helidon invokes the `shouldStartSpan` method on each such implementation, passing the link:{jakarta-jaxrs-javadoc-url}/jakarta.ws.rs/jakarta/ws/rs/client/clientrequestcontext[Jakarta REST client request context] for the request. If at least one implementation returns `false` then Helidon suppresses the automatic child span. If all implementations return `true` then Helidon creates the automatic child span. + +The following example shows how to allow automatic spans in an app that invokes the Helidon greet example app. The example permits automatic child spans for outgoing requests for the default greeting but not for the personalized greeting or the `PUT` request to change the greeting message (because the update path ends with `greeting` not `greet`). + +.Example Client Helper for the Helidon MP Greeting App +[source,java] +---- +include::{sourcedir}/mp/TelemetrySnippets.java[tag=snippet_12, indent=0] +---- + +Also add a Java services file so Helidon will discover your helper. + +.`META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryClientFilterHelper` +[source,properties] +---- +io.helidon.example.telemetry.CustomRestClientRequestFilterHelper +---- == Configuration IMPORTANT: MicroProfile Telemetry is not activated by default. To activate this feature, you need to specify the configuration `otel.sdk.disabled=false` in one of the MicroProfile Config or other config sources. diff --git a/docs/src/main/java/io/helidon/docs/mp/TelemetrySnippets.java b/docs/src/main/java/io/helidon/docs/mp/TelemetrySnippets.java index f7bb37a4472..9db3fc479c9 100644 --- a/docs/src/main/java/io/helidon/docs/mp/TelemetrySnippets.java +++ b/docs/src/main/java/io/helidon/docs/mp/TelemetrySnippets.java @@ -15,6 +15,9 @@ */ package io.helidon.docs.mp; +import io.helidon.microprofile.telemetry.spi.HelidonTelemetryClientFilterHelper; +import io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper; + import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanKind; @@ -28,7 +31,9 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.glassfish.jersey.server.Uri; @@ -225,4 +230,33 @@ public String getSecondaryMessage() { // end::snippet_10[] } + class FilterHelperSnippets_11_to_12 { + + // tag::snippet_11[] + public class CustomRestRequestFilterHelper implements HelidonTelemetryContainerFilterHelper { + + @Override + public boolean shouldStartSpan(ContainerRequestContext containerRequestContext) { + + // Allows automatic spans for incoming requests for the default greeting but not for + // personalized greetings or the PUT request to update the greeting message. + return containerRequestContext.getUriInfo().getPath().endsWith("greet"); + } + } + // end::snippet_11[] + + // tag::snippet_12[] + public class CustomRestClientRequestFilterHelper implements HelidonTelemetryClientFilterHelper { + + @Override + public boolean shouldStartSpan(ClientRequestContext clientRequestContext) { + + // Allows automatic spans for outgoing requests for the default greeting but not for + // personalized greetings or the PUT request to update the greeting message. + return clientRequestContext.getUri().getPath().endsWith("greet"); + } + } + // end::snippet_12[] + } + } diff --git a/docs/src/main/java/io/helidon/docs/mp/restclient/RestclientMetricsSnippets.java b/docs/src/main/java/io/helidon/docs/mp/restclient/RestclientMetricsSnippets.java index 46574e1322d..770516c11ed 100644 --- a/docs/src/main/java/io/helidon/docs/mp/restclient/RestclientMetricsSnippets.java +++ b/docs/src/main/java/io/helidon/docs/mp/restclient/RestclientMetricsSnippets.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.docs.mp; +package io.helidon.docs.mp.restclient; import java.net.URI; @@ -33,7 +33,7 @@ import org.eclipse.microprofile.metrics.annotation.Timed; import org.eclipse.microprofile.rest.client.RestClientBuilder; -import static io.helidon.docs.mp.RestclientMetricsSnippets.Snippet1.GreetRestClient; +import static io.helidon.docs.mp.restclient.RestclientMetricsSnippets.Snippet1.GreetRestClient; @SuppressWarnings("ALL") class RestclientMetricsSnippets { diff --git a/docs/src/main/java/io/helidon/docs/mp/restclient/RestclientSnippets.java b/docs/src/main/java/io/helidon/docs/mp/restclient/RestclientSnippets.java index 28522d2ab67..856cc5a5dbf 100644 --- a/docs/src/main/java/io/helidon/docs/mp/restclient/RestclientSnippets.java +++ b/docs/src/main/java/io/helidon/docs/mp/restclient/RestclientSnippets.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.docs.mp; +package io.helidon.docs.mp.restclient; import java.io.IOException; import java.net.URI; diff --git a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/TelemetryAutoDiscoverable.java b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/TelemetryAutoDiscoverable.java index 300df0e0109..129e4404dad 100644 --- a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/TelemetryAutoDiscoverable.java +++ b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/TelemetryAutoDiscoverable.java @@ -22,6 +22,12 @@ */ public class TelemetryAutoDiscoverable implements AutoDiscoverable { + /** + * For service loading. + */ + public TelemetryAutoDiscoverable() { + } + /** * Used to register {@code HelidonTelemetryContainerFilter} and {@code HelidonTelemetryClientFilter} * filters. diff --git a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/TelemetryCdiExtension.java b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/TelemetryCdiExtension.java index a4767bd01cd..2e587f08ce5 100644 --- a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/TelemetryCdiExtension.java +++ b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/TelemetryCdiExtension.java @@ -36,6 +36,12 @@ public class TelemetryCdiExtension implements Extension { private static final System.Logger LOGGER = System.getLogger(TelemetryCdiExtension.class.getName()); + /** + * For service loading. + */ + public TelemetryCdiExtension() { + } + /** * Add {@code HelidonWithSpan} annotation with interceptor. * From 978b3369d5d1728417c223d439f18a3b91e69449 Mon Sep 17 00:00:00 2001 From: Tim Quinn Date: Thu, 5 Dec 2024 07:17:02 -0600 Subject: [PATCH 3/3] Use CDI instead of Java service loading to locate helpers --- docs/src/main/asciidoc/mp/telemetry.adoc | 17 ++++---------- .../io/helidon/docs/mp/TelemetrySnippets.java | 2 ++ .../HelidonTelemetryClientFilter.java | 15 ++++++------- .../HelidonTelemetryContainerFilter.java | 22 +++++++++---------- ...ectorSuppressPersonalizedGreetingSpan.java | 2 ++ ....spi.HelidonTelemetryContainerFilterHelper | 1 - 6 files changed, 26 insertions(+), 33 deletions(-) delete mode 100644 tests/integration/mp-telemetry/src/main/resources/META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper diff --git a/docs/src/main/asciidoc/mp/telemetry.adoc b/docs/src/main/asciidoc/mp/telemetry.adoc index 42ddd7a339f..02981c59d69 100644 --- a/docs/src/main/asciidoc/mp/telemetry.adoc +++ b/docs/src/main/asciidoc/mp/telemetry.adoc @@ -214,19 +214,15 @@ When Helidon receives an incoming REST request it invokes the `shouldStartSpan` The following example shows how to allow automatic spans in the Helidon greet example app for requests for the default greeting but not for the personalized greeting or the `PUT` request to change the greeting message (because the update path ends with `greeting` not `greet`). +Your implementation of `HelidonTelemetryContainerFilterHelper` must have a CDI bean-defining annotation. The example shows `@ApplicationScoped`. + .Example container helper for the Helidon MP Greeting app [source,java] ---- include::{sourcedir}/mp/TelemetrySnippets.java[tag=snippet_11, indent=0] ---- -Also add a Java services file so Helidon will discover your helper. -.`META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerHelper` -[source,properties] ----- -io.helidon.example.telemetry.CustomRestRequestFilterHelper ----- ==== Controlling Automatic Spans for Outgoing REST Client Requests To selectively suppress child span creation for outgoing REST client requests implement the link:{telemetry-javadoc-base-url}/io/helidon/microprofile/telemetry/spi/HelidonTelemetryClientFilterHelper.html[HelidonTelemetryClientFilterHelper interface]. @@ -234,19 +230,14 @@ When your application sends an outgoing REST client request Helidon invokes the The following example shows how to allow automatic spans in an app that invokes the Helidon greet example app. The example permits automatic child spans for outgoing requests for the default greeting but not for the personalized greeting or the `PUT` request to change the greeting message (because the update path ends with `greeting` not `greet`). +Your implementation of `HelidonTelemetryClientFilterHelper` must have a CDI bean-defining annotation. The example shows `@ApplicationScoped`. + .Example Client Helper for the Helidon MP Greeting App [source,java] ---- include::{sourcedir}/mp/TelemetrySnippets.java[tag=snippet_12, indent=0] ---- -Also add a Java services file so Helidon will discover your helper. - -.`META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryClientFilterHelper` -[source,properties] ----- -io.helidon.example.telemetry.CustomRestClientRequestFilterHelper ----- == Configuration IMPORTANT: MicroProfile Telemetry is not activated by default. To activate this feature, you need to specify the configuration `otel.sdk.disabled=false` in one of the MicroProfile Config or other config sources. diff --git a/docs/src/main/java/io/helidon/docs/mp/TelemetrySnippets.java b/docs/src/main/java/io/helidon/docs/mp/TelemetrySnippets.java index 9db3fc479c9..3de3c94c0e0 100644 --- a/docs/src/main/java/io/helidon/docs/mp/TelemetrySnippets.java +++ b/docs/src/main/java/io/helidon/docs/mp/TelemetrySnippets.java @@ -233,6 +233,7 @@ public String getSecondaryMessage() { class FilterHelperSnippets_11_to_12 { // tag::snippet_11[] + @ApplicationScoped public class CustomRestRequestFilterHelper implements HelidonTelemetryContainerFilterHelper { @Override @@ -246,6 +247,7 @@ public boolean shouldStartSpan(ContainerRequestContext containerRequestContext) // end::snippet_11[] // tag::snippet_12[] + @ApplicationScoped public class CustomRestClientRequestFilterHelper implements HelidonTelemetryClientFilterHelper { @Override diff --git a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryClientFilter.java b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryClientFilter.java index 059ef4abbd3..96f77a2ee65 100644 --- a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryClientFilter.java +++ b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryClientFilter.java @@ -21,7 +21,6 @@ import java.util.Set; import io.helidon.common.HelidonServiceLoader; -import io.helidon.common.LazyValue; import io.helidon.microprofile.telemetry.spi.HelidonTelemetryClientFilterHelper; import io.helidon.tracing.HeaderConsumer; import io.helidon.tracing.HeaderProvider; @@ -30,6 +29,7 @@ import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.context.Context; +import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestFilter; @@ -59,23 +59,23 @@ class HelidonTelemetryClientFilter implements ClientRequestFilter, ClientRespons Response.Status.Family.CLIENT_ERROR, Response.Status.Family.SERVER_ERROR); - private static final LazyValue> HELPERS = LazyValue.create( - HelidonTelemetryClientFilter::helpers); - private static final String HELPER_START_SPAN_PROPERTY = HelidonTelemetryClientFilterHelper.class.getName() + ".startSpan"; private final io.helidon.tracing.Tracer helidonTracer; + private final List helpers; + @Inject - HelidonTelemetryClientFilter(io.helidon.tracing.Tracer helidonTracer) { + HelidonTelemetryClientFilter(io.helidon.tracing.Tracer helidonTracer, + Instance helpersInstance) { this.helidonTracer = helidonTracer; + helpers = helpersInstance.stream().toList(); } - @Override public void filter(ClientRequestContext clientRequestContext) { - boolean startSpan = HELPERS.get().stream().allMatch(h -> h.shouldStartSpan(clientRequestContext)); + boolean startSpan = helpers.stream().allMatch(h -> h.shouldStartSpan(clientRequestContext)); clientRequestContext.setProperty(HELPER_START_SPAN_PROPERTY, startSpan); if (!startSpan) { if (LOGGER.isLoggable(System.Logger.Level.TRACE)) { @@ -118,7 +118,6 @@ public void filter(ClientRequestContext clientRequestContext) { new RequestContextHeaderInjector(clientRequestContext.getHeaders())); } - @Override public void filter(ClientRequestContext clientRequestContext, ClientResponseContext clientResponseContext) { diff --git a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryContainerFilter.java b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryContainerFilter.java index 17001e21c70..6548f912211 100644 --- a/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryContainerFilter.java +++ b/microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryContainerFilter.java @@ -19,11 +19,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Optional; -import java.util.ServiceLoader; import java.util.concurrent.atomic.AtomicBoolean; -import io.helidon.common.HelidonServiceLoader; -import io.helidon.common.LazyValue; import io.helidon.common.context.Contexts; import io.helidon.config.mp.MpConfig; import io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper; @@ -36,6 +33,7 @@ import io.opentelemetry.api.baggage.BaggageEntryMetadata; import io.opentelemetry.context.Context; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.container.ContainerRequestContext; @@ -69,9 +67,6 @@ class HelidonTelemetryContainerFilter implements ContainerRequestFilter, Contain private static final String HELPER_START_SPAN_PROPERTY = HelidonTelemetryContainerFilterHelper.class + ".startSpan"; - private static final LazyValue> HELPERS = LazyValue.create( - HelidonTelemetryContainerFilter::helpers); - @Deprecated(forRemoval = true, since = "4.1") static final String SPAN_NAME_INCLUDES_METHOD = "telemetry.span.name-includes-method"; @@ -90,12 +85,15 @@ class HelidonTelemetryContainerFilter implements ContainerRequestFilter, Contain */ private final boolean restSpanNameIncludesMethod; + private final List helpers; + @jakarta.ws.rs.core.Context private ResourceInfo resourceInfo; @Inject HelidonTelemetryContainerFilter(io.helidon.tracing.Tracer helidonTracer, - org.eclipse.microprofile.config.Config mpConfig) { + org.eclipse.microprofile.config.Config mpConfig, + Instance helpersInstance) { this.helidonTracer = helidonTracer; isAgentPresent = HelidonOpenTelemetry.AgentDetector.isAgentPresent(MpConfig.toHelidonConfig(mpConfig)); @@ -115,6 +113,8 @@ class HelidonTelemetryContainerFilter implements ContainerRequestFilter, Contain SPAN_NAME_INCLUDES_METHOD)); } // end of code to remove in 5.x. + + helpers = helpersInstance.stream().toList(); } @Override @@ -124,7 +124,7 @@ public void filter(ContainerRequestContext requestContext) { return; } - boolean startSpan = HELPERS.get().stream().allMatch(h -> h.shouldStartSpan(requestContext)); + boolean startSpan = helpers.stream().allMatch(h -> h.shouldStartSpan(requestContext)); requestContext.setProperty(HELPER_START_SPAN_PROPERTY, startSpan); if (!startSpan) { if (LOGGER.isLoggable(System.Logger.Level.TRACE)) { @@ -203,9 +203,9 @@ public void filter(final ContainerRequestContext request, final ContainerRespons } } - private static List helpers() { - return HelidonServiceLoader.create(ServiceLoader.load(HelidonTelemetryContainerFilterHelper.class)).asList(); - } +// private static List helpers() { +// return HelidonServiceLoader.create(ServiceLoader.load(HelidonTelemetryContainerFilterHelper.class)).asList(); +// } private String spanName(ContainerRequestContext requestContext, String route) { // @Deprecated(forRemoval = true) In 5.x remove the option of excluding the HTTP method from the REST span name. diff --git a/tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/FilterSelectorSuppressPersonalizedGreetingSpan.java b/tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/FilterSelectorSuppressPersonalizedGreetingSpan.java index e53704a785d..7ac86be00f6 100644 --- a/tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/FilterSelectorSuppressPersonalizedGreetingSpan.java +++ b/tests/integration/mp-telemetry/src/main/java/io/helidon/tests/integration/telemetry/mp/filterselectivity/FilterSelectorSuppressPersonalizedGreetingSpan.java @@ -17,8 +17,10 @@ import io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.ws.rs.container.ContainerRequestContext; +@ApplicationScoped public class FilterSelectorSuppressPersonalizedGreetingSpan implements HelidonTelemetryContainerFilterHelper { @Override diff --git a/tests/integration/mp-telemetry/src/main/resources/META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper b/tests/integration/mp-telemetry/src/main/resources/META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper deleted file mode 100644 index 233ff0ee9eb..00000000000 --- a/tests/integration/mp-telemetry/src/main/resources/META-INF/services/io.helidon.microprofile.telemetry.spi.HelidonTelemetryContainerFilterHelper +++ /dev/null @@ -1 +0,0 @@ -io.helidon.tests.integration.telemetry.mp.filterselectivity.FilterSelectorSuppressPersonalizedGreetingSpan \ No newline at end of file