diff --git a/tracing/providers/jaeger/pom.xml b/tracing/providers/jaeger/pom.xml index 116887b6fd5..d02677b8a7e 100644 --- a/tracing/providers/jaeger/pom.xml +++ b/tracing/providers/jaeger/pom.xml @@ -154,12 +154,12 @@ org.apache.maven.plugins maven-surefire-plugin - + default-test - **/JaegerBaggagePropagationTest.java + **/JaegerBaggagePropagationTest.java,**/JaegerTracerBuilderTest.java @@ -176,6 +176,20 @@ **/JaegerBaggagePropagationTest.java + + tracer-builder-tests + + test + + + + + junit.jupiter.extensions.autodetection.enabled = true + + + **/JaegerTracerBuilderTest.java + + diff --git a/tracing/providers/jaeger/src/main/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilder.java b/tracing/providers/jaeger/src/main/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilder.java index 4f13506fd13..5bf59f6672f 100644 --- a/tracing/providers/jaeger/src/main/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilder.java +++ b/tracing/providers/jaeger/src/main/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilder.java @@ -18,6 +18,7 @@ import java.lang.System.Logger.Level; import java.time.Duration; +import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; @@ -37,6 +38,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter; @@ -46,6 +48,7 @@ import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; @@ -179,6 +182,7 @@ public class JaegerTracerBuilder implements TracerBuilder { private int maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; private int maxExportBatchSize = DEFAULT_MAX_EXPORT_BATCH_SIZE; private SpanProcessorType spanProcessorType = SpanProcessorType.BATCH; + private final List adHocExporters = new ArrayList<>(); // primarily for testing /** @@ -512,17 +516,25 @@ public Tracer build() { : Sampler.alwaysOff(); }; - Resource serviceName = Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, this.serviceName)); + AttributesBuilder attributesBuilder = Attributes.builder() + .put(ResourceAttributes.SERVICE_NAME, serviceName); + tags.forEach(attributesBuilder::put); + Resource otelResource = Resource.create(attributesBuilder.build()); + + SdkTracerProviderBuilder sdkTracerProviderBuilder = SdkTracerProvider.builder() + .setSampler(sampler) + .setResource(otelResource) + .addSpanProcessor(spanProcessor(exporter)); + adHocExporters.stream() + .map(this::spanProcessor) + .forEach(sdkTracerProviderBuilder::addSpanProcessor); + OpenTelemetry ot = OpenTelemetrySdk.builder() - .setTracerProvider(SdkTracerProvider.builder() - .addSpanProcessor(spanProcessor(exporter)) - .setSampler(sampler) - .setResource(serviceName) - .build()) + .setTracerProvider(sdkTracerProviderBuilder.build()) .setPropagators(ContextPropagators.create(TextMapPropagator.composite(createPropagators()))) .build(); - result = HelidonOpenTelemetry.create(ot, ot.getTracer(this.serviceName), tags); + result = HelidonOpenTelemetry.create(ot, ot.getTracer(this.serviceName), Map.of()); if (global) { GlobalOpenTelemetry.set(ot); @@ -604,6 +616,12 @@ List createPropagators() { .toList(); } + // Primarily for testing + JaegerTracerBuilder exporter(SpanExporter spanExporter) { + adHocExporters.add(spanExporter); + return this; + } + private SpanProcessor spanProcessor(SpanExporter exporter) { return switch (spanProcessorType) { case BATCH -> BatchSpanProcessor.builder(exporter) diff --git a/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilderTest.java b/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilderTest.java index c2d14b93b0e..d97ddf28157 100644 --- a/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilderTest.java +++ b/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/JaegerTracerBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023 Oracle and/or its affiliates. + * Copyright (c) 2019, 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. @@ -21,13 +21,18 @@ import java.util.Map; import io.helidon.config.Config; +import io.helidon.tracing.Span; import io.helidon.tracing.Tracer; import io.helidon.tracing.TracerBuilder; +import io.helidon.tracing.providers.opentelemetry.HelidonOpenTelemetry; import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.extension.trace.propagation.B3Propagator; import io.opentelemetry.extension.trace.propagation.JaegerPropagator; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.trace.data.SpanData; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -126,4 +131,27 @@ void testFullHttp() { assertThat(propagators.get(1), instanceOf(JaegerPropagator.class)); assertThat(propagators.get(2), instanceOf(W3CBaggagePropagator.class)); } + + @Test + void testProcessTagHandling() { + TracerBuilder builder = TracerBuilder.create(config.get("jaeger-defaults")); + builder.addTracerTag("tracerLevelTag", "val-1"); + JaegerTracerBuilder jaegerTracerBuilder = builder.unwrap(JaegerTracerBuilder.class); + jaegerTracerBuilder.scheduleDelay(Duration.ofMillis(100)); // for faster test runs + jaegerTracerBuilder.exporter(new TestSpanExporterProvider().createExporter(null)); + Tracer tracer = builder.build(); + + Span span = tracer.spanBuilder("testSpan").tag("spanLevelTag", "val-2").start(); + span.end(); + + try (TestSpanExporter exporter = TestSpanExporterProvider.exporter()) { + List spanData = exporter.spanData(1); + assertThat("Expect span count", spanData.size(), is(1)); + SpanData spanDataItem = spanData.get(0); + assertThat("Trace-level attribute tracerLevelTag", + spanDataItem.getResource().getAttributes().get(AttributeKey.stringKey("tracerLevelTag")), + is("val-1")); + } + + } } \ No newline at end of file diff --git a/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/TestSpanExporter.java b/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/TestSpanExporter.java new file mode 100644 index 00000000000..bd64892307b --- /dev/null +++ b/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/TestSpanExporter.java @@ -0,0 +1,87 @@ +/* + * 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.tracing.providers.jaeger; + +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 static org.hamcrest.Matchers.iterableWithSize; + +// Partially inspired by the MP Telemetry TCK InMemorySpanExporter. +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; + } + + void clear() { + spanData.clear(); + } +} diff --git a/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/TestSpanExporterProvider.java b/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/TestSpanExporterProvider.java new file mode 100644 index 00000000000..022159d2800 --- /dev/null +++ b/tracing/providers/jaeger/src/test/java/io/helidon/tracing/providers/jaeger/TestSpanExporterProvider.java @@ -0,0 +1,48 @@ +/* + * 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.tracing.providers.jaeger; + +import io.helidon.common.LazyValue; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; +import io.opentelemetry.sdk.trace.export.SpanExporter; + +public class TestSpanExporterProvider implements ConfigurableSpanExporterProvider { + + private static final LazyValue SPAN_EXPORTER = LazyValue.create(TestSpanExporter::new); + + public TestSpanExporterProvider() { + System.err.println("provider ctor"); + } + + @Override + public SpanExporter createExporter(ConfigProperties configProperties) { + return SPAN_EXPORTER.get(); + } + + @Override + public String getName() { + return "in-memory"; + } + + static TestSpanExporter exporter() { + if (SPAN_EXPORTER.isLoaded()) { + return SPAN_EXPORTER.get(); + } + throw new IllegalStateException("Attempt to retrieve TestSpanExporter before it has been created"); + } +}