From adfbf5e6667901d6a3734ae107780ecc72c96263 Mon Sep 17 00:00:00 2001 From: brunobat Date: Thu, 15 Feb 2024 14:57:53 +0000 Subject: [PATCH] Add OTel Metrics plus refactor for generic senders --- .../deployment/OpenTelemetryProcessor.java | 1 + .../exporter/otlp/OtlpExporterProcessor.java | 53 ++- .../deployment/metric/MetricProcessor.java | 96 ++++++ .../deployment/tracing/TracerEnabled.java | 13 +- .../OpenTelemetryContinuousTestingTest.java | 1 + .../OpenTelemetryDisabledSdkTest.java | 2 +- .../OpenTelemetryLegacyConfigurationTest.java | 2 - .../deployment/OpenTelemetryMDCTest.java | 9 +- .../deployment/OpenTelemetryResourceTest.java | 22 +- .../common/InMemoryMetricExporter.java | 209 ++++++++++++ .../InMemoryMetricExporterProvider.java | 19 ++ .../exporter/otlp/OtlpExporterConfigTest.java | 4 +- ...ava => OtlpTraceExporterDisabledTest.java} | 20 +- .../GraphQLOpenTelemetryTest.java | 1 + .../GrpcOpenInstrumentationDisabledTest.java | 7 +- .../GrpcOpenTelemetryTest.java | 7 +- .../RestClientOpenTelemetryTest.java | 7 +- .../VertxClientOpenTelemetryTest.java | 7 +- .../VertxOpenTelemetryForwardedTest.java | 7 +- .../VertxOpenTelemetryXForwardedTest.java | 7 +- .../AddingSpanAttributesInterceptorTest.java | 2 +- .../interceptor/WithSpanInterceptorTest.java | 2 +- .../WithSpanLegacyInterceptorTest.java | 2 +- .../metrics/AsyncDoubleCounterTest.java | 42 +++ .../metrics/AsyncLongCounterTest.java | 41 +++ .../deployment/metrics/BaseMetricsTest.java | 53 +++ .../deployment/metrics/DoubleCounterTest.java | 59 ++++ .../deployment/metrics/DoubleGaugeTest.java | 41 +++ .../metrics/DoubleHistogramTest.java | 58 ++++ .../deployment/metrics/GaugeCdiTest.java | 84 +++++ .../deployment/metrics/LongCounterTest.java | 58 ++++ .../deployment/metrics/LongGaugeTest.java | 42 +++ .../deployment/metrics/LongHistogramTest.java | 60 ++++ .../metrics/LongUpDownCounterTest.java | 59 ++++ .../metrics/MetricsDisabledTest.java | 30 ++ ...emetryTextMapPropagatorCustomizerTest.java | 4 +- .../NonAppEndpointsDisabledTest.java | 4 +- ...nAppEndpointsDisabledWithRootPathTest.java | 4 +- ...dpointsEnabledLegacyConfigurationTest.java | 4 +- .../NonAppEndpointsEnabledTest.java | 4 +- .../NonAppEndpointsEqualRootPath.java | 4 +- .../OpenTelemetryCustomSamplerBeanTest.java | 4 +- .../OpenTelemetryHttpCDILegacyTest.java | 6 +- .../OpenTelemetryHttpCDITest.java | 6 +- .../OpenTelemetryIdGeneratorTest.java | 2 +- ...etryJdbcInstrumentationValidationTest.java | 2 +- .../OpenTelemetryReactiveRoutesTest.java | 4 +- .../OpenTelemetrySamplerBeanTest.java | 4 +- .../OpenTelemetrySamplerConfigTest.java | 4 +- .../OpenTelemetrySpanSecurityEventsTest.java | 2 +- .../TracerDisabledLegacyTest.java | 2 +- .../{ => traces}/TracerRouterUT.java | 2 +- ...metrics.ConfigurableMetricExporterProvider | 1 + .../resources/application-default.properties | 6 +- .../application-no-metrics.properties | 8 + .../resource-config/application.properties | 4 +- ...uredOpenTelemetrySdkBuilderCustomizer.java | 54 +++- .../OTelFallbackConfigSourceInterceptor.java | 1 - .../build/EndUserSpanProcessorConfig.java | 5 +- .../config/build/MetricsBuildConfig.java | 29 ++ .../runtime/config/build/OTelBuildConfig.java | 6 +- .../config/runtime/ExemplarsFilterType.java | 23 ++ .../config/runtime/MetricsRuntimeConfig.java | 20 ++ .../config/runtime/OTelRuntimeConfig.java | 5 + .../runtime/exporter/OtlpExporterConfig.java | 119 +++++++ .../exporter/OtlpExporterMetricsConfig.java | 8 + .../exporter/OtlpExporterRuntimeConfig.java | 6 +- .../exporter/OtlpExporterTracesConfig.java | 108 +------ .../exporter/otlp/OTelExporterRecorder.java | 216 +++++++++++-- .../exporter/otlp/OTelExporterUtil.java | 6 +- .../exporter/otlp/VertxHttpExporter.java | 302 ----------------- .../otlp/metrics/NoopMetricExporter.java | 37 +++ .../otlp/metrics/VertxGrpcMetricExporter.java | 52 +++ .../metrics/VertxHttpMetricsExporter.java | 52 +++ .../VertxGrpcSender.java} | 251 +++++++-------- .../exporter/otlp/sender/VertxHttpSender.java | 304 ++++++++++++++++++ .../{ => tracing}/EndUserSpanProcessor.java | 2 +- .../LateBoundBatchSpanProcessor.java | 2 +- ...RemoveableLateBoundBatchSpanProcessor.java | 2 +- .../otlp/tracing/VertxGrpcSpanExporter.java | 34 ++ .../otlp/tracing/VertxHttpSpanExporter.java | 34 ++ .../runtime/metrics/cdi/MetricsProducer.java | 29 ++ .../spi/MetricsExporterCDIProvider.java | 29 ++ .../runtime/tracing/cdi/TracerProducer.java | 94 +----- ...metrics.ConfigurableMetricExporterProvider | 1 + ...boss.resteasy.spi.concurrent.ThreadContext | 2 +- .../otlp/OtlpExporterProviderTest.java | 62 ++++ .../vertx/exporter/HelloResource.java | 9 + .../src/main/resources/application.properties | 1 + .../vertx/exporter/AbstractExporterTest.java | 58 +++- .../opentelemetry/vertx/exporter/Metrics.java | 19 ++ .../OtelCollectorLifecycleManager.java | 34 +- .../src/test/resources/otel-config.yaml | 6 +- .../it/opentelemetry/AbstractEndUserTest.java | 2 +- 94 files changed, 2469 insertions(+), 763 deletions(-) create mode 100644 extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/metric/MetricProcessor.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/InMemoryMetricExporter.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/InMemoryMetricExporterProvider.java rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/{OtlpExporterDisabledTest.java => OtlpTraceExporterDisabledTest.java} (51%) create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/AsyncDoubleCounterTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/AsyncLongCounterTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/BaseMetricsTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleCounterTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleGaugeTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleHistogramTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/GaugeCdiTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongCounterTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongGaugeTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongHistogramTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongUpDownCounterTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/MetricsDisabledTest.java rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => propagation}/OpenTelemetryTextMapPropagatorCustomizerTest.java (95%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/NonAppEndpointsDisabledTest.java (92%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/NonAppEndpointsDisabledWithRootPathTest.java (93%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/NonAppEndpointsEnabledLegacyConfigurationTest.java (93%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/NonAppEndpointsEnabledTest.java (93%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/NonAppEndpointsEqualRootPath.java (93%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/OpenTelemetryCustomSamplerBeanTest.java (96%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/OpenTelemetryHttpCDILegacyTest.java (94%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/OpenTelemetryHttpCDITest.java (94%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/OpenTelemetryIdGeneratorTest.java (98%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/OpenTelemetryJdbcInstrumentationValidationTest.java (97%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/OpenTelemetryReactiveRoutesTest.java (95%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/OpenTelemetrySamplerBeanTest.java (92%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/OpenTelemetrySamplerConfigTest.java (92%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/OpenTelemetrySpanSecurityEventsTest.java (98%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/TracerDisabledLegacyTest.java (94%) rename extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/{ => traces}/TracerRouterUT.java (89%) create mode 100644 extensions/opentelemetry/deployment/src/test/resources/META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider create mode 100644 extensions/opentelemetry/deployment/src/test/resources/resource-config/application-no-metrics.properties create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/MetricsBuildConfig.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/ExemplarsFilterType.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/MetricsRuntimeConfig.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterConfig.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterMetricsConfig.java delete mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxHttpExporter.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/NoopMetricExporter.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/VertxGrpcMetricExporter.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/VertxHttpMetricsExporter.java rename extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/{VertxGrpcExporter.java => sender/VertxGrpcSender.java} (74%) create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/sender/VertxHttpSender.java rename extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/{ => tracing}/EndUserSpanProcessor.java (96%) rename extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/{ => tracing}/LateBoundBatchSpanProcessor.java (97%) rename extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/{ => tracing}/RemoveableLateBoundBatchSpanProcessor.java (90%) create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/VertxGrpcSpanExporter.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/VertxHttpSpanExporter.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/cdi/MetricsProducer.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/spi/MetricsExporterCDIProvider.java create mode 100644 extensions/opentelemetry/runtime/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider create mode 100644 integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/Metrics.java diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java index b5b404f9f8120..893a33457d51e 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java @@ -105,6 +105,7 @@ AdditionalBeanBuildItem ensureProducerIsRetained() { AutoConfiguredOpenTelemetrySdkBuilderCustomizer.ResourceCustomizer.class, AutoConfiguredOpenTelemetrySdkBuilderCustomizer.SamplerCustomizer.class, AutoConfiguredOpenTelemetrySdkBuilderCustomizer.TracerProviderCustomizer.class, + AutoConfiguredOpenTelemetrySdkBuilderCustomizer.MetricProviderCustomizer.class, AutoConfiguredOpenTelemetrySdkBuilderCustomizer.TextMapPropagatorCustomizers.class) .build(); } 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 8d2d594d3ca44..9282c253fd5eb 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 @@ -13,6 +13,7 @@ import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; +import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -26,16 +27,16 @@ 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.OTelExporterRecorder; +import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.EndUserSpanProcessor; +import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.LateBoundBatchSpanProcessor; import io.quarkus.runtime.TlsConfig; import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; -@BuildSteps(onlyIf = OtlpExporterProcessor.OtlpExporterEnabled.class) +@BuildSteps public class OtlpExporterProcessor { - static class OtlpExporterEnabled implements BooleanSupplier { + static class OtlpTracingExporterEnabled implements BooleanSupplier { OtlpExporterBuildConfig exportBuildConfig; OTelBuildConfig otelBuildConfig; @@ -47,7 +48,19 @@ public boolean getAsBoolean() { } } - @BuildStep + static class OtlpMetricsExporterEnabled implements BooleanSupplier { + OtlpExporterBuildConfig exportBuildConfig; + OTelBuildConfig otelBuildConfig; + + public boolean getAsBoolean() { + return otelBuildConfig.enabled() && + otelBuildConfig.metrics().enabled().orElse(Boolean.TRUE) && + otelBuildConfig.metrics().exporter().contains(CDI_VALUE) && + exportBuildConfig.enabled(); + } + } + + @BuildStep(onlyIf = OtlpExporterProcessor.OtlpTracingExporterEnabled.class) void createEndUserSpanProcessor( BuildProducer buildProducer, OTelBuildConfig otelBuildConfig) { @@ -59,7 +72,7 @@ void createEndUserSpanProcessor( } @SuppressWarnings("deprecation") - @BuildStep + @BuildStep(onlyIf = OtlpExporterProcessor.OtlpTracingExporterEnabled.class) @Record(ExecutionTime.RUNTIME_INIT) void createBatchSpanProcessor(OTelExporterRecorder recorder, OTelRuntimeConfig otelRuntimeConfig, @@ -84,4 +97,32 @@ void createBatchSpanProcessor(OTelExporterRecorder recorder, vertxBuildItem.getVertx())) .done()); } + + @BuildStep(onlyIf = OtlpMetricsExporterEnabled.class) + @Record(ExecutionTime.RUNTIME_INIT) + void createMetricsExporterProcessor( + OTelExporterRecorder recorder, + List externalOtelExporterBuildItem, + OTelRuntimeConfig otelRuntimeConfig, + OtlpExporterRuntimeConfig exporterRuntimeConfig, + TlsConfig tlsConfig, + CoreVertxBuildItem vertxBuildItem, + BuildProducer syntheticBeanBuildItemBuildProducer) { + if (!externalOtelExporterBuildItem.isEmpty()) { + // if there is an external exporter, we don't want to create the default one + return; + } + + syntheticBeanBuildItemBuildProducer.produce(SyntheticBeanBuildItem + .configure(MetricExporter.class)// FIXME concrete class? No, PeriodicMetricReader is final + .types(MetricExporter.class) + .setRuntimeInit() + .scope(Singleton.class) + .unremovable() + .addInjectionPoint(ParameterizedType.create(DotName.createSimple(Instance.class), + new Type[] { ClassType.create(DotName.createSimple(MetricExporter.class.getName())) }, null)) + .createWith(recorder.createMetricExporter(otelRuntimeConfig, exporterRuntimeConfig, tlsConfig, + vertxBuildItem.getVertx())) + .done()); + } } diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/metric/MetricProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/metric/MetricProcessor.java new file mode 100644 index 0000000000000..11dd1b2058b95 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/metric/MetricProcessor.java @@ -0,0 +1,96 @@ +package io.quarkus.opentelemetry.deployment.metric; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Function; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; + +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig; +import io.quarkus.opentelemetry.runtime.metrics.cdi.MetricsProducer; + +@BuildSteps(onlyIf = MetricProcessor.MetricEnabled.class) +public class MetricProcessor { + private static final DotName METRIC_EXPORTER = DotName.createSimple(MetricExporter.class.getName()); + private static final DotName METRIC_READER = DotName.createSimple(MetricReader.class.getName()); + + @BuildStep + UnremovableBeanBuildItem ensureProducersAreRetained( + CombinedIndexBuildItem indexBuildItem, + BuildProducer additionalBeans) { + + additionalBeans.produce(AdditionalBeanBuildItem.builder() + .setUnremovable() + .addBeanClass(MetricsProducer.class) + .build()); + + IndexView index = indexBuildItem.getIndex(); + + // Find all known SpanExporters and SpanProcessors + Collection knownClasses = new HashSet<>(); + knownClasses.add(METRIC_EXPORTER.toString()); + index.getAllKnownImplementors(METRIC_EXPORTER) + .forEach(classInfo -> knownClasses.add(classInfo.name().toString())); + + knownClasses.add(METRIC_READER.toString()); + index.getAllKnownImplementors(METRIC_READER) + .forEach(classInfo -> knownClasses.add(classInfo.name().toString())); + + Set retainProducers = new HashSet<>(); + + for (AnnotationInstance annotation : index.getAnnotations(DotNames.PRODUCES)) { + AnnotationTarget target = annotation.target(); + switch (target.kind()) { + case METHOD: + MethodInfo method = target.asMethod(); + String returnType = method.returnType().name().toString(); + if (knownClasses.contains(returnType)) { + retainProducers.add(method.declaringClass().name().toString()); + } + break; + case FIELD: + FieldInfo field = target.asField(); + String fieldType = field.type().name().toString(); + if (knownClasses.contains(fieldType)) { + retainProducers.add(field.declaringClass().name().toString()); + } + break; + default: + break; + } + } + + return new UnremovableBeanBuildItem(new UnremovableBeanBuildItem.BeanClassNamesExclusion(retainProducers)); + } + + public static class MetricEnabled implements BooleanSupplier { + OTelBuildConfig otelBuildConfig; + + public boolean getAsBoolean() { + return otelBuildConfig.metrics().enabled() + .map(new Function() { + @Override + public Boolean apply(Boolean enabled) { + return otelBuildConfig.enabled() && enabled; + } + }) + .orElseGet(() -> otelBuildConfig.enabled()); + } + } +} diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerEnabled.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerEnabled.java index 96478719d5f3d..5102a51329bbc 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerEnabled.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerEnabled.java @@ -9,12 +9,13 @@ public class TracerEnabled implements BooleanSupplier { OTelBuildConfig otelConfig; public boolean getAsBoolean() { - return otelConfig.traces().enabled().map(new Function() { - @Override - public Boolean apply(Boolean tracerEnabled) { - return otelConfig.enabled() && tracerEnabled; - } - }) + return otelConfig.traces().enabled() + .map(new Function() { + @Override + public Boolean apply(Boolean tracerEnabled) { + return otelConfig.enabled() && tracerEnabled; + } + }) .orElseGet(() -> otelConfig.enabled()); } } diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryContinuousTestingTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryContinuousTestingTest.java index a1fb1c731deec..248412047e079 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryContinuousTestingTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryContinuousTestingTest.java @@ -10,6 +10,7 @@ import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; import io.quarkus.opentelemetry.deployment.common.TracerRouter; +import io.quarkus.opentelemetry.deployment.traces.TracerRouterUT; import io.quarkus.test.ContinuousTestingTestUtils; import io.quarkus.test.ContinuousTestingTestUtils.TestStatus; import io.quarkus.test.QuarkusDevModeTest; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDisabledSdkTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDisabledSdkTest.java index a239051302a0e..d77a2f14dbbb2 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDisabledSdkTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryDisabledSdkTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.opentelemetry.api.OpenTelemetry; -import io.quarkus.opentelemetry.runtime.exporter.otlp.LateBoundBatchSpanProcessor; +import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.LateBoundBatchSpanProcessor; import io.quarkus.test.QuarkusUnitTest; @Disabled("Not implemented") diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java index 3067e5df896b1..de6e6f9c11770 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryLegacyConfigurationTest.java @@ -31,7 +31,6 @@ class OpenTelemetryLegacyConfigurationTest { .overrideConfigKey("quarkus.opentelemetry.tracer.sampler", "off") .overrideConfigKey("quarkus.opentelemetry.tracer.sampler.ratio", "2.0d") .overrideConfigKey("quarkus.opentelemetry.tracer.exporter.otlp.headers", "header=value") - .overrideConfigKey("quarkus.opentelemetry.tracer.exporter.otlp.enabled", "false") .overrideConfigKey("quarkus.opentelemetry.tracer.exporter.otlp.endpoint", "http://localhost:4318/"); @Inject @@ -58,7 +57,6 @@ void config() { assertEquals("always_off", oTelBuildConfig.traces().sampler()); assertTrue(oTelRuntimeConfig.traces().samplerArg().isPresent()); assertEquals("2.0d", oTelRuntimeConfig.traces().samplerArg().get()); - assertEquals(FALSE, otlpExporterBuildConfig.enabled()); assertTrue(otlpExporterRuntimeConfig.traces().legacyEndpoint().isPresent()); assertTrue(otlpExporterRuntimeConfig.traces().headers().isPresent()); assertEquals("header=value", otlpExporterRuntimeConfig.traces().headers().get().get(0)); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryMDCTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryMDCTest.java index 103ea2c4a531c..572a020cb89b7 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryMDCTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryMDCTest.java @@ -28,6 +28,8 @@ import io.opentelemetry.context.Scope; import io.opentelemetry.sdk.trace.data.SpanData; import io.quarkus.arc.Unremovable; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider; import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; import io.quarkus.test.QuarkusUnitTest; @@ -40,9 +42,12 @@ public class OpenTelemetryMDCTest { .addClass(MdcEntry.class) .addClass(TestMdcCapturer.class) .addClass(TestResource.class) - .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) + .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class, + InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), - "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider")) .withConfigurationResource("application-default.properties"); @Inject diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryResourceTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryResourceTest.java index 92c919c44fe80..f1ee913e35cd6 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryResourceTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryResourceTest.java @@ -13,12 +13,17 @@ import jakarta.ws.rs.Path; import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider; import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; import io.quarkus.test.QuarkusUnitTest; @@ -29,17 +34,21 @@ public class OpenTelemetryResourceTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( () -> ShrinkWrap.create(JavaArchive.class) - .addClass(TestSpanExporter.class) - .addClass(TestSpanExporterProvider.class) + .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class, + InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) .addAsResource("resource-config/application.properties", "application.properties") .addAsResource( "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", - "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")); + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider")); @Inject SmallRyeConfig config; @Inject TestSpanExporter spanExporter; + @Inject + InMemoryMetricExporter metricExporter; @Test void resource() { @@ -56,12 +65,19 @@ void resource() { assertEquals(config.getRawValue("quarkus.uuid"), server.getResource().getAttribute(AttributeKey.stringKey("service.instance.id"))); assertNotNull(server.getResource().getAttribute(AttributeKey.stringKey("host.name"))); + + metricExporter.assertCountAtLeast(1); + List finishedMetricItems = metricExporter.getFinishedMetricItems(); } @Path("/hello") public static class HelloResource { + @Inject + Meter meter; + @GET public String hello() { + meter.counterBuilder("hello").build().add(1); return "hello"; } } diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/InMemoryMetricExporter.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/InMemoryMetricExporter.java new file mode 100644 index 0000000000000..59bd8aa6f4399 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/InMemoryMetricExporter.java @@ -0,0 +1,209 @@ +package io.quarkus.opentelemetry.deployment.common; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toMap; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Collectors; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Assertions; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.data.PointData; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.opentelemetry.semconv.SemanticAttributes; +import io.quarkus.arc.Unremovable; + +@Unremovable +@ApplicationScoped +public class InMemoryMetricExporter implements MetricExporter { + + private static final List LEGACY_KEY_COMPONENTS = List.of(SemanticAttributes.HTTP_METHOD.getKey(), + SemanticAttributes.HTTP_ROUTE.getKey(), + SemanticAttributes.HTTP_STATUS_CODE.getKey()); + private static final List KEY_COMPONENTS = List.of(SemanticAttributes.HTTP_REQUEST_METHOD.getKey(), + SemanticAttributes.HTTP_ROUTE.getKey(), + SemanticAttributes.HTTP_RESPONSE_STATUS_CODE.getKey()); + + private final Queue finishedMetricItems = new ConcurrentLinkedQueue<>(); + private final AggregationTemporality aggregationTemporality = AggregationTemporality.CUMULATIVE; + private boolean isStopped = false; + + public static Map getPointAttributes(final MetricData metricData, final String path) { + try { + return metricData.getData().getPoints().stream() + .filter(point -> isPathFound(path, point.getAttributes())) + .map(point -> point.getAttributes()) + .map(attributes1 -> attributes1.asMap()) + .flatMap(map -> map.entrySet().stream()) + .collect(toMap(map -> map.getKey().toString(), map -> map.getValue().toString())); + } catch (Exception e) { + System.out.println("Error getting point attributes for " + metricData.getName()); + metricData.getData().getPoints().stream() + .filter(point -> isPathFound(path, point.getAttributes())) + .map(point -> point.getAttributes()) + .map(attributes1 -> attributes1.asMap()) + .flatMap(map -> map.entrySet().stream()) + .forEach(attributeKeyObjectEntry -> System.out + .println(attributeKeyObjectEntry.getKey() + " " + attributeKeyObjectEntry.getValue())); + throw e; + } + } + + public static Map getMostRecentPointsMap(List finishedMetricItems) { + return finishedMetricItems.stream() + .flatMap(metricData -> metricData.getData().getPoints().stream()) + // exclude data from /export endpoint + .filter(InMemoryMetricExporter::notExporterPointData) + // newer first + .sorted(Comparator.comparingLong(PointData::getEpochNanos).reversed()) + .collect(toMap( + pointData -> pointData.getAttributes().asMap().entrySet().stream() + //valid attributes for the resulting map key + .filter(entry -> { + if (SemconvStability.emitOldHttpSemconv()) { + return LEGACY_KEY_COMPONENTS.contains(entry.getKey().getKey()); + } else { + return KEY_COMPONENTS.contains(entry.getKey().getKey()); + } + }) + // ensure order + .sorted(Comparator.comparing(o -> o.getKey().getKey())) + // build key + .map(entry -> entry.getKey().getKey() + ":" + entry.getValue().toString()) + .collect(joining(",")), + pointData -> pointData, + // most recent points will surface + (older, newer) -> newer)); + } + + /* + * ignore points with /export in the route + */ + private static boolean notExporterPointData(PointData pointData) { + return pointData.getAttributes().asMap().entrySet().stream() + .noneMatch(entry -> entry.getKey().getKey().equals(SemanticAttributes.HTTP_ROUTE.getKey()) && + entry.getValue().toString().contains("/export")); + } + + private static boolean isPathFound(String path, Attributes attributes) { + if (path == null) { + return true;// any match + } + Object value = attributes.asMap().get(AttributeKey.stringKey(SemanticAttributes.HTTP_ROUTE.getKey())); + if (value == null) { + return false; + } + return value.toString().equals(path); + } + + public void assertCount(final int count) { + Awaitility.await().atMost(5, SECONDS) + .untilAsserted(() -> Assertions.assertEquals(count, getFinishedMetricItems().size())); + } + + public void assertCount(final String name, final String target, final int count) { + Awaitility.await().atMost(5, SECONDS) + .untilAsserted(() -> Assertions.assertEquals(count, getFinishedMetricItems(name, target).size())); + } + + public void assertCountAtLeast(final int count) { + Awaitility.await().atMost(5, SECONDS) + .untilAsserted(() -> Assertions.assertTrue(count < getFinishedMetricItems().size())); + } + + public void assertCountAtLeast(final String name, final String target, final int count) { + Awaitility.await().atMost(5, SECONDS) + .untilAsserted(() -> Assertions.assertTrue(count < getFinishedMetricItems(name, target).size())); + } + + /** + * Returns a {@code List} of the finished {@code Metric}s, represented by {@code MetricData}. + * + * @return a {@code List} of the finished {@code Metric}s. + */ + public List getFinishedMetricItems() { + return Collections.unmodifiableList(new ArrayList<>(finishedMetricItems)); + } + + public List getFinishedMetricItems(final String name, final String target) { + return Collections.unmodifiableList(new ArrayList<>( + finishedMetricItems.stream() + .filter(metricData -> metricData.getName().equals(name)) + .filter(metricData -> metricData.getData().getPoints().stream() + .anyMatch(point -> isPathFound(target, point.getAttributes()))) + .collect(Collectors.toList()))); + } + + /** + * Clears the internal {@code List} of finished {@code Metric}s. + * + *

+ * Does not reset the state of this exporter if already shutdown. + */ + public void reset() { + finishedMetricItems.clear(); + } + + @Override + public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { + return aggregationTemporality; + } + + /** + * Exports the collection of {@code Metric}s into the inmemory queue. + * + *

+ * If this is called after {@code shutdown}, this will return {@code ResultCode.FAILURE}. + */ + @Override + public CompletableResultCode export(Collection metrics) { + if (isStopped) { + return CompletableResultCode.ofFailure(); + } + finishedMetricItems.addAll(metrics); + return CompletableResultCode.ofSuccess(); + } + + /** + * The InMemory exporter does not batch metrics, so this method will immediately return with + * success. + * + * @return always Success + */ + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + /** + * Clears the internal {@code List} of finished {@code Metric}s. + * + *

+ * Any subsequent call to export() function on this MetricExporter, will return {@code + * CompletableResultCode.ofFailure()} + */ + @Override + public CompletableResultCode shutdown() { + isStopped = true; + finishedMetricItems.clear(); + return CompletableResultCode.ofSuccess(); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/InMemoryMetricExporterProvider.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/InMemoryMetricExporterProvider.java new file mode 100644 index 0000000000000..6927ccb5ef4ae --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/InMemoryMetricExporterProvider.java @@ -0,0 +1,19 @@ +package io.quarkus.opentelemetry.deployment.common; + +import jakarta.enterprise.inject.spi.CDI; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider; +import io.opentelemetry.sdk.metrics.export.MetricExporter; + +public class InMemoryMetricExporterProvider implements ConfigurableMetricExporterProvider { + @Override + public MetricExporter createExporter(ConfigProperties configProperties) { + return CDI.current().select(InMemoryMetricExporter.class).get(); + } + + @Override + public String getName() { + return "in-memory"; + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterConfigTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterConfigTest.java index 26a3eb5f277ec..d9f82b8f6226e 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterConfigTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterConfigTest.java @@ -15,8 +15,8 @@ public class OtlpExporterConfigTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withEmptyApplication() - .overrideConfigKey("otel.traces.exporter", "cdi") - .overrideConfigKey("otel.exporter.otlp.traces.protocol", "http/protobuf") + .overrideConfigKey("quarkus.otel.traces.exporter", "cdi") + .overrideConfigKey("quarkus.otel.exporter.otlp.traces.protocol", "http/protobuf") .overrideConfigKey("quarkus.opentelemetry.tracer.exporter.otlp.endpoint", "http://localhost ") .overrideConfigKey("quarkus.otel.bsp.schedule.delay", "50") .overrideConfigKey("quarkus.otel.bsp.export.timeout", "PT1S"); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterDisabledTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpTraceExporterDisabledTest.java similarity index 51% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterDisabledTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpTraceExporterDisabledTest.java index 32d3abb217055..7d9e074025c18 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterDisabledTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpTraceExporterDisabledTest.java @@ -1,23 +1,25 @@ package io.quarkus.opentelemetry.deployment.exporter.otlp; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.opentelemetry.api.OpenTelemetry; -import io.quarkus.opentelemetry.runtime.exporter.otlp.LateBoundBatchSpanProcessor; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.LateBoundBatchSpanProcessor; import io.quarkus.test.QuarkusUnitTest; -public class OtlpExporterDisabledTest { +public class OtlpTraceExporterDisabledTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .withEmptyApplication() - .overrideConfigKey("otel.traces.exporter", "cdi") - .overrideConfigKey("quarkus.opentelemetry.tracer.exporter.otlp.enabled", "false"); + .overrideConfigKey("quarkus.otel.exporter.otlp.enabled", "false"); @Inject OpenTelemetry openTelemetry; @@ -25,9 +27,13 @@ public class OtlpExporterDisabledTest { @Inject Instance lateBoundBatchSpanProcessorInstance; + @Inject + Instance metricExporters; + @Test void testOpenTelemetryButNoBatchSpanProcessor() { - Assertions.assertNotNull(openTelemetry); - Assertions.assertFalse(lateBoundBatchSpanProcessorInstance.isResolvable()); + assertNotNull(openTelemetry); + assertFalse(lateBoundBatchSpanProcessorInstance.isResolvable()); + assertFalse(metricExporters.isResolvable()); } } diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java index b685abb0ec4c4..293e5279f2fb3 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GraphQLOpenTelemetryTest.java @@ -62,6 +62,7 @@ public class GraphQLOpenTelemetryTest { .addAsResource(new StringAsset("smallrye.graphql.allowGet=true"), "application.properties") .addAsResource(new StringAsset("smallrye.graphql.printDataFetcherException=true"), "application.properties") .addAsResource(new StringAsset("smallrye.graphql.events.enabled=true"), "application.properties") + .addAsResource(new StringAsset("quarkus.otel.metrics.exporter=none"), "application.properties") .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenInstrumentationDisabledTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenInstrumentationDisabledTest.java index 5c6bb07a37f23..6b1a4af1b4c82 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenInstrumentationDisabledTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenInstrumentationDisabledTest.java @@ -30,6 +30,8 @@ import io.quarkus.opentelemetry.deployment.HelloRequest; import io.quarkus.opentelemetry.deployment.HelloRequestOrBuilder; import io.quarkus.opentelemetry.deployment.MutinyGreeterGrpc; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider; import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; import io.quarkus.test.QuarkusUnitTest; @@ -41,13 +43,16 @@ public class GrpcOpenInstrumentationDisabledTest { static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot(root -> root .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) + .addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) .addClasses(HelloService.class) .addClasses(GreeterGrpc.class, MutinyGreeterGrpc.class, Greeter.class, GreeterBean.class, GreeterClient.class, HelloProto.class, HelloRequest.class, HelloRequestOrBuilder.class, HelloReply.class, HelloReplyOrBuilder.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), - "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider")) .withConfigurationResource("application-default.properties") .overrideConfigKey("quarkus.grpc.clients.hello.host", "localhost") .overrideConfigKey("quarkus.grpc.clients.hello.port", "9001") diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenTelemetryTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenTelemetryTest.java index e9d3d4c42c7b2..f2fc2c2c62638 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenTelemetryTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/GrpcOpenTelemetryTest.java @@ -58,6 +58,8 @@ import io.quarkus.opentelemetry.deployment.StreamingClient; import io.quarkus.opentelemetry.deployment.StreamingGrpc; import io.quarkus.opentelemetry.deployment.StreamingProto; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider; import io.quarkus.opentelemetry.deployment.common.SemconvResolver; import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; @@ -70,6 +72,7 @@ public class GrpcOpenTelemetryTest { static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class, SemconvResolver.class) + .addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) .addClasses(HelloService.class) .addClasses(GreeterGrpc.class, MutinyGreeterGrpc.class, Greeter.class, GreeterBean.class, GreeterClient.class, @@ -80,7 +83,9 @@ public class GrpcOpenTelemetryTest { Streaming.class, StreamingBean.class, StreamingClient.class, StreamingProto.class, Item.class, ItemOrBuilder.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), - "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider")) .withConfigurationResource("application-default.properties") .overrideConfigKey("quarkus.grpc.clients.greeter.host", "localhost") .overrideConfigKey("quarkus.grpc.clients.greeter.port", "9001") diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/RestClientOpenTelemetryTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/RestClientOpenTelemetryTest.java index 4cff5b2f144b4..97fba8ed5dda4 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/RestClientOpenTelemetryTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/RestClientOpenTelemetryTest.java @@ -36,6 +36,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider; import io.quarkus.opentelemetry.deployment.common.SemconvResolver; import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; @@ -46,8 +48,11 @@ public class RestClientOpenTelemetryTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest().withApplicationRoot((jar) -> jar .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class, SemconvResolver.class) + .addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), - "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider")) .withConfigurationResource("application-default.properties") .overrideConfigKey("quarkus.rest-client.client.url", "${test.url}"); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxClientOpenTelemetryTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxClientOpenTelemetryTest.java index bfde0e210a3e5..fc828e8ccfbb6 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxClientOpenTelemetryTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxClientOpenTelemetryTest.java @@ -31,6 +31,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider; import io.quarkus.opentelemetry.deployment.common.SemconvResolver; import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; @@ -50,8 +52,11 @@ public class VertxClientOpenTelemetryTest { static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class, SemconvResolver.class) + .addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), - "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider")) .withConfigurationResource("application-default.properties"); @Inject diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryForwardedTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryForwardedTest.java index b7cb2da17bc83..5ecf22ad53dbb 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryForwardedTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryForwardedTest.java @@ -15,6 +15,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider; import io.quarkus.opentelemetry.deployment.common.SemconvResolver; import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; @@ -28,8 +30,11 @@ public class VertxOpenTelemetryForwardedTest { .withApplicationRoot((jar) -> jar .addClass(TracerRouter.class) .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class, SemconvResolver.class) + .addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), - "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider")) .withConfigurationResource("application-default.properties"); @Inject diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryXForwardedTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryXForwardedTest.java index bbf97ebedb203..44c648d102ea2 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryXForwardedTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/instrumentation/VertxOpenTelemetryXForwardedTest.java @@ -15,6 +15,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider; import io.quarkus.opentelemetry.deployment.common.SemconvResolver; import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; @@ -28,8 +30,11 @@ public class VertxOpenTelemetryXForwardedTest { .withApplicationRoot((jar) -> jar .addClass(TracerRouter.class) .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class, SemconvResolver.class) + .addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), - "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider")) .withConfigurationResource("application-default.properties"); @Inject diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/AddingSpanAttributesInterceptorTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/AddingSpanAttributesInterceptorTest.java index 9853edf6aa817..eec6fb9ebd107 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/AddingSpanAttributesInterceptorTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/AddingSpanAttributesInterceptorTest.java @@ -38,7 +38,7 @@ public class AddingSpanAttributesInterceptorTest { .addAsManifestResource( "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", "services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") - .addAsResource("resource-config/application.properties", "application.properties")); + .addAsResource("resource-config/application-no-metrics.properties", "application.properties")); @Inject HelloRouter helloRouter; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/WithSpanInterceptorTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/WithSpanInterceptorTest.java index b7a0796320eb8..a27ffefde3dc1 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/WithSpanInterceptorTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/WithSpanInterceptorTest.java @@ -53,7 +53,7 @@ public class WithSpanInterceptorTest { .addAsManifestResource( "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", "services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") - .addAsResource("resource-config/application.properties", "application.properties")); + .addAsResource("resource-config/application-no-metrics.properties", "application.properties")); @Inject SpanBean spanBean; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/WithSpanLegacyInterceptorTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/WithSpanLegacyInterceptorTest.java index a38c18c57f626..6e334e0797421 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/WithSpanLegacyInterceptorTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/interceptor/WithSpanLegacyInterceptorTest.java @@ -44,7 +44,7 @@ public class WithSpanLegacyInterceptorTest { .addAsManifestResource( "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", "services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") - .addAsResource("resource-config/application.properties", "application.properties")); + .addAsResource("resource-config/application-no-metrics.properties", "application.properties")); @Inject SpanBean spanBean; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/AsyncDoubleCounterTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/AsyncDoubleCounterTest.java new file mode 100644 index 0000000000000..9cb0bf8a12ebd --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/AsyncDoubleCounterTest.java @@ -0,0 +1,42 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.metrics.data.MetricData; + +public class AsyncDoubleCounterTest extends BaseMetricsTest { + + private static final String counterName = "testAsyncDoubleCounter"; + private static final String counterDescription = "Testing double counter"; + private static final String counterUnit = "Metric Tonnes"; + + @Test + void counter() { + assertNotNull( + meter.counterBuilder(counterName) + .ofDoubles() + .setDescription(counterDescription) + .setUnit(counterUnit) + .buildWithCallback(measurement -> { + measurement.record(1, Attributes.empty()); + })); + + metricExporter.assertCountAtLeast(counterName, null, 1); + MetricData metric = metricExporter.getFinishedMetricItems(counterName, null).get(0); + + assertEquals(metric.getName(), counterName); + assertEquals(metric.getDescription(), counterDescription); + assertEquals(metric.getUnit(), counterUnit); + + assertEquals(metric.getDoubleSumData() + .getPoints() + .stream() + .findFirst() + .get() + .getValue(), 1); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/AsyncLongCounterTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/AsyncLongCounterTest.java new file mode 100644 index 0000000000000..bf1fc19c42929 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/AsyncLongCounterTest.java @@ -0,0 +1,41 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.metrics.data.MetricData; + +public class AsyncLongCounterTest extends BaseMetricsTest { + + private static final String counterName = "testLongCounter"; + private static final String counterDescription = "Testing long counter"; + private static final String counterUnit = "Metric Tonnes"; + + @Test + void counter() { + assertNotNull( + meter.counterBuilder(counterName) + .setDescription(counterDescription) + .setUnit(counterUnit) + .buildWithCallback(measurement -> { + measurement.record(1, Attributes.empty()); + })); + + metricExporter.assertCountAtLeast(counterName, null, 1); + MetricData metric = metricExporter.getFinishedMetricItems(counterName, null).get(0); + + assertEquals(metric.getName(), counterName); + assertEquals(metric.getDescription(), counterDescription); + assertEquals(metric.getUnit(), counterUnit); + + assertEquals(metric.getLongSumData() + .getPoints() + .stream() + .findFirst() + .get() + .getValue(), 1); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/BaseMetricsTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/BaseMetricsTest.java new file mode 100644 index 0000000000000..8751545d1f0bd --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/BaseMetricsTest.java @@ -0,0 +1,53 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import java.util.Map; +import java.util.stream.Collectors; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.metrics.Meter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class BaseMetricsTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) + .addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider") + .add(new StringAsset( + "quarkus.otel.traces.exporter=none\n" + + "quarkus.otel.metrics.exporter=in-memory\n" + + "quarkus.otel.metric.export.interval=50ms\n"), + "application.properties")); + + @Inject + protected Meter meter; + @Inject + protected InMemoryMetricExporter metricExporter; + + @BeforeEach + void setUp() { + metricExporter.reset(); + } + + protected static String mapToString(Map, ?> map) { + return (String) map.keySet().stream() + .map(key -> "" + key.getKey() + "=" + map.get(key)) + .collect(Collectors.joining(", ", "{", "}")); + } + +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleCounterTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleCounterTest.java new file mode 100644 index 0000000000000..922ef440d7550 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleCounterTest.java @@ -0,0 +1,59 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleCounter; +import io.opentelemetry.sdk.metrics.data.MetricData; + +public class DoubleCounterTest extends BaseMetricsTest { + private static final String counterName = "testDoubleCounter"; + private static final String counterDescription = "Testing double counter"; + private static final String counterUnit = "Metric Tonnes"; + + private static final double DOUBLE_WITH_ATTRIBUTES = 20.2; + private static final double DOUBLE_WITHOUT_ATTRIBUTES = 10.1; + + @Test + void counter() { + DoubleCounter doubleCounter = meter.counterBuilder(counterName) + .ofDoubles() + .setDescription(counterDescription) + .setUnit(counterUnit) + .build(); + assertNotNull(doubleCounter); + + Map expectedResults = new HashMap(); + expectedResults.put(DOUBLE_WITH_ATTRIBUTES, Attributes.builder().put("K", "V").build()); + expectedResults.put(DOUBLE_WITHOUT_ATTRIBUTES, Attributes.empty()); + expectedResults.keySet().stream() + .forEach(key -> doubleCounter.add(key, expectedResults.get(key))); + + metricExporter.assertCountAtLeast(counterName, null, 1); + MetricData metric = metricExporter.getFinishedMetricItems(counterName, null).get(0); + + assertEquals(metric.getName(), counterName); + assertEquals(metric.getDescription(), counterDescription); + assertEquals(metric.getUnit(), counterUnit); + + metric.getDoubleSumData().getPoints().stream() + .forEach(point -> { + assertTrue(expectedResults.containsKey(point.getValue()), + "Double" + point.getValue() + " was not an expected result"); + assertTrue(point.getAttributes().equals(expectedResults.get(point.getValue())), + "Attributes were not equal." + + System.lineSeparator() + "Actual values: " + + mapToString(point.getAttributes().asMap()) + + System.lineSeparator() + "Expected values: " + + mapToString(expectedResults.get(point.getValue()).asMap())); + }); + + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleGaugeTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleGaugeTest.java new file mode 100644 index 0000000000000..510e15452fff7 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleGaugeTest.java @@ -0,0 +1,41 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.metrics.data.MetricData; + +public class DoubleGaugeTest extends BaseMetricsTest { + + private static final String gaugeName = "testDoubleGauge"; + private static final String gaugeDescription = "Testing double gauge"; + private static final String gaugeUnit = "ms"; + + @Test + void gauge() { + assertNotNull( + meter.gaugeBuilder(gaugeName) + .setDescription(gaugeDescription) + .setUnit("ms") + .buildWithCallback(measurement -> { + measurement.record(1, Attributes.empty()); + })); + + metricExporter.assertCountAtLeast(gaugeName, null, 1); + MetricData metric = metricExporter.getFinishedMetricItems(gaugeName, null).get(0); + + assertEquals(metric.getName(), gaugeName); + assertEquals(metric.getDescription(), gaugeDescription); + assertEquals(metric.getUnit(), gaugeUnit); + + assertEquals(metric.getDoubleGaugeData() + .getPoints() + .stream() + .findFirst() + .get() + .getValue(), 1); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleHistogramTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleHistogramTest.java new file mode 100644 index 0000000000000..b4b8eb34bef57 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/DoubleHistogramTest.java @@ -0,0 +1,58 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.sdk.metrics.data.MetricData; + +public class DoubleHistogramTest extends BaseMetricsTest { + + private static final String histogramName = "testDoubleHistogram"; + private static final String histogramDescription = "Testing double histogram"; + private static final String histogramUnit = "Metric Tonnes"; + + private static final double DOUBLE_WITH_ATTRIBUTES = 20; + private static final double DOUBLE_WITHOUT_ATTRIBUTES = 10; + + @Test + void histogram() { + DoubleHistogram doubleHistogram = meter.histogramBuilder(histogramName) + .setDescription(histogramDescription) + .setUnit(histogramUnit) + .build(); + assertNotNull(doubleHistogram); + + Map expectedResults = new HashMap(); + expectedResults.put(DOUBLE_WITH_ATTRIBUTES, Attributes.builder().put("K", "V").build()); + expectedResults.put(DOUBLE_WITHOUT_ATTRIBUTES, Attributes.empty()); + expectedResults.keySet().stream() + .forEach(key -> doubleHistogram.record(key, expectedResults.get(key))); + + metricExporter.assertCountAtLeast(histogramName, null, 1); + MetricData metric = metricExporter.getFinishedMetricItems(histogramName, null).get(0); + + assertEquals(metric.getName(), histogramName); + assertEquals(metric.getDescription(), histogramDescription); + assertEquals(metric.getUnit(), histogramUnit); + + metric.getHistogramData().getPoints().stream() + .forEach(point -> { + assertTrue(expectedResults.containsKey(point.getSum()), + "Double " + point.getSum() + " was not an expected result"); + assertTrue(point.getAttributes().equals(expectedResults.get(point.getSum())), + "Attributes were not equal." + + System.lineSeparator() + "Actual values: " + + mapToString(point.getAttributes().asMap()) + + System.lineSeparator() + "Expected values: " + + mapToString(expectedResults.get(point.getSum()).asMap())); + }); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/GaugeCdiTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/GaugeCdiTest.java new file mode 100644 index 0000000000000..cec4a0f7240b2 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/GaugeCdiTest.java @@ -0,0 +1,84 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporter; +import io.quarkus.opentelemetry.deployment.common.TestSpanExporterProvider; +import io.quarkus.opentelemetry.deployment.common.TestUtil; +import io.quarkus.test.QuarkusUnitTest; + +public class GaugeCdiTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClass(TestUtil.class) + .addClass(MeterBean.class) + .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) + .addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class) + .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider") + .addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()), + "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider") + .add(new StringAsset( + "quarkus.datasource.db-kind=h2\n" + + "quarkus.datasource.jdbc.telemetry=true\n" + + "quarkus.otel.traces.exporter=test-span-exporter\n" + + "quarkus.otel.metrics.exporter=in-memory\n" + + "quarkus.otel.metric.export.interval=50ms\n" + + "quarkus.otel.bsp.export.timeout=1s\n" + + "quarkus.otel.bsp.schedule.delay=50\n"), + "application.properties")); + + @Inject + MeterBean meterBean; + + @Inject + InMemoryMetricExporter exporter; + + @BeforeEach + void setUp() { + exporter.reset(); + } + + @Test + void gauge() throws InterruptedException { + meterBean.getMeter() + .gaugeBuilder("jvm.memory.total") + .setDescription("Reports JVM memory usage.") + .setUnit("byte") + .buildWithCallback( + result -> result.record(Runtime.getRuntime().totalMemory(), Attributes.empty())); + exporter.assertCountAtLeast("jvm.memory.total", null, 1); + assertNotNull(exporter.getFinishedMetricItems("jvm.memory.total", null).get(0)); + } + + @Test + void meter() { + assertNotNull(meterBean.getMeter()); + } + + @ApplicationScoped + public static class MeterBean { + @Inject + Meter meter; + + public Meter getMeter() { + return meter; + } + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongCounterTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongCounterTest.java new file mode 100644 index 0000000000000..0cce6d182b7d8 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongCounterTest.java @@ -0,0 +1,58 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.sdk.metrics.data.MetricData; + +public class LongCounterTest extends BaseMetricsTest { + + private static final String counterName = "testLongCounter"; + private static final String counterDescription = "Testing long counter"; + private static final String counterUnit = "Metric Tonnes"; + + private static final long LONG_VALUE = 12; + private static final long LONG_WITH_ATTRIBUTES = 24; + private static final long LONG_WITHOUT_ATTRIBUTES = 12; + + @Test + void counter() { + LongCounter longCounter = meter.counterBuilder(counterName) + .setDescription(counterDescription) + .setUnit(counterUnit) + .build(); + assertNotNull(longCounter); + + Map expectedResults = new HashMap(); + expectedResults.put(LONG_WITH_ATTRIBUTES, Attributes.builder().put("K", "V").build()); + expectedResults.put(LONG_WITHOUT_ATTRIBUTES, Attributes.empty()); + expectedResults.keySet().stream().forEach(key -> longCounter.add(key, expectedResults.get(key))); + + metricExporter.assertCountAtLeast(counterName, null, 1); + MetricData metric = metricExporter.getFinishedMetricItems(counterName, null).get(0); + + assertEquals(metric.getName(), counterName); + assertEquals(metric.getDescription(), counterDescription); + assertEquals(metric.getUnit(), counterUnit); + + metric.getLongSumData().getPoints().stream() + .forEach(point -> { + assertTrue(expectedResults.containsKey(point.getValue()), + "Long" + point.getValue() + " was not an expected result"); + assertTrue(point.getAttributes().equals(expectedResults.get(point.getValue())), + "Attributes were not equal." + + System.lineSeparator() + "Actual values: " + + mapToString(point.getAttributes().asMap()) + + System.lineSeparator() + "Expected values: " + + mapToString(expectedResults.get(point.getValue()).asMap())); + }); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongGaugeTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongGaugeTest.java new file mode 100644 index 0000000000000..db1292eca01c7 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongGaugeTest.java @@ -0,0 +1,42 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.metrics.data.MetricData; + +public class LongGaugeTest extends BaseMetricsTest { + + private static final String gaugeName = "testLongGauge"; + private static final String gaugeDescription = "Testing long gauge"; + private static final String gaugeUnit = "ms"; + + @Test + void gauge() { + assertNotNull( + meter.gaugeBuilder(gaugeName) + .ofLongs() + .setDescription(gaugeDescription) + .setUnit("ms") + .buildWithCallback(measurement -> { + measurement.record(1, Attributes.empty()); + })); + + metricExporter.assertCountAtLeast(gaugeName, null, 1); + MetricData metric = metricExporter.getFinishedMetricItems(gaugeName, null).get(0); + + assertEquals(metric.getName(), gaugeName); + assertEquals(metric.getDescription(), gaugeDescription); + assertEquals(metric.getUnit(), gaugeUnit); + + assertEquals(metric.getLongGaugeData() + .getPoints() + .stream() + .findFirst() + .get() + .getValue(), 1); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongHistogramTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongHistogramTest.java new file mode 100644 index 0000000000000..c440490304f58 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongHistogramTest.java @@ -0,0 +1,60 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongHistogram; +import io.opentelemetry.sdk.metrics.data.MetricData; + +public class LongHistogramTest extends BaseMetricsTest { + + private static final String histogramName = "testLongHistogram"; + private static final String histogramDescription = "Testing long histogram"; + private static final String histogramUnit = "Metric Tonnes"; + + private static final long LONG_WITH_ATTRIBUTES = 20; + private static final long LONG_WITHOUT_ATTRIBUTES = 10; + + @Test + void histogram() { + LongHistogram longHistogram = meter.histogramBuilder(histogramName) + .ofLongs() + .setDescription(histogramDescription) + .setUnit(histogramUnit) + .build(); + assertNotNull(longHistogram); + + Map expectedResults = new HashMap(); + expectedResults.put(LONG_WITH_ATTRIBUTES, Attributes.builder().put("K", "V").build()); + expectedResults.put(LONG_WITHOUT_ATTRIBUTES, Attributes.empty()); + + expectedResults.keySet().stream() + .forEach(key -> longHistogram.record(key, expectedResults.get(key))); + + metricExporter.assertCountAtLeast(histogramName, null, 1); + MetricData metric = metricExporter.getFinishedMetricItems(histogramName, null).get(0); + + assertEquals(metric.getName(), histogramName); + assertEquals(metric.getDescription(), histogramDescription); + assertEquals(metric.getUnit(), histogramUnit); + + metric.getHistogramData().getPoints().stream() + .forEach(point -> { + assertTrue(expectedResults.containsKey((long) point.getSum()), + "Long " + (long) point.getSum() + " was not an expected result"); + assertTrue(point.getAttributes().equals(expectedResults.get((long) point.getSum())), + "Attributes were not equal." + + System.lineSeparator() + "Actual values: " + + mapToString(point.getAttributes().asMap()) + + System.lineSeparator() + "Expected values: " + + mapToString(expectedResults.get((long) point.getSum()).asMap())); + }); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongUpDownCounterTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongUpDownCounterTest.java new file mode 100644 index 0000000000000..3cf96585f6255 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/LongUpDownCounterTest.java @@ -0,0 +1,59 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongUpDownCounter; +import io.opentelemetry.sdk.metrics.data.MetricData; + +public class LongUpDownCounterTest extends BaseMetricsTest { + private static final String counterName = "testLongUpDownCounter"; + private static final String counterDescription = "Testing long up down counter"; + private static final String counterUnit = "Metric Tonnes"; + + private static final long LONG_WITH_ATTRIBUTES = -20; + private static final long LONG_WITHOUT_ATTRIBUTES = -10; + + @Test + void counter() { + LongUpDownCounter longUpDownCounter = meter.upDownCounterBuilder(counterName) + .setDescription(counterDescription) + .setUnit(counterUnit) + .build(); + assertNotNull(longUpDownCounter); + + Map expectedResults = new HashMap(); + expectedResults.put(LONG_WITH_ATTRIBUTES, Attributes.builder().put("K", "V").build()); + expectedResults.put(LONG_WITHOUT_ATTRIBUTES, Attributes.empty()); + + expectedResults.keySet().stream() + .forEach(key -> longUpDownCounter.add(key, expectedResults.get(key))); + + metricExporter.assertCountAtLeast(counterName, null, 1); + MetricData metric = metricExporter.getFinishedMetricItems(counterName, null).get(0); + + assertEquals(metric.getName(), counterName); + assertEquals(metric.getDescription(), counterDescription); + assertEquals(metric.getUnit(), counterUnit); + + metric.getDoubleSumData().getPoints().stream() + .forEach(point -> { + assertTrue(expectedResults.containsKey(point.getValue()), + "Long" + point.getValue() + " was not an expected result"); + assertTrue(point.getAttributes().equals(expectedResults.get(point.getValue())), + "Attributes were not equal." + + System.lineSeparator() + "Actual values: " + + mapToString(point.getAttributes().asMap()) + + System.lineSeparator() + "Expected values: " + + mapToString(expectedResults.get(point.getValue()).asMap())); + }); + + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/MetricsDisabledTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/MetricsDisabledTest.java new file mode 100644 index 0000000000000..41196b759df8f --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/MetricsDisabledTest.java @@ -0,0 +1,30 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import jakarta.enterprise.inject.spi.DeploymentException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.api.metrics.Meter; +import io.quarkus.test.QuarkusUnitTest; + +public class MetricsDisabledTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withEmptyApplication() + .overrideConfigKey("quarkus.otel.metrics.enabled", "false") + .assertException(t -> Assertions.assertEquals(DeploymentException.class, t.getClass())); + + @Inject + Meter openTelemetryMeter; + + @Test + void testNoOpenTelemetry() { + //Should not be reached: dump what was injected if it somehow passed + Assertions.assertNull(openTelemetryMeter, + "A OpenTelemetry Meter instance should not be found/injected when OpenTelemetry metrics is disabled"); + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTextMapPropagatorCustomizerTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/propagation/OpenTelemetryTextMapPropagatorCustomizerTest.java similarity index 95% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTextMapPropagatorCustomizerTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/propagation/OpenTelemetryTextMapPropagatorCustomizerTest.java index caf4b0ccaad5e..715dca1d8b5e5 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryTextMapPropagatorCustomizerTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/propagation/OpenTelemetryTextMapPropagatorCustomizerTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.propagation; import static io.quarkus.opentelemetry.deployment.common.TestSpanExporter.getSpanByKindAndParentId; import static org.assertj.core.api.Assertions.assertThat; @@ -39,7 +39,7 @@ public class OpenTelemetryTextMapPropagatorCustomizerTest { .addClass(TestSpanExporter.class) .addClass(TestSpanExporterProvider.class) .addClass(TestTextMapPropagatorCustomizer.class) - .addAsResource("resource-config/application.properties", "application.properties") + .addAsResource("resource-config/application-no-metrics.properties", "application.properties") .addAsResource( "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsDisabledTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsDisabledTest.java similarity index 92% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsDisabledTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsDisabledTest.java index 0cf4e370432fe..cd0a044062461 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsDisabledTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsDisabledTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -23,7 +23,7 @@ public class NonAppEndpointsDisabledTest { .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .withConfigurationResource("application-default.properties"); + .withConfigurationResource("resource-config/application-no-metrics.properties"); @Inject TestSpanExporter testSpanExporter; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsDisabledWithRootPathTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsDisabledWithRootPathTest.java similarity index 93% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsDisabledWithRootPathTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsDisabledWithRootPathTest.java index c468f987e7690..48589c7abb23b 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsDisabledWithRootPathTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsDisabledWithRootPathTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -23,7 +23,7 @@ public class NonAppEndpointsDisabledWithRootPathTest { .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .withConfigurationResource("application-default.properties") + .withConfigurationResource("resource-config/application-no-metrics.properties") .overrideConfigKey("quarkus.http.root-path", "/app") .overrideConfigKey("quarkus.http.non-application-root-path", "quarkus"); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsEnabledLegacyConfigurationTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsEnabledLegacyConfigurationTest.java similarity index 93% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsEnabledLegacyConfigurationTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsEnabledLegacyConfigurationTest.java index 5f2a2e62b021e..153c6ad54d940 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsEnabledLegacyConfigurationTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsEnabledLegacyConfigurationTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -23,7 +23,7 @@ public class NonAppEndpointsEnabledLegacyConfigurationTest { .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .withConfigurationResource("application-default.properties") + .withConfigurationResource("resource-config/application-no-metrics.properties") .overrideConfigKey("quarkus.opentelemetry.tracer.suppress-non-application-uris", "false"); @Inject diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsEnabledTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsEnabledTest.java similarity index 93% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsEnabledTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsEnabledTest.java index cc7fe7621c4ee..8e36d68c90cbb 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsEnabledTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsEnabledTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -23,7 +23,7 @@ public class NonAppEndpointsEnabledTest { .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .withConfigurationResource("application-default.properties") + .withConfigurationResource("resource-config/application-no-metrics.properties") .overrideConfigKey("quarkus.otel.traces.suppress-non-application-uris", "false"); @Inject diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsEqualRootPath.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsEqualRootPath.java similarity index 93% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsEqualRootPath.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsEqualRootPath.java index a0785225cd75f..0ce7116abdbee 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/NonAppEndpointsEqualRootPath.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/NonAppEndpointsEqualRootPath.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -23,7 +23,7 @@ public class NonAppEndpointsEqualRootPath { .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .withConfigurationResource("application-default.properties") + .withConfigurationResource("resource-config/application-no-metrics.properties") .overrideConfigKey("quarkus.http.root-path", "/app") .overrideConfigKey("quarkus.http.non-application-root-path", "/app"); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryCustomSamplerBeanTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryCustomSamplerBeanTest.java similarity index 96% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryCustomSamplerBeanTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryCustomSamplerBeanTest.java index 95cb9c7ee9835..9196e280c3dbc 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryCustomSamplerBeanTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryCustomSamplerBeanTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -43,7 +43,7 @@ public class OpenTelemetryCustomSamplerBeanTest { .addClass(TestUtil.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .withConfigurationResource("application-default.properties"); + .withConfigurationResource("resource-config/application-no-metrics.properties"); @Inject OpenTelemetry openTelemetry; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryHttpCDILegacyTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryHttpCDILegacyTest.java similarity index 94% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryHttpCDILegacyTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryHttpCDILegacyTest.java index dd03e834fa0a9..fdb7218df99e9 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryHttpCDILegacyTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryHttpCDILegacyTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static io.opentelemetry.api.trace.SpanKind.INTERNAL; import static io.opentelemetry.api.trace.SpanKind.SERVER; @@ -42,7 +42,7 @@ public class OpenTelemetryHttpCDILegacyTest { .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .withConfigurationResource("application-default.properties"); + .withConfigurationResource("resource-config/application-no-metrics.properties"); @Inject TestSpanExporter spanExporter; @@ -66,7 +66,7 @@ void telemetry() { assertEquals(SERVER, server.getKind()); // verify that OpenTelemetryServerFilter took place assertStringAttribute(server, SemanticAttributes.CODE_NAMESPACE, - "io.quarkus.opentelemetry.deployment.OpenTelemetryHttpCDILegacyTest$HelloResource"); + "io.quarkus.opentelemetry.deployment.traces.OpenTelemetryHttpCDILegacyTest$HelloResource"); assertStringAttribute(server, SemanticAttributes.CODE_FUNCTION, "hello"); SpanData internal = getSpanByKindAndParentId(spans, INTERNAL, server.getSpanId()); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryHttpCDITest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryHttpCDITest.java similarity index 94% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryHttpCDITest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryHttpCDITest.java index 19027b911c568..f8bd9be6817e5 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryHttpCDITest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryHttpCDITest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static io.opentelemetry.api.trace.SpanKind.INTERNAL; import static io.opentelemetry.api.trace.SpanKind.SERVER; @@ -42,7 +42,7 @@ public class OpenTelemetryHttpCDITest { .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .withConfigurationResource("application-default.properties"); + .withConfigurationResource("resource-config/application-no-metrics.properties"); @Inject TestSpanExporter spanExporter; @@ -65,7 +65,7 @@ void telemetry() { assertEquals("GET /hello", server.getName()); // verify that OpenTelemetryServerFilter took place assertStringAttribute(server, SemanticAttributes.CODE_NAMESPACE, - "io.quarkus.opentelemetry.deployment.OpenTelemetryHttpCDITest$HelloResource"); + "io.quarkus.opentelemetry.deployment.traces.OpenTelemetryHttpCDITest$HelloResource"); assertStringAttribute(server, SemanticAttributes.CODE_FUNCTION, "hello"); final SpanData internalFromBean = getSpanByKindAndParentId(spans, INTERNAL, server.getSpanId()); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryIdGeneratorTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryIdGeneratorTest.java similarity index 98% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryIdGeneratorTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryIdGeneratorTest.java index 1d4f148342567..fbf70b4ea0a65 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryIdGeneratorTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryIdGeneratorTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryJdbcInstrumentationValidationTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryJdbcInstrumentationValidationTest.java similarity index 97% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryJdbcInstrumentationValidationTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryJdbcInstrumentationValidationTest.java index 4f9f34bcade87..10f6ad3f8417e 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryJdbcInstrumentationValidationTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryJdbcInstrumentationValidationTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryReactiveRoutesTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryReactiveRoutesTest.java similarity index 95% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryReactiveRoutesTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryReactiveRoutesTest.java index 2af44b8e60a37..fd9b3088e43e7 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetryReactiveRoutesTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetryReactiveRoutesTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static io.opentelemetry.api.trace.SpanKind.SERVER; import static io.quarkus.opentelemetry.deployment.common.TestSpanExporter.getSpanByKindAndParentId; @@ -35,7 +35,7 @@ public class OpenTelemetryReactiveRoutesTest { .addClass(TestUtil.class) .addClass(TestSpanExporter.class) .addClass(TestSpanExporterProvider.class) - .addAsResource("resource-config/application.properties", "application.properties") + .addAsResource("resource-config/application-no-metrics.properties", "application.properties") .addAsResource( "META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerBeanTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySamplerBeanTest.java similarity index 92% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerBeanTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySamplerBeanTest.java index c58566046a8be..d155ed7d7fb21 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerBeanTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySamplerBeanTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.stringContainsInOrder; @@ -27,7 +27,7 @@ public class OpenTelemetrySamplerBeanTest { .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .withConfigurationResource("application-default.properties"); + .withConfigurationResource("resource-config/application-no-metrics.properties"); @Inject OpenTelemetry openTelemetry; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerConfigTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySamplerConfigTest.java similarity index 92% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerConfigTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySamplerConfigTest.java index 728c9b66af6c5..0ff64298d64b4 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySamplerConfigTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySamplerConfigTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -24,7 +24,7 @@ public class OpenTelemetrySamplerConfigTest { .addClasses(TestSpanExporter.class, TestSpanExporterProvider.class) .addAsResource(new StringAsset(TestSpanExporterProvider.class.getCanonicalName()), "META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")) - .withConfigurationResource("application-default.properties") + .withConfigurationResource("resource-config/application-no-metrics.properties") .overrideConfigKey("quarkus.otel.traces.sampler", "traceidratio") .overrideConfigKey("quarkus.otel.traces.sampler.arg", "0.5") .overrideConfigKey("quarkus.otel.traces.suppress-non-application-uris", "false"); diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySpanSecurityEventsTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySpanSecurityEventsTest.java similarity index 98% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySpanSecurityEventsTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySpanSecurityEventsTest.java index 616786d7ee668..9c89a1b2451c1 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/OpenTelemetrySpanSecurityEventsTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/OpenTelemetrySpanSecurityEventsTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/TracerDisabledLegacyTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/TracerDisabledLegacyTest.java similarity index 94% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/TracerDisabledLegacyTest.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/TracerDisabledLegacyTest.java index 1b0ac8fbe65e0..4cfeecbadaca9 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/TracerDisabledLegacyTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/TracerDisabledLegacyTest.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import jakarta.enterprise.inject.spi.DeploymentException; import jakarta.inject.Inject; diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/TracerRouterUT.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/TracerRouterUT.java similarity index 89% rename from extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/TracerRouterUT.java rename to extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/TracerRouterUT.java index 094e26d8e89cf..ef125fe6bc45c 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/TracerRouterUT.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/traces/TracerRouterUT.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.deployment; +package io.quarkus.opentelemetry.deployment.traces; import static org.hamcrest.Matchers.is; diff --git a/extensions/opentelemetry/deployment/src/test/resources/META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider b/extensions/opentelemetry/deployment/src/test/resources/META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider new file mode 100644 index 0000000000000..52aa734e638e5 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/resources/META-INF/services-config/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider @@ -0,0 +1 @@ +io.quarkus.opentelemetry.deployment.common.InMemoryMetricExporterProvider \ No newline at end of file diff --git a/extensions/opentelemetry/deployment/src/test/resources/application-default.properties b/extensions/opentelemetry/deployment/src/test/resources/application-default.properties index 3284a760fc5eb..67d968fe3afb8 100644 --- a/extensions/opentelemetry/deployment/src/test/resources/application-default.properties +++ b/extensions/opentelemetry/deployment/src/test/resources/application-default.properties @@ -1,3 +1,5 @@ quarkus.otel.traces.exporter=test-span-exporter -quarkus.otel.bsp.schedule.delay=50 -quarkus.otel.bsp.export.timeout=1s \ No newline at end of file +quarkus.otel.bsp.schedule.delay=50ms +quarkus.otel.bsp.export.timeout=1s +quarkus.otel.metrics.exporter=in-memory +quarkus.otel.metric.export.interval=50ms \ No newline at end of file diff --git a/extensions/opentelemetry/deployment/src/test/resources/resource-config/application-no-metrics.properties b/extensions/opentelemetry/deployment/src/test/resources/resource-config/application-no-metrics.properties new file mode 100644 index 0000000000000..cb6529bd136d5 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/resources/resource-config/application-no-metrics.properties @@ -0,0 +1,8 @@ +quarkus.application.name=resource-test +quarkus.otel.resource.attributes=service.name=authservice,service.instance.id=${quarkus.uuid} + +quarkus.otel.traces.exporter=test-span-exporter +quarkus.otel.bsp.schedule.delay=50ms +quarkus.otel.metrics.exporter=none + +quarkus.rest-client.client.url=${test.url} diff --git a/extensions/opentelemetry/deployment/src/test/resources/resource-config/application.properties b/extensions/opentelemetry/deployment/src/test/resources/resource-config/application.properties index a63d6ddd2f6b9..7b239c0e99f1c 100644 --- a/extensions/opentelemetry/deployment/src/test/resources/resource-config/application.properties +++ b/extensions/opentelemetry/deployment/src/test/resources/resource-config/application.properties @@ -2,6 +2,8 @@ quarkus.application.name=resource-test quarkus.otel.resource.attributes=service.name=authservice,service.instance.id=${quarkus.uuid} quarkus.otel.traces.exporter=test-span-exporter -quarkus.otel.bsp.schedule.delay=50 +quarkus.otel.bsp.schedule.delay=50ms +quarkus.otel.metrics.exporter=in-memory +quarkus.otel.metric.export.interval=50ms quarkus.rest-client.client.url=${test.url} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/AutoConfiguredOpenTelemetrySdkBuilderCustomizer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/AutoConfiguredOpenTelemetrySdkBuilderCustomizer.java index 27ec3b7e2661c..09390ad3932f9 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/AutoConfiguredOpenTelemetrySdkBuilderCustomizer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/AutoConfiguredOpenTelemetrySdkBuilderCustomizer.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; +import java.util.function.Predicate; import jakarta.enterprise.inject.Any; import jakarta.enterprise.inject.Instance; @@ -17,6 +18,8 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.IdGenerator; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; @@ -25,7 +28,7 @@ import io.quarkus.arc.All; import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig; import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig; -import io.quarkus.opentelemetry.runtime.exporter.otlp.RemoveableLateBoundBatchSpanProcessor; +import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.RemoveableLateBoundBatchSpanProcessor; import io.quarkus.opentelemetry.runtime.propagation.TextMapPropagatorCustomizer; import io.quarkus.opentelemetry.runtime.tracing.DelayedAttributes; import io.quarkus.opentelemetry.runtime.tracing.DropTargetsSampler; @@ -71,7 +74,12 @@ public Resource apply(Resource existingResource, ConfigProperties configProperti // inside resource customizer String serviceName = oTelRuntimeConfig .serviceName() - .filter(sn -> !sn.equals(appConfig.name.orElse("unset"))) + .filter(new Predicate() { + @Override + public boolean test(String sn) { + return !sn.equals(appConfig.name.orElse("unset")); + } + }) .orElse(null); // must be resolved at startup, once. @@ -170,19 +178,51 @@ public void customize(AutoConfiguredOpenTelemetrySdkBuilder builder) { builder.addTracerProviderCustomizer( new BiFunction<>() { @Override - public SdkTracerProviderBuilder apply(SdkTracerProviderBuilder builder, + public SdkTracerProviderBuilder apply(SdkTracerProviderBuilder tracerProviderBuilder, ConfigProperties configProperties) { if (oTelBuildConfig.traces().enabled().orElse(TRUE)) { - idGenerator.stream().findFirst().ifPresent(builder::setIdGenerator); // from cdi - spanProcessors.stream().filter(sp -> !(sp instanceof RemoveableLateBoundBatchSpanProcessor)) - .forEach(builder::addSpanProcessor); + idGenerator.stream().findFirst().ifPresent(tracerProviderBuilder::setIdGenerator); // from cdi + spanProcessors.stream().filter(new Predicate() { + @Override + public boolean test(SpanProcessor sp) { + return !(sp instanceof RemoveableLateBoundBatchSpanProcessor); + } + }) + .forEach(tracerProviderBuilder::addSpanProcessor); } - return builder; + return tracerProviderBuilder; } }); } } + @Singleton + final class MetricProviderCustomizer implements AutoConfiguredOpenTelemetrySdkBuilderCustomizer { + private final OTelBuildConfig oTelBuildConfig; + private final Clock clock; + + public MetricProviderCustomizer(OTelBuildConfig oTelBuildConfig, + final Clock clock) { + this.oTelBuildConfig = oTelBuildConfig; + this.clock = clock; + } + + @Override + public void customize(AutoConfiguredOpenTelemetrySdkBuilder builder) { + if (oTelBuildConfig.metrics().enabled().orElse(TRUE)) { + builder.addMeterProviderCustomizer( + new BiFunction() { + @Override + public SdkMeterProviderBuilder apply(SdkMeterProviderBuilder metricProvider, + ConfigProperties configProperties) { + metricProvider.setClock(clock); + return metricProvider; + } + }); + } + } + } + @Singleton final class TextMapPropagatorCustomizers implements AutoConfiguredOpenTelemetrySdkBuilderCustomizer { diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java index 3bdfea3b4863b..1ab8b9484b730 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java @@ -30,7 +30,6 @@ public class OTelFallbackConfigSourceInterceptor extends FallbackConfigSourceInt FALLBACKS.put("quarkus.otel.traces.include-static-resources", "quarkus.opentelemetry.tracer.include-static-resources"); FALLBACKS.put("quarkus.otel.traces.sampler", "quarkus.opentelemetry.tracer.sampler"); FALLBACKS.put("quarkus.otel.traces.sampler.arg", "quarkus.opentelemetry.tracer.sampler.ratio"); - FALLBACKS.put("quarkus.otel.exporter.otlp.enabled", "quarkus.opentelemetry.tracer.exporter.otlp.enabled"); FALLBACKS.put("quarkus.otel.exporter.otlp.traces.legacy-endpoint", "quarkus.opentelemetry.tracer.exporter.otlp.endpoint"); FALLBACKS.put("quarkus.otel.exporter.otlp.traces.headers", "quarkus.opentelemetry.tracer.exporter.otlp.headers"); 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 index 2e261c0d3e89b..1c94ea77e40b0 100644 --- 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 @@ -2,6 +2,7 @@ import java.util.Optional; +import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.EndUserSpanProcessor; import io.quarkus.runtime.annotations.ConfigGroup; import io.smallrye.config.WithDefault; @@ -12,9 +13,9 @@ public interface EndUserSpanProcessorConfig { /** - * Enable the {@link io.quarkus.opentelemetry.runtime.exporter.otlp.EndUserSpanProcessor}. + * Enable the {@link EndUserSpanProcessor}. *

- * The {@link io.quarkus.opentelemetry.runtime.exporter.otlp.EndUserSpanProcessor} adds + * The {@link EndUserSpanProcessor} adds * the {@link io.opentelemetry.semconv.SemanticAttributes.ENDUSER_ID} * and {@link io.opentelemetry.semconv.SemanticAttributes.ENDUSER_ROLE} to the Span. */ diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/MetricsBuildConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/MetricsBuildConfig.java new file mode 100644 index 0000000000000..1a215a0108cb2 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/MetricsBuildConfig.java @@ -0,0 +1,29 @@ +package io.quarkus.opentelemetry.runtime.config.build; + +import static io.quarkus.opentelemetry.runtime.config.build.ExporterType.Constants.CDI_VALUE; + +import java.util.List; +import java.util.Optional; + +import io.smallrye.config.WithDefault; + +public interface MetricsBuildConfig { + + /** + * Enable metrics with OpenTelemetry. + *

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

+ * Support for metrics will be enabled if OpenTelemetry support is enabled + * and either this value is true, or this value is unset. + */ + @Deprecated // FIXME deprecation? + @WithDefault("true") + Optional enabled(); + + /** + * The Metrics exporter to use. + */ + @WithDefault(CDI_VALUE) + List exporter(); +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/OTelBuildConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/OTelBuildConfig.java index 6a7a5aa09928b..883b94bf30465 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/OTelBuildConfig.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/build/OTelBuildConfig.java @@ -45,11 +45,9 @@ public interface OTelBuildConfig { TracesBuildConfig traces(); /** - * No Metrics exporter for now + * Metrics exporter configurations. */ - @WithName("metrics.exporter") - @WithDefault("none") - List metricsExporter(); + MetricsBuildConfig metrics(); /** * No Log exporter for now. diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/ExemplarsFilterType.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/ExemplarsFilterType.java new file mode 100644 index 0000000000000..9504070122212 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/ExemplarsFilterType.java @@ -0,0 +1,23 @@ +package io.quarkus.opentelemetry.runtime.config.runtime; + +public enum ExemplarsFilterType { + TRACE_BASED(Constants.TRACE_BASED), + ALWAYS_ON(Constants.ALWAYS_ON), + ALWAYS_OFF(Constants.ALWAYS_OFF); + + private final String value; + + ExemplarsFilterType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static class Constants { + public static final String TRACE_BASED = "trace_based"; + public static final String ALWAYS_ON = "always_on"; + public static final String ALWAYS_OFF = "always_off"; + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/MetricsRuntimeConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/MetricsRuntimeConfig.java new file mode 100644 index 0000000000000..da9abd483eb3e --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/MetricsRuntimeConfig.java @@ -0,0 +1,20 @@ +package io.quarkus.opentelemetry.runtime.config.runtime; + +import java.time.Duration; + +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithName; + +public interface MetricsRuntimeConfig { + + /** + * The interval, between the start of two metric export attempts. + *

+ * Default is 1min. + * + * @return the interval Duration. + */ + @WithName("export.interval") + @WithDefault("60s") + Duration exportInterval(); +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/OTelRuntimeConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/OTelRuntimeConfig.java index 3bacf75031d83..becd20d52ff46 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/OTelRuntimeConfig.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/OTelRuntimeConfig.java @@ -28,6 +28,11 @@ public interface OTelRuntimeConfig { */ TracesRuntimeConfig traces(); + /** + * Metric runtime config. + */ + MetricsRuntimeConfig metric(); + /** * environment variables for the types of attributes, for which that SDK implements truncation mechanism. */ diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterConfig.java new file mode 100644 index 0000000000000..ab09efccbb018 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterConfig.java @@ -0,0 +1,119 @@ +package io.quarkus.opentelemetry.runtime.config.runtime.exporter; + +import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig.DEFAULT_GRPC_BASE_URI; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; + +import io.quarkus.runtime.annotations.ConfigDocDefault; +import io.quarkus.runtime.annotations.ConfigGroup; +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithName; + +@ConfigGroup +public interface OtlpExporterConfig { + + /** + * OTLP Exporter specific. Will override otel.exporter.otlp.endpoint, if set. + *

+ * Fallbacks to the legacy property quarkus.opentelemetry.tracer.exporter.otlp.endpoint< or + * defaults to {@value OtlpExporterRuntimeConfig#DEFAULT_GRPC_BASE_URI}. + */ + @WithDefault(DEFAULT_GRPC_BASE_URI) + Optional endpoint(); + + /** + * Key-value pairs to be used as headers associated with gRPC requests. + * The format is similar to the {@code OTEL_EXPORTER_OTLP_HEADERS} environment variable, + * a list of key-value pairs separated by the "=" character. i.e.: key1=value1,key2=value2 + */ + Optional> headers(); + + /** + * Sets the method used to compress payloads. If unset, compression is disabled. Currently + * supported compression methods include `gzip` and `none`. + */ + Optional compression(); + + /** + * Sets the maximum time to wait for the collector to process an exported batch of spans. If + * unset, defaults to {@value OtlpExporterRuntimeConfig#DEFAULT_TIMEOUT_SECS}s. + */ + @WithDefault("10s") + Duration timeout(); + + /** + * OTLP defines the encoding of telemetry data and the protocol used to exchange data between the client and the + * server. Depending on the exporter, the available protocols will be different. + *

+ * Currently, only {@code grpc} and {@code http/protobuf} are allowed. + */ + @WithDefault(Protocol.GRPC) + Optional protocol(); + + /** + * Key/cert configuration in the PEM format. + */ + @WithName("key-cert") + KeyCert keyCert(); + + /** + * Trust configuration in the PEM format. + */ + @WithName("trust-cert") + TrustCert trustCert(); + + /** + * Set proxy options + */ + ProxyConfig proxyOptions(); + + interface ProxyConfig { + /** + * Set proxy username. + */ + Optional username(); + + /** + * Set proxy password. + */ + Optional password(); + + /** + * Set proxy port. + */ + @ConfigDocDefault("3128") + OptionalInt port(); + + /** + * Set proxy host. + */ + Optional host(); + } + + interface KeyCert { + /** + * Comma-separated list of the path to the key files (Pem format). + */ + Optional> keys(); + + /** + * Comma-separated list of the path to the certificate files (Pem format). + */ + Optional> certs(); + } + + interface TrustCert { + /** + * Comma-separated list of the trust certificate files (Pem format). + */ + Optional> certs(); + } + + class Protocol { + public static final String GRPC = "grpc"; + public static final String HTTP_PROTOBUF = "http/protobuf"; + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterMetricsConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterMetricsConfig.java new file mode 100644 index 0000000000000..1cf455445a7c1 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterMetricsConfig.java @@ -0,0 +1,8 @@ +package io.quarkus.opentelemetry.runtime.config.runtime.exporter; + +import io.quarkus.runtime.annotations.ConfigGroup; + +@ConfigGroup +public interface OtlpExporterMetricsConfig extends OtlpExporterConfig { + +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterRuntimeConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterRuntimeConfig.java index a6e8725cf3cff..d2d308c841296 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterRuntimeConfig.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterRuntimeConfig.java @@ -31,7 +31,11 @@ public interface OtlpExporterRuntimeConfig { * OTLP traces exporter configuration. */ OtlpExporterTracesConfig traces(); - // TODO metrics(); + + /** + * OTLP metrics exporter configuration. + */ + OtlpExporterMetricsConfig metrics(); // TODO logs(); // TODO additional global exporter configuration diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java index d65ed16722eae..12eac3685da91 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/exporter/OtlpExporterTracesConfig.java @@ -2,27 +2,14 @@ import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig.DEFAULT_GRPC_BASE_URI; -import java.time.Duration; -import java.util.List; import java.util.Optional; -import java.util.OptionalInt; -import io.quarkus.runtime.annotations.ConfigDocDefault; import io.quarkus.runtime.annotations.ConfigGroup; import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; @ConfigGroup -public interface OtlpExporterTracesConfig { - - /** - * OTLP Exporter specific. Will override otel.exporter.otlp.endpoint, if set. - *

- * Fallbacks to the legacy property quarkus.opentelemetry.tracer.exporter.otlp.endpoint< or - * defaults to {@value OtlpExporterRuntimeConfig#DEFAULT_GRPC_BASE_URI}. - */ - @WithDefault(DEFAULT_GRPC_BASE_URI) - Optional endpoint(); +public interface OtlpExporterTracesConfig extends OtlpExporterConfig { /** * See {@link OtlpExporterTracesConfig#endpoint} @@ -32,97 +19,4 @@ public interface OtlpExporterTracesConfig { @WithName("legacy-endpoint") @WithDefault(DEFAULT_GRPC_BASE_URI) Optional legacyEndpoint(); - - /** - * Key-value pairs to be used as headers associated with gRPC requests. - * The format is similar to the {@code OTEL_EXPORTER_OTLP_HEADERS} environment variable, - * a list of key-value pairs separated by the "=" character. i.e.: key1=value1,key2=value2 - */ - Optional> headers(); - - /** - * Sets the method used to compress payloads. If unset, compression is disabled. Currently - * supported compression methods include `gzip` and `none`. - */ - Optional compression(); - - /** - * Sets the maximum time to wait for the collector to process an exported batch of spans. If - * unset, defaults to {@value OtlpExporterRuntimeConfig#DEFAULT_TIMEOUT_SECS}s. - */ - @WithDefault("10s") - Duration timeout(); - - /** - * OTLP defines the encoding of telemetry data and the protocol used to exchange data between the client and the - * server. Depending on the exporter, the available protocols will be different. - *

- * Currently, only {@code grpc} and {@code http/protobuf} are allowed. - */ - @WithDefault(Protocol.GRPC) - Optional protocol(); - - /** - * Key/cert configuration in the PEM format. - */ - @WithName("key-cert") - KeyCert keyCert(); - - /** - * Trust configuration in the PEM format. - */ - @WithName("trust-cert") - TrustCert trustCert(); - - /** - * Set proxy options - */ - ProxyConfig proxyOptions(); - - interface KeyCert { - /** - * Comma-separated list of the path to the key files (Pem format). - */ - Optional> keys(); - - /** - * Comma-separated list of the path to the certificate files (Pem format). - */ - Optional> certs(); - } - - interface TrustCert { - /** - * Comma-separated list of the trust certificate files (Pem format). - */ - Optional> certs(); - } - - interface ProxyConfig { - /** - * Set proxy username. - */ - Optional username(); - - /** - * Set proxy password. - */ - Optional password(); - - /** - * Set proxy port. - */ - @ConfigDocDefault("3128") - OptionalInt port(); - - /** - * Set proxy host. - */ - Optional host(); - } - - class Protocol { - public static final String GRPC = "grpc"; - public static final String HTTP_PROTOBUF = "http/protobuf"; - } } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OTelExporterRecorder.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OTelExporterRecorder.java index c572dd72aba3f..9a382e0be6972 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OTelExporterRecorder.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OTelExporterRecorder.java @@ -1,8 +1,8 @@ package io.quarkus.opentelemetry.runtime.exporter.otlp; +import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterConfig.Protocol.GRPC; +import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterConfig.Protocol.HTTP_PROTOBUF; import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig.DEFAULT_GRPC_BASE_URI; -import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterTracesConfig.Protocol.GRPC; -import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterTracesConfig.Protocol.HTTP_PROTOBUF; import java.net.URI; import java.util.HashMap; @@ -18,17 +18,30 @@ import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.internal.ExporterBuilderUtil; +import io.opentelemetry.exporter.internal.grpc.GrpcExporter; import io.opentelemetry.exporter.internal.http.HttpExporter; +import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler; import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler; import io.opentelemetry.exporter.otlp.internal.OtlpUserAgent; +import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder; import io.opentelemetry.sdk.trace.export.SpanExporter; import io.quarkus.arc.SyntheticCreationalContext; import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig; import io.quarkus.opentelemetry.runtime.config.runtime.exporter.CompressionType; +import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterConfig; import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig; import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterTracesConfig; +import io.quarkus.opentelemetry.runtime.exporter.otlp.metrics.NoopMetricExporter; +import io.quarkus.opentelemetry.runtime.exporter.otlp.metrics.VertxGrpcMetricExporter; +import io.quarkus.opentelemetry.runtime.exporter.otlp.metrics.VertxHttpMetricsExporter; +import io.quarkus.opentelemetry.runtime.exporter.otlp.sender.VertxGrpcSender; +import io.quarkus.opentelemetry.runtime.exporter.otlp.sender.VertxHttpSender; +import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.LateBoundBatchSpanProcessor; +import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.RemoveableLateBoundBatchSpanProcessor; +import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.VertxGrpcSpanExporter; +import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.VertxHttpSpanExporter; import io.quarkus.runtime.TlsConfig; import io.quarkus.runtime.annotations.Recorder; import io.vertx.core.Vertx; @@ -102,17 +115,18 @@ private SpanExporter createOtlpGrpcSpanExporter(OtlpExporterRuntimeConfig export OtlpExporterTracesConfig tracesConfig = exporterRuntimeConfig.traces(); - return new VertxGrpcExporter( + return new VertxGrpcSpanExporter(new GrpcExporter( "otlp", // use the same as OTel does "span", // use the same as OTel does - MeterProvider::noop, - baseUri, - determineCompression(tracesConfig), - tracesConfig.timeout(), - populateTracingExportHttpHeaders(tracesConfig), - new HttpClientOptionsConsumer(tracesConfig, baseUri, tlsConfig), - vertx); - + new VertxGrpcSender( + baseUri, + VertxGrpcSender.GRPC_TRACE_SERVICE_NAME, + determineCompression(tracesConfig), + tracesConfig.timeout(), + populateTracingExportHttpHeaders(tracesConfig), + new HttpClientOptionsConsumer(tracesConfig, baseUri, tlsConfig), + vertx), + MeterProvider::noop)); } private SpanExporter createHttpSpanExporter(OtlpExporterRuntimeConfig exporterRuntimeConfig, Vertx vertx, @@ -122,11 +136,12 @@ private SpanExporter createHttpSpanExporter(OtlpExporterRuntimeConfig exporterRu boolean exportAsJson = false; //TODO: this will be enhanced in the future - return new VertxHttpExporter(new HttpExporter( + return new VertxHttpSpanExporter(new HttpExporter( "otlp", // use the same as OTel does "span", // use the same as OTel does - new VertxHttpExporter.VertxHttpSender( + new VertxHttpSender( baseUri, + VertxHttpSender.TRACES_PATH, determineCompression(tracesConfig), tracesConfig.timeout(), populateTracingExportHttpHeaders(tracesConfig), @@ -139,18 +154,167 @@ private SpanExporter createHttpSpanExporter(OtlpExporterRuntimeConfig exporterRu }; } - private static boolean determineCompression(OtlpExporterTracesConfig tracesConfig) { - if (tracesConfig.compression().isPresent()) { - return (tracesConfig.compression().get() == CompressionType.GZIP); + // public Function, MetricReader> createMetricReader( + // OTelRuntimeConfig otelRuntimeConfig, + // OtlpExporterRuntimeConfig exporterRuntimeConfig, + // TlsConfig tlsConfig, + // Supplier vertx) { + // + // final URI baseUri = getBaseUri(exporterRuntimeConfig); + // + // return new Function<>() { + // @Override + // public MetricReader apply(SyntheticCreationalContext context) { + // + // if (otelRuntimeConfig.sdkDisabled() || baseUri == null) { + // return NoopMetricReader.INSTANCE; + // } + // // Only create if an endpoint was set in runtime config and was properly validated at startup + // Instance metricExporters = context.getInjectedReference(new TypeLiteral<>() { + // }); + // if (!metricExporters.isUnsatisfied()) { + // return NoopMetricReader.INSTANCE; + // } + // + // MetricExporter metricExporter; + // + // try { + // OtlpExporterConfig metricsConfig = exporterRuntimeConfig.metrics(); + // if (metricsConfig.protocol().isEmpty()) { + // throw new IllegalStateException("No OTLP protocol specified. " + + // "Please check `quarkus.otel.exporter.otlp.traces.protocol` property"); + // } + // + // String protocol = metricsConfig.protocol().get(); + // if (GRPC.equals(protocol)) { + // metricExporter = new VertxGrpcMetricExporter( + // new GrpcExporter( + // "otlp", // use the same as OTel does + // "metric", // use the same as OTel does + // new VertxGrpcSender( + // baseUri, + // VertxGrpcSender.GRPC_METRIC_SERVICE_NAME, + // determineCompression(metricsConfig), + // metricsConfig.timeout(), + // populateTracingExportHttpHeaders(metricsConfig), + // new HttpClientOptionsConsumer(metricsConfig, baseUri, tlsConfig), + // vertx.get()), + // MeterProvider::noop)); + // } else if (HTTP_PROTOBUF.equals(protocol)) { + // boolean exportAsJson = false; //TODO: this will be enhanced in the future + // metricExporter = new VertxHttpMetricsExporter( + // new HttpExporter( + // "otlp", // use the same as OTel does + // "metric", // use the same as OTel does + // new VertxHttpSender( + // baseUri, + // VertxHttpSender.METRICS_PATH, + // determineCompression(metricsConfig), + // metricsConfig.timeout(), + // populateTracingExportHttpHeaders(metricsConfig), + // exportAsJson ? "application/json" : "application/x-protobuf", + // new HttpClientOptionsConsumer(metricsConfig, baseUri, tlsConfig), + // vertx.get()), + // MeterProvider::noop, + // exportAsJson)); + // } else { + // throw new IllegalArgumentException(String.format("Unsupported OTLP protocol %s specified. " + + // "Please check `quarkus.otel.exporter.otlp.traces.protocol` property", protocol)); + // } + // + // } catch (IllegalArgumentException iae) { + // throw new IllegalStateException("Unable to install OTLP Exporter", iae); + // } + // return PeriodicMetricReader.builder(metricExporter) + // .setInterval(otelRuntimeConfig.metrics().exportInterval()) + // // .setExecutor() // TODO don't use default? Executors.newScheduledThreadPool(1, new DaemonThreadFactory("PeriodicMetricReader")); + // .build(); + // } + // }; + // } + + public Function, MetricExporter> createMetricExporter( + OTelRuntimeConfig otelRuntimeConfig, + OtlpExporterRuntimeConfig exporterRuntimeConfig, + TlsConfig tlsConfig, + Supplier vertx) { + + final URI baseUri = getBaseUri(exporterRuntimeConfig); + + return new Function<>() { + @Override + public MetricExporter apply(SyntheticCreationalContext context) { + + if (otelRuntimeConfig.sdkDisabled() || baseUri == null) { + return NoopMetricExporter.INSTANCE; + } + + MetricExporter metricExporter; + + try { + OtlpExporterConfig metricsConfig = exporterRuntimeConfig.metrics(); + if (metricsConfig.protocol().isEmpty()) { + throw new IllegalStateException("No OTLP protocol specified. " + + "Please check `quarkus.otel.exporter.otlp.traces.protocol` property"); + } + + String protocol = metricsConfig.protocol().get(); + if (GRPC.equals(protocol)) { + metricExporter = new VertxGrpcMetricExporter( + new GrpcExporter( + "otlp", // use the same as OTel does + "metric", // use the same as OTel does + new VertxGrpcSender( + baseUri, + VertxGrpcSender.GRPC_METRIC_SERVICE_NAME, + determineCompression(metricsConfig), + metricsConfig.timeout(), + populateTracingExportHttpHeaders(metricsConfig), + new HttpClientOptionsConsumer(metricsConfig, baseUri, tlsConfig), + vertx.get()), + MeterProvider::noop)); + } else if (HTTP_PROTOBUF.equals(protocol)) { + boolean exportAsJson = false; //TODO: this will be enhanced in the future + metricExporter = new VertxHttpMetricsExporter( + new HttpExporter( + "otlp", // use the same as OTel does + "metric", // use the same as OTel does + new VertxHttpSender( + baseUri, + VertxHttpSender.METRICS_PATH, + determineCompression(metricsConfig), + metricsConfig.timeout(), + populateTracingExportHttpHeaders(metricsConfig), + exportAsJson ? "application/json" : "application/x-protobuf", + new HttpClientOptionsConsumer(metricsConfig, baseUri, tlsConfig), + vertx.get()), + MeterProvider::noop, + exportAsJson)); + } else { + throw new IllegalArgumentException(String.format("Unsupported OTLP protocol %s specified. " + + "Please check `quarkus.otel.exporter.otlp.traces.protocol` property", protocol)); + } + + } catch (IllegalArgumentException iae) { + throw new IllegalStateException("Unable to install OTLP Exporter", iae); + } + return metricExporter; + } + }; + } + + private static boolean determineCompression(OtlpExporterConfig config) { + if (config.compression().isPresent()) { + return (config.compression().get() == CompressionType.GZIP); } return false; } - private static Map populateTracingExportHttpHeaders(OtlpExporterTracesConfig tracesConfig) { + private static Map populateTracingExportHttpHeaders(OtlpExporterConfig config) { Map headersMap = new HashMap<>(); OtlpUserAgent.addUserAgentHeader(headersMap::put); - if (tracesConfig.headers().isPresent()) { - List headers = tracesConfig.headers().get(); + if (config.headers().isPresent()) { + List headers = config.headers().get(); if (!headers.isEmpty()) { for (String header : headers) { if (header.isEmpty()) { @@ -167,7 +331,7 @@ private static Map populateTracingExportHttpHeaders(OtlpExporter } private URI getBaseUri(OtlpExporterRuntimeConfig exporterRuntimeConfig) { - String endpoint = resolveEndpoint(exporterRuntimeConfig).trim(); + String endpoint = resolveEndpoint(exporterRuntimeConfig).trim(); // FIXME must be signal independent if (endpoint.isEmpty()) { return null; } @@ -190,12 +354,12 @@ private static boolean excludeDefaultEndpoint(String endpoint) { } private static class HttpClientOptionsConsumer implements Consumer { - private final OtlpExporterTracesConfig tracesConfig; + private final OtlpExporterConfig config; private final URI baseUri; private final TlsConfig tlsConfig; - public HttpClientOptionsConsumer(OtlpExporterTracesConfig tracesConfig, URI baseUri, TlsConfig tlsConfig) { - this.tracesConfig = tracesConfig; + public HttpClientOptionsConsumer(OtlpExporterConfig config, URI baseUri, TlsConfig tlsConfig) { + this.config = config; this.baseUri = baseUri; this.tlsConfig = tlsConfig; } @@ -222,7 +386,7 @@ private void configureTLS(HttpClientOptions options) { } private void configureProxyOptions(HttpClientOptions options) { - var proxyConfig = tracesConfig.proxyOptions(); + var proxyConfig = config.proxyOptions(); Optional proxyHost = proxyConfig.host(); if (proxyHost.isPresent()) { ProxyOptions proxyOptions = new ProxyOptions() @@ -270,7 +434,7 @@ private void configureProxyOptionsFromJDKSysProps(HttpClientOptions options) { } private KeyCertOptions toPemKeyCertOptions() { - OtlpExporterTracesConfig.KeyCert keyCert = tracesConfig.keyCert(); + OtlpExporterTracesConfig.KeyCert keyCert = config.keyCert(); if (keyCert.certs().isEmpty() && keyCert.keys().isEmpty()) { return null; } @@ -290,7 +454,7 @@ private KeyCertOptions toPemKeyCertOptions() { } private PemTrustOptions toPemTrustOptions() { - OtlpExporterTracesConfig.TrustCert trustCert = tracesConfig.trustCert(); + OtlpExporterTracesConfig.TrustCert trustCert = config.trustCert(); if (trustCert.certs().isPresent()) { List certs = trustCert.certs().get(); if (!certs.isEmpty()) { diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OTelExporterUtil.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OTelExporterUtil.java index 02aefe66744f9..7853c77875e9d 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OTelExporterUtil.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OTelExporterUtil.java @@ -3,12 +3,12 @@ import java.net.URI; import java.util.Locale; -final class OTelExporterUtil { +public final class OTelExporterUtil { private OTelExporterUtil() { } - static int getPort(URI uri) { + public static int getPort(URI uri) { int originalPort = uri.getPort(); if (originalPort > -1) { return originalPort; @@ -20,7 +20,7 @@ static int getPort(URI uri) { return 80; } - static boolean isHttps(URI uri) { + public static boolean isHttps(URI uri) { return "https".equals(uri.getScheme().toLowerCase(Locale.ROOT)); } } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxHttpExporter.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxHttpExporter.java deleted file mode 100644 index bc8472286dae8..0000000000000 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxHttpExporter.java +++ /dev/null @@ -1,302 +0,0 @@ -package io.quarkus.opentelemetry.runtime.exporter.otlp; - -import static io.quarkus.opentelemetry.runtime.exporter.otlp.OTelExporterUtil.getPort; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.URI; -import java.time.Duration; -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.zip.GZIPOutputStream; - -import io.opentelemetry.exporter.internal.http.HttpExporter; -import io.opentelemetry.exporter.internal.http.HttpSender; -import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.internal.ThrottlingLogger; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import io.quarkus.vertx.core.runtime.BufferOutputStream; -import io.smallrye.mutiny.Uni; -import io.vertx.core.AsyncResult; -import io.vertx.core.Handler; -import io.vertx.core.Vertx; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpClient; -import io.vertx.core.http.HttpClientOptions; -import io.vertx.core.http.HttpClientRequest; -import io.vertx.core.http.HttpClientResponse; -import io.vertx.core.http.HttpMethod; -import io.vertx.core.tracing.TracingPolicy; - -final class VertxHttpExporter implements SpanExporter { - - private static final Logger internalLogger = Logger.getLogger(VertxHttpExporter.class.getName()); - private static final ThrottlingLogger logger = new ThrottlingLogger(internalLogger); - - private static final int MAX_ATTEMPTS = 3; - - private final HttpExporter delegate; - - VertxHttpExporter(HttpExporter delegate) { - this.delegate = delegate; - } - - @Override - public CompletableResultCode export(Collection spans) { - TraceRequestMarshaler exportRequest = TraceRequestMarshaler.create(spans); - return delegate.export(exportRequest, spans.size()); - } - - @Override - public CompletableResultCode flush() { - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode shutdown() { - return delegate.shutdown(); - } - - static final class VertxHttpSender implements HttpSender { - - private static final String TRACES_PATH = "/v1/traces"; - private final String basePath; - private final boolean compressionEnabled; - private final Map headers; - private final String contentType; - private final HttpClient client; - - VertxHttpSender( - URI baseUri, - boolean compressionEnabled, - Duration timeout, - Map headersMap, - String contentType, - Consumer clientOptionsCustomizer, - Vertx vertx) { - this.basePath = determineBasePath(baseUri); - this.compressionEnabled = compressionEnabled; - this.headers = headersMap; - this.contentType = contentType; - var httpClientOptions = new HttpClientOptions() - .setReadIdleTimeout((int) timeout.getSeconds()) - .setDefaultHost(baseUri.getHost()) - .setDefaultPort(getPort(baseUri)) - .setTracingPolicy(TracingPolicy.IGNORE); // needed to avoid tracing the calls from this http client - clientOptionsCustomizer.accept(httpClientOptions); - this.client = vertx.createHttpClient(httpClientOptions); - } - - private final AtomicBoolean isShutdown = new AtomicBoolean(); - private final CompletableResultCode shutdownResult = new CompletableResultCode(); - - private static String determineBasePath(URI baseUri) { - String path = baseUri.getPath(); - if (path.isEmpty() || path.equals("/")) { - return ""; - } - if (path.endsWith("/")) { // strip ending slash - path = path.substring(0, path.length() - 1); - } - if (!path.startsWith("/")) { // prepend leading slash - path = "/" + path; - } - return path; - } - - @Override - public void send(Consumer marshaler, - int contentLength, - Consumer onHttpResponseRead, - Consumer onError) { - - String requestURI = basePath + TRACES_PATH; - var clientRequestSuccessHandler = new ClientRequestSuccessHandler(client, requestURI, headers, compressionEnabled, - contentType, - contentLength, onHttpResponseRead, - onError, marshaler, 1); - initiateSend(client, requestURI, MAX_ATTEMPTS, clientRequestSuccessHandler, onError); - } - - private static void initiateSend(HttpClient client, String requestURI, - int numberOfAttempts, - Handler clientRequestSuccessHandler, - Consumer onError) { - Uni.createFrom().completionStage(new Supplier>() { - @Override - public CompletionStage get() { - return client.request(HttpMethod.POST, requestURI).toCompletionStage(); - } - }).onFailure().retry() - .withBackOff(Duration.ofMillis(100)) - .atMost(numberOfAttempts) - .subscribe().with(new Consumer<>() { - @Override - public void accept(HttpClientRequest request) { - clientRequestSuccessHandler.handle(request); - } - }, onError); - } - - @Override - public CompletableResultCode shutdown() { - if (!isShutdown.compareAndSet(false, true)) { - logger.log(Level.FINE, "Calling shutdown() multiple times."); - return shutdownResult; - } - - client.close() - .onSuccess( - new Handler<>() { - @Override - public void handle(Void event) { - shutdownResult.succeed(); - } - }) - .onFailure(new Handler<>() { - @Override - public void handle(Throwable event) { - shutdownResult.fail(); - } - }); - return shutdownResult; - } - - private static class ClientRequestSuccessHandler implements Handler { - private final HttpClient client; - private final String requestURI; - private final Map headers; - private final boolean compressionEnabled; - private final String contentType; - private final int contentLength; - private final Consumer onHttpResponseRead; - private final Consumer onError; - private final Consumer marshaler; - - private final int attemptNumber; - - public ClientRequestSuccessHandler(HttpClient client, - String requestURI, Map headers, - boolean compressionEnabled, - String contentType, - int contentLength, - Consumer onHttpResponseRead, - Consumer onError, - Consumer marshaler, - int attemptNumber) { - this.client = client; - this.requestURI = requestURI; - this.headers = headers; - this.compressionEnabled = compressionEnabled; - this.contentType = contentType; - this.contentLength = contentLength; - this.onHttpResponseRead = onHttpResponseRead; - this.onError = onError; - this.marshaler = marshaler; - this.attemptNumber = attemptNumber; - } - - @Override - public void handle(HttpClientRequest request) { - - HttpClientRequest clientRequest = request.response(new Handler<>() { - @Override - public void handle(AsyncResult callResult) { - if (callResult.succeeded()) { - HttpClientResponse clientResponse = callResult.result(); - clientResponse.body(new Handler<>() { - @Override - public void handle(AsyncResult bodyResult) { - if (bodyResult.succeeded()) { - if (clientResponse.statusCode() >= 500) { - if (attemptNumber <= MAX_ATTEMPTS) { - // we should retry for 5xx error as they might be recoverable - initiateSend(client, requestURI, - MAX_ATTEMPTS - attemptNumber, - newAttempt(), - onError); - return; - } - } - onHttpResponseRead.accept(new Response() { - @Override - public int statusCode() { - return clientResponse.statusCode(); - } - - @Override - public String statusMessage() { - return clientResponse.statusMessage(); - } - - @Override - public byte[] responseBody() { - return bodyResult.result().getBytes(); - } - }); - } else { - if (attemptNumber <= MAX_ATTEMPTS) { - // retry - initiateSend(client, requestURI, - MAX_ATTEMPTS - attemptNumber, - newAttempt(), - onError); - } else { - onError.accept(bodyResult.cause()); - } - } - } - }); - } else { - if (attemptNumber <= MAX_ATTEMPTS) { - // retry - initiateSend(client, requestURI, - MAX_ATTEMPTS - attemptNumber, - newAttempt(), - onError); - } else { - onError.accept(callResult.cause()); - } - } - } - }) - .putHeader("Content-Type", contentType); - - Buffer buffer = Buffer.buffer(contentLength); - OutputStream os = new BufferOutputStream(buffer); - if (compressionEnabled) { - clientRequest.putHeader("Content-Encoding", "gzip"); - try (var gzos = new GZIPOutputStream(os)) { - marshaler.accept(gzos); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } else { - marshaler.accept(os); - } - - if (!headers.isEmpty()) { - for (var entry : headers.entrySet()) { - clientRequest.putHeader(entry.getKey(), entry.getValue()); - } - } - - clientRequest.send(buffer); - } - - public ClientRequestSuccessHandler newAttempt() { - return new ClientRequestSuccessHandler(client, requestURI, headers, compressionEnabled, - contentType, contentLength, onHttpResponseRead, - onError, marshaler, attemptNumber + 1); - } - } - } -} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/NoopMetricExporter.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/NoopMetricExporter.java new file mode 100644 index 0000000000000..a02cfd815c7fb --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/NoopMetricExporter.java @@ -0,0 +1,37 @@ +package io.quarkus.opentelemetry.runtime.exporter.otlp.metrics; + +import java.util.Collection; + +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.MetricExporter; + +public class NoopMetricExporter implements MetricExporter { + + public static final NoopMetricExporter INSTANCE = new NoopMetricExporter(); + + private NoopMetricExporter() { + } + + @Override + public CompletableResultCode export(Collection metrics) { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { + return AggregationTemporality.CUMULATIVE; + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/VertxGrpcMetricExporter.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/VertxGrpcMetricExporter.java new file mode 100644 index 0000000000000..db1855f569753 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/VertxGrpcMetricExporter.java @@ -0,0 +1,52 @@ +package io.quarkus.opentelemetry.runtime.exporter.otlp.metrics; + +import java.util.Collection; + +import io.opentelemetry.exporter.internal.grpc.GrpcExporter; +import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.MemoryMode; +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.MetricExporter; + +public class VertxGrpcMetricExporter implements MetricExporter { + + private final GrpcExporter delegate; + + public VertxGrpcMetricExporter(GrpcExporter grpcExporter) { + this.delegate = grpcExporter; + } + + @Override + public CompletableResultCode export(Collection metrics) { + return delegate.export(MetricsRequestMarshaler.create(metrics), metrics.size()); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return delegate.shutdown(); + } + + @Override + public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { + return AggregationTemporality.CUMULATIVE; // FIXME Make configurable + } + + @Override + public Aggregation getDefaultAggregation(InstrumentType instrumentType) { + return Aggregation.defaultAggregation(); // FIXME Make configurable + } + + @Override + public MemoryMode getMemoryMode() { + return MemoryMode.IMMUTABLE_DATA; // FIXME Make configurable + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/VertxHttpMetricsExporter.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/VertxHttpMetricsExporter.java new file mode 100644 index 0000000000000..a1bce46b478a6 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/metrics/VertxHttpMetricsExporter.java @@ -0,0 +1,52 @@ +package io.quarkus.opentelemetry.runtime.exporter.otlp.metrics; + +import java.util.Collection; + +import io.opentelemetry.exporter.internal.http.HttpExporter; +import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.MemoryMode; +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.MetricExporter; + +public class VertxHttpMetricsExporter implements MetricExporter { + + private final HttpExporter delegate; + + public VertxHttpMetricsExporter(HttpExporter delegate) { + this.delegate = delegate; + } + + @Override + public CompletableResultCode export(Collection metrics) { + return delegate.export(MetricsRequestMarshaler.create(metrics), metrics.size()); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return delegate.shutdown(); + } + + @Override + public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { + return AggregationTemporality.CUMULATIVE; // FIXME Make configurable + } + + @Override + public Aggregation getDefaultAggregation(InstrumentType instrumentType) { + return Aggregation.defaultAggregation(); // FIXME Make configurable + } + + @Override + public MemoryMode getMemoryMode() { + return MemoryMode.IMMUTABLE_DATA; // FIXME Make configurable + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxGrpcExporter.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/sender/VertxGrpcSender.java similarity index 74% rename from extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxGrpcExporter.java rename to extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/sender/VertxGrpcSender.java index af5206f3c152b..2cd09c3b46948 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/VertxGrpcExporter.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/sender/VertxGrpcSender.java @@ -1,26 +1,27 @@ -package io.quarkus.opentelemetry.runtime.exporter.otlp; +package io.quarkus.opentelemetry.runtime.exporter.otlp.sender; import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.Collection; import java.util.Map; import java.util.concurrent.CompletionStage; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; import io.netty.handler.codec.http.QueryStringDecoder; -import io.opentelemetry.api.metrics.MeterProvider; -import io.opentelemetry.exporter.internal.ExporterMetrics; -import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler; +import io.opentelemetry.exporter.internal.grpc.GrpcResponse; +import io.opentelemetry.exporter.internal.grpc.GrpcSender; +import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.internal.ThrottlingLogger; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.quarkus.opentelemetry.runtime.exporter.otlp.OTelExporterUtil; import io.quarkus.vertx.core.runtime.BufferOutputStream; import io.smallrye.mutiny.Uni; import io.vertx.core.Handler; @@ -36,15 +37,16 @@ import io.vertx.grpc.common.GrpcStatus; import io.vertx.grpc.common.ServiceName; -final class VertxGrpcExporter implements SpanExporter { +public final class VertxGrpcSender implements GrpcSender { - private static final String GRPC_SERVICE_NAME = "opentelemetry.proto.collector.trace.v1.TraceService"; + public static final String GRPC_TRACE_SERVICE_NAME = "opentelemetry.proto.collector.trace.v1.TraceService"; + public static final String GRPC_METRIC_SERVICE_NAME = "opentelemetry.proto.collector.metrics.v1.MetricsService"; private static final String GRPC_METHOD_NAME = "Export"; private static final String GRPC_STATUS = "grpc-status"; private static final String GRPC_MESSAGE = "grpc-message"; - private static final Logger internalLogger = Logger.getLogger(VertxGrpcExporter.class.getName()); + private static final Logger internalLogger = Logger.getLogger(VertxGrpcSender.class.getName()); private static final int MAX_ATTEMPTS = 3; private final ThrottlingLogger logger = new ThrottlingLogger(internalLogger); // TODO: is there something in JBoss Logging we can use? @@ -53,25 +55,22 @@ final class VertxGrpcExporter implements SpanExporter { private final AtomicBoolean loggedUnimplemented = new AtomicBoolean(); private final AtomicBoolean isShutdown = new AtomicBoolean(); private final CompletableResultCode shutdownResult = new CompletableResultCode(); - private final String type; - private final ExporterMetrics exporterMetrics; private final SocketAddress server; private final boolean compressionEnabled; private final Map headers; + private final String grpcEndpointPath; private final GrpcClient client; - VertxGrpcExporter( - String exporterName, - String type, - Supplier meterProviderSupplier, - URI grpcBaseUri, boolean compressionEnabled, + public VertxGrpcSender( + URI grpcBaseUri, + String grpcEndpointPath, + boolean compressionEnabled, Duration timeout, Map headersMap, Consumer clientOptionsCustomizer, Vertx vertx) { - this.type = type; - this.exporterMetrics = ExporterMetrics.createGrpcOkHttp(exporterName, type, meterProviderSupplier); + this.grpcEndpointPath = grpcEndpointPath; this.server = SocketAddress.inetSocketAddress(OTelExporterUtil.getPort(grpcBaseUri), grpcBaseUri.getHost()); this.compressionEnabled = compressionEnabled; this.headers = headersMap; @@ -83,70 +82,21 @@ final class VertxGrpcExporter implements SpanExporter { this.client = GrpcClient.client(vertx, httpClientOptions); } - private CompletableResultCode export(TraceRequestMarshaler marshaler, int numItems) { - if (isShutdown.get()) { - return CompletableResultCode.ofFailure(); - } - - exporterMetrics.addSeen(numItems); + @Override + public void send(Marshaler request, Runnable onSuccess, BiConsumer onError) { - var result = new CompletableResultCode(); - var onSuccessHandler = new ClientRequestOnSuccessHandler(client, server, headers, compressionEnabled, exporterMetrics, - marshaler, - loggedUnimplemented, logger, type, numItems, result, 1); + final String marshalerType = request.getClass().getSimpleName(); + var onSuccessHandler = new ClientRequestOnSuccessHandler(client, server, headers, compressionEnabled, + request, + loggedUnimplemented, logger, marshalerType, onSuccess, onError, 1, grpcEndpointPath, + isShutdown::get); initiateSend(client, server, MAX_ATTEMPTS, onSuccessHandler, new Consumer<>() { @Override public void accept(Throwable throwable) { - failOnClientRequest(numItems, throwable, result); + failOnClientRequest(marshalerType, throwable, onError); } }); - - return result; - } - - private static void initiateSend(GrpcClient client, SocketAddress server, - int numberOfAttempts, - Handler> onSuccessHandler, - Consumer onFailureCallback) { - Uni.createFrom().completionStage(new Supplier>>() { - - @Override - public CompletionStage> get() { - return client.request(server).toCompletionStage(); - } - }).onFailure().retry() - .withBackOff(Duration.ofMillis(100)) - .atMost(numberOfAttempts).subscribe().with( - new Consumer<>() { - @Override - public void accept(GrpcClientRequest request) { - onSuccessHandler.handle(request); - } - }, onFailureCallback); - } - - private void failOnClientRequest(int numItems, Throwable t, CompletableResultCode result) { - exporterMetrics.addFailed(numItems); - logger.log( - Level.SEVERE, - "Failed to export " - + type - + "s. The request could not be executed. Full error message: " - + t.getMessage()); - result.fail(); - } - - @Override - public CompletableResultCode export(Collection spans) { - TraceRequestMarshaler request = TraceRequestMarshaler.create(spans); - - return export(request, spans.size()); - } - - @Override - public CompletableResultCode flush() { - return CompletableResultCode.ofSuccess(); } @Override @@ -173,47 +123,96 @@ public void handle(Throwable event) { return shutdownResult; } + private static void initiateSend(GrpcClient client, SocketAddress server, + int numberOfAttempts, + Handler> onSuccessHandler, + Consumer onFailureCallback) { + Uni.createFrom().completionStage(new Supplier>>() { + @Override + public CompletionStage> get() { + return client.request(server).toCompletionStage(); + } + }) + .onFailure(new Predicate() { + @Override + public boolean test(Throwable t) { + // Will not retry on shutdown + return t instanceof IllegalStateException || + t instanceof RejectedExecutionException; + } + }) + .recoverWithUni(new Supplier>>() { + @Override + public Uni> get() { + return Uni.createFrom().nothing(); + } + }) + .onFailure() + .retry() + .withBackOff(Duration.ofMillis(100)) + .atMost(numberOfAttempts) + .subscribe().with( + new Consumer<>() { + @Override + public void accept(GrpcClientRequest request) { + onSuccessHandler.handle(request); + } + }, onFailureCallback); + } + + private void failOnClientRequest(String type, Throwable t, BiConsumer onError) { + String message = "Failed to export " + + type + + "s. The request could not be executed. Full error message: " + + (t.getMessage() == null ? t.getClass().getName() : t.getMessage()); + logger.log(Level.WARNING, message); + onError.accept(GrpcResponse.create(2 /* UNKNOWN */, message), t); + } + private static final class ClientRequestOnSuccessHandler implements Handler> { private final GrpcClient client; private final SocketAddress server; private final Map headers; private final boolean compressionEnabled; - private final ExporterMetrics exporterMetrics; - private final TraceRequestMarshaler marshaler; + private final Marshaler marshaler; private final AtomicBoolean loggedUnimplemented; private final ThrottlingLogger logger; private final String type; - private final int numItems; - private final CompletableResultCode result; + private final Runnable onSuccess; + private final BiConsumer onError; + private final String grpcEndpointPath; private final int attemptNumber; + private final Supplier isShutdown; public ClientRequestOnSuccessHandler(GrpcClient client, SocketAddress server, Map headers, boolean compressionEnabled, - ExporterMetrics exporterMetrics, - TraceRequestMarshaler marshaler, + Marshaler marshaler, AtomicBoolean loggedUnimplemented, ThrottlingLogger logger, String type, - int numItems, - CompletableResultCode result, - int attemptNumber) { + Runnable onSuccess, + BiConsumer onError, + int attemptNumber, + String grpcEndpointPath, + Supplier isShutdown) { this.client = client; this.server = server; + this.grpcEndpointPath = grpcEndpointPath; this.headers = headers; this.compressionEnabled = compressionEnabled; - this.exporterMetrics = exporterMetrics; this.marshaler = marshaler; this.loggedUnimplemented = loggedUnimplemented; this.logger = logger; this.type = type; - this.numItems = numItems; - this.result = result; + this.onSuccess = onSuccess; + this.onError = onError; this.attemptNumber = attemptNumber; + this.isShutdown = isShutdown; } @Override @@ -223,7 +222,7 @@ public void handle(GrpcClientRequest request) { } // Set the service name and the method to call - request.serviceName(ServiceName.create(GRPC_SERVICE_NAME)); + request.serviceName(ServiceName.create(grpcEndpointPath)); request.methodName(GRPC_METHOD_NAME); if (!headers.isEmpty()) { @@ -244,7 +243,7 @@ public void handle(GrpcClientResponse response) { response.exceptionHandler(new Handler<>() { @Override public void handle(Throwable t) { - if (attemptNumber <= MAX_ATTEMPTS) { + if (attemptNumber <= MAX_ATTEMPTS && !isShutdown.get()) { // retry initiateSend(client, server, MAX_ATTEMPTS - attemptNumber, @@ -252,19 +251,12 @@ public void handle(Throwable t) { new Consumer<>() { @Override public void accept(Throwable throwable) { - failOnClientRequest(numItems, throwable, result); + failOnClientRequest(throwable, onError, attemptNumber); } }); } else { - exporterMetrics.addFailed(numItems); - logger.log( - Level.SEVERE, - "Failed to export " - + type - + "s. The stream failed. Full error message: " - + t.getMessage()); - result.fail(); + failOnClientRequest(t, onError, attemptNumber); } } }).errorHandler(new Handler<>() { @@ -277,8 +269,7 @@ public void handle(GrpcError error) { public void handle(Void ignored) { GrpcStatus status = getStatus(response); if (status == GrpcStatus.OK) { - exporterMetrics.addSuccess(numItems); - result.succeed(); + onSuccess.run(); } else { handleError(status, response); } @@ -289,8 +280,9 @@ public void handle(Void ignored) { private void handleError(GrpcStatus status, GrpcClientResponse response) { String statusMessage = getStatusMessage(response); logAppropriateWarning(status, statusMessage); - exporterMetrics.addFailed(numItems); - result.fail(); + onError.accept( + GrpcResponse.create(2 /* UNKNOWN */, statusMessage), + new IllegalStateException(statusMessage)); } private void logAppropriateWarning(GrpcStatus status, @@ -301,7 +293,7 @@ private void logAppropriateWarning(GrpcStatus status, } } else if (status == GrpcStatus.UNAVAILABLE) { logger.log( - Level.SEVERE, + Level.WARNING, "Failed to export " + type + "s. Server is UNAVAILABLE. " @@ -355,7 +347,7 @@ private void logUnimplemented(Logger logger, String type, String fullErrorMessag } logger.log( - Level.SEVERE, + Level.WARNING, "Failed to export " + type + "s. Server responded with UNIMPLEMENTED. " @@ -397,7 +389,7 @@ private String getStatusMessage(GrpcClientResponse response) { }).onFailure(new Handler<>() { @Override public void handle(Throwable t) { - if (attemptNumber <= MAX_ATTEMPTS) { + if (attemptNumber <= MAX_ATTEMPTS && !isShutdown.get()) { // retry initiateSend(client, server, MAX_ATTEMPTS - attemptNumber, @@ -405,47 +397,38 @@ public void handle(Throwable t) { new Consumer<>() { @Override public void accept(Throwable throwable) { - failOnClientRequest(numItems, throwable, result); + failOnClientRequest(throwable, onError, attemptNumber); } }); } else { - exporterMetrics.addFailed(numItems); - logger.log( - Level.SEVERE, - "Failed to export " - + type - + "s. The request could not be executed. Full error message: " - + t.getMessage()); - result.fail(); + failOnClientRequest(t, onError, attemptNumber); } } }); } catch (IOException e) { - exporterMetrics.addFailed(numItems); - logger.log( - Level.SEVERE, - "Failed to export " - + type - + "s. Unable to serialize payload. Full error message: " - + e.getMessage()); - result.fail(); + final String message = "Failed to export " + + type + + "s. Unable to serialize payload. Full error message: " + + (e.getMessage() == null ? e.getClass().getName() : e.getMessage()); + logger.log(Level.WARNING, message); + onError.accept(GrpcResponse.create(2 /* UNKNOWN */, message), e); } } - private void failOnClientRequest(int numItems, Throwable t, CompletableResultCode result) { - exporterMetrics.addFailed(numItems); - logger.log( - Level.SEVERE, - "Failed to export " - + type - + "s. The request could not be executed. Full error message: " - + t.getMessage()); - result.fail(); + private void failOnClientRequest(Throwable t, BiConsumer onError, int attemptNumber) { + final String message = "Failed to export " + + type + + "s. The request could not be executed after " + attemptNumber + + " attempts. Full error message: " + + (t != null ? t.getMessage() : ""); + logger.log(Level.WARNING, message); + onError.accept(GrpcResponse.create(2 /* UNKNOWN */, message), t); } public ClientRequestOnSuccessHandler newAttempt() { - return new ClientRequestOnSuccessHandler(client, server, headers, compressionEnabled, exporterMetrics, marshaler, - loggedUnimplemented, logger, type, numItems, result, attemptNumber + 1); + return new ClientRequestOnSuccessHandler(client, server, headers, compressionEnabled, marshaler, + loggedUnimplemented, logger, type, onSuccess, onError, attemptNumber + 1, + grpcEndpointPath, isShutdown); } } } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/sender/VertxHttpSender.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/sender/VertxHttpSender.java new file mode 100644 index 0000000000000..d2b794d1278cc --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/sender/VertxHttpSender.java @@ -0,0 +1,304 @@ +package io.quarkus.opentelemetry.runtime.exporter.otlp.sender; + +import static io.quarkus.opentelemetry.runtime.exporter.otlp.OTelExporterUtil.getPort; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.GZIPOutputStream; + +import io.opentelemetry.exporter.internal.http.HttpSender; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.internal.ThrottlingLogger; +import io.quarkus.vertx.core.runtime.BufferOutputStream; +import io.smallrye.mutiny.Uni; +import io.vertx.core.AsyncResult; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpClient; +import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.http.HttpClientRequest; +import io.vertx.core.http.HttpClientResponse; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.tracing.TracingPolicy; + +public final class VertxHttpSender implements HttpSender { + + public static final String TRACES_PATH = "/v1/traces"; + public static final String METRICS_PATH = "/v1/metrics"; + + private static final Logger internalLogger = Logger.getLogger(VertxHttpSender.class.getName()); + private static final ThrottlingLogger logger = new ThrottlingLogger(internalLogger); + + private static final int MAX_ATTEMPTS = 3; + + private final String basePath; + private final boolean compressionEnabled; + private final Map headers; + private final String contentType; + private final HttpClient client; + private final String signalPath; + + public VertxHttpSender( + URI baseUri, + String signalPath, + boolean compressionEnabled, + Duration timeout, + Map headersMap, + String contentType, + Consumer clientOptionsCustomizer, + Vertx vertx) { + this.basePath = determineBasePath(baseUri); + this.signalPath = signalPath; + this.compressionEnabled = compressionEnabled; + this.headers = headersMap; + this.contentType = contentType; + var httpClientOptions = new HttpClientOptions() + .setReadIdleTimeout((int) timeout.getSeconds()) + .setDefaultHost(baseUri.getHost()) + .setDefaultPort(getPort(baseUri)) + .setTracingPolicy(TracingPolicy.IGNORE); // needed to avoid tracing the calls from this http client + clientOptionsCustomizer.accept(httpClientOptions); + this.client = vertx.createHttpClient(httpClientOptions); + } + + private final AtomicBoolean isShutdown = new AtomicBoolean(); + private final CompletableResultCode shutdownResult = new CompletableResultCode(); + + private static String determineBasePath(URI baseUri) { + String path = baseUri.getPath(); + if (path.isEmpty() || path.equals("/")) { + return ""; + } + if (path.endsWith("/")) { // strip ending slash + path = path.substring(0, path.length() - 1); + } + if (!path.startsWith("/")) { // prepend leading slash + path = "/" + path; + } + return path; + } + + @Override + public void send(Consumer marshaler, + int contentLength, + Consumer onHttpResponseRead, + Consumer onError) { + + String requestURI = basePath + signalPath; + var clientRequestSuccessHandler = new ClientRequestSuccessHandler(client, requestURI, headers, compressionEnabled, + contentType, + contentLength, onHttpResponseRead, + onError, marshaler, 1, isShutdown::get); + initiateSend(client, requestURI, MAX_ATTEMPTS, clientRequestSuccessHandler, onError, isShutdown::get); + } + + private static void initiateSend(HttpClient client, String requestURI, + int numberOfAttempts, + Handler clientRequestSuccessHandler, + Consumer onError, + Supplier isShutdown) { + Uni.createFrom().completionStage(new Supplier>() { + @Override + public CompletionStage get() { + return client.request(HttpMethod.POST, requestURI).toCompletionStage(); + } + }) + .onFailure(new Predicate() { + @Override + public boolean test(Throwable t) { + // Will not retry on shutdown + return t instanceof IllegalStateException || + t instanceof RejectedExecutionException; + } + }) + .recoverWithUni(new Supplier>() { + @Override + public Uni get() { + return Uni.createFrom().nothing(); + } + }) + .onFailure() + .retry() + .withBackOff(Duration.ofMillis(100)) + .atMost(numberOfAttempts) + .subscribe().with( + new Consumer<>() { + @Override + public void accept(HttpClientRequest request) { + clientRequestSuccessHandler.handle(request); + } + }, onError); + } + + @Override + public CompletableResultCode shutdown() { + if (!isShutdown.compareAndSet(false, true)) { + logger.log(Level.FINE, "Calling shutdown() multiple times."); + return shutdownResult; + } + + client.close() + .onSuccess( + new Handler<>() { + @Override + public void handle(Void event) { + shutdownResult.succeed(); + } + }) + .onFailure(new Handler<>() { + @Override + public void handle(Throwable event) { + shutdownResult.fail(); + } + }); + return shutdownResult; + } + + private static class ClientRequestSuccessHandler implements Handler { + private final HttpClient client; + private final String requestURI; + private final Map headers; + private final boolean compressionEnabled; + private final String contentType; + private final int contentLength; + private final Consumer onHttpResponseRead; + private final Consumer onError; + private final Consumer marshaler; + + private final int attemptNumber; + private final Supplier isShutdown; + + public ClientRequestSuccessHandler(HttpClient client, + String requestURI, Map headers, + boolean compressionEnabled, + String contentType, + int contentLength, + Consumer onHttpResponseRead, + Consumer onError, + Consumer marshaler, + int attemptNumber, + Supplier isShutdown) { + this.client = client; + this.requestURI = requestURI; + this.headers = headers; + this.compressionEnabled = compressionEnabled; + this.contentType = contentType; + this.contentLength = contentLength; + this.onHttpResponseRead = onHttpResponseRead; + this.onError = onError; + this.marshaler = marshaler; + this.attemptNumber = attemptNumber; + this.isShutdown = isShutdown; + } + + @Override + public void handle(HttpClientRequest request) { + + HttpClientRequest clientRequest = request.response(new Handler<>() { + @Override + public void handle(AsyncResult callResult) { + if (callResult.succeeded()) { + HttpClientResponse clientResponse = callResult.result(); + Throwable cause = callResult.cause(); + clientResponse.body(new Handler<>() { + @Override + public void handle(AsyncResult bodyResult) { + if (bodyResult.succeeded()) { + if (clientResponse.statusCode() >= 500) { + if (attemptNumber <= MAX_ATTEMPTS && !isShutdown.get()) { + // we should retry for 5xx error as they might be recoverable + initiateSend(client, requestURI, + MAX_ATTEMPTS - attemptNumber, + newAttempt(), + onError, + isShutdown); + return; + } + } + onHttpResponseRead.accept(new Response() { + @Override + public int statusCode() { + return clientResponse.statusCode(); + } + + @Override + public String statusMessage() { + return clientResponse.statusMessage(); + } + + @Override + public byte[] responseBody() { + return bodyResult.result().getBytes(); + } + }); + } else { + if (attemptNumber <= MAX_ATTEMPTS && !isShutdown.get()) { + // retry + initiateSend(client, requestURI, + MAX_ATTEMPTS - attemptNumber, + newAttempt(), + onError, + isShutdown); + } else { + onError.accept(bodyResult.cause()); + } + } + } + }); + } else { + if (attemptNumber <= MAX_ATTEMPTS && !isShutdown.get()) { + // retry + initiateSend(client, requestURI, + MAX_ATTEMPTS - attemptNumber, + newAttempt(), + onError, + isShutdown); + } else { + onError.accept(callResult.cause()); + } + } + } + }) + .putHeader("Content-Type", contentType); + + Buffer buffer = Buffer.buffer(contentLength); + OutputStream os = new BufferOutputStream(buffer); + if (compressionEnabled) { + clientRequest.putHeader("Content-Encoding", "gzip"); + try (var gzos = new GZIPOutputStream(os)) { + marshaler.accept(gzos); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } else { + marshaler.accept(os); + } + + if (!headers.isEmpty()) { + for (var entry : headers.entrySet()) { + clientRequest.putHeader(entry.getKey(), entry.getValue()); + } + } + + clientRequest.send(buffer); + } + + public ClientRequestSuccessHandler newAttempt() { + return new ClientRequestSuccessHandler(client, requestURI, headers, compressionEnabled, + contentType, contentLength, onHttpResponseRead, + onError, marshaler, attemptNumber + 1, isShutdown); + } + } +} 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/tracing/EndUserSpanProcessor.java similarity index 96% rename from extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/EndUserSpanProcessor.java rename to extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/EndUserSpanProcessor.java index a17cff566afd3..18f9c8f2b2fd9 100644 --- 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/tracing/EndUserSpanProcessor.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.runtime.exporter.otlp; +package io.quarkus.opentelemetry.runtime.exporter.otlp.tracing; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.control.ActivateRequestContext; diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/LateBoundBatchSpanProcessor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/LateBoundBatchSpanProcessor.java similarity index 97% rename from extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/LateBoundBatchSpanProcessor.java rename to extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/LateBoundBatchSpanProcessor.java index dde43e7c9dcc0..90318940e3497 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/LateBoundBatchSpanProcessor.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/LateBoundBatchSpanProcessor.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.runtime.exporter.otlp; +package io.quarkus.opentelemetry.runtime.exporter.otlp.tracing; import org.jboss.logging.Logger; diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/RemoveableLateBoundBatchSpanProcessor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/RemoveableLateBoundBatchSpanProcessor.java similarity index 90% rename from extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/RemoveableLateBoundBatchSpanProcessor.java rename to extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/RemoveableLateBoundBatchSpanProcessor.java index da44a0084b5b4..d8654e5ff634e 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/RemoveableLateBoundBatchSpanProcessor.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/RemoveableLateBoundBatchSpanProcessor.java @@ -1,4 +1,4 @@ -package io.quarkus.opentelemetry.runtime.exporter.otlp; +package io.quarkus.opentelemetry.runtime.exporter.otlp.tracing; import io.quarkus.opentelemetry.runtime.AutoConfiguredOpenTelemetrySdkBuilderCustomizer.TracerProviderCustomizer; diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/VertxGrpcSpanExporter.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/VertxGrpcSpanExporter.java new file mode 100644 index 0000000000000..58e03f24a4e45 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/VertxGrpcSpanExporter.java @@ -0,0 +1,34 @@ +package io.quarkus.opentelemetry.runtime.exporter.otlp.tracing; + +import java.util.Collection; + +import io.opentelemetry.exporter.internal.grpc.GrpcExporter; +import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; + +public final class VertxGrpcSpanExporter implements SpanExporter { + + private final GrpcExporter delegate; + + public VertxGrpcSpanExporter(GrpcExporter delegate) { + this.delegate = delegate; + } + + @Override + public CompletableResultCode export(Collection spans) { + TraceRequestMarshaler exportRequest = TraceRequestMarshaler.create(spans); + return delegate.export(exportRequest, spans.size()); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return delegate.shutdown(); + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/VertxHttpSpanExporter.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/VertxHttpSpanExporter.java new file mode 100644 index 0000000000000..dedba95064e45 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/exporter/otlp/tracing/VertxHttpSpanExporter.java @@ -0,0 +1,34 @@ +package io.quarkus.opentelemetry.runtime.exporter.otlp.tracing; + +import java.util.Collection; + +import io.opentelemetry.exporter.internal.http.HttpExporter; +import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; + +public final class VertxHttpSpanExporter implements SpanExporter { + + private final HttpExporter delegate; + + public VertxHttpSpanExporter(HttpExporter delegate) { + this.delegate = delegate; + } + + @Override + public CompletableResultCode export(Collection spans) { + TraceRequestMarshaler exportRequest = TraceRequestMarshaler.create(spans); + return delegate.export(exportRequest, spans.size()); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return delegate.shutdown(); + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/cdi/MetricsProducer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/cdi/MetricsProducer.java new file mode 100644 index 0000000000000..14972b1c82341 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/cdi/MetricsProducer.java @@ -0,0 +1,29 @@ +package io.quarkus.opentelemetry.runtime.metrics.cdi; + +import static io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig.INSTRUMENTATION_NAME; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.sdk.common.Clock; +import io.quarkus.arc.DefaultBean; + +@Singleton +public class MetricsProducer { + @Produces + @ApplicationScoped + @DefaultBean + public Meter getMeter() { + return GlobalOpenTelemetry.getMeter(INSTRUMENTATION_NAME); + } + + @Produces + @Singleton + @DefaultBean + public Clock getClock() { + return Clock.getDefault(); + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/spi/MetricsExporterCDIProvider.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/spi/MetricsExporterCDIProvider.java new file mode 100644 index 0000000000000..00fc8dd993c58 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/spi/MetricsExporterCDIProvider.java @@ -0,0 +1,29 @@ +package io.quarkus.opentelemetry.runtime.metrics.spi; + +import static io.quarkus.opentelemetry.runtime.config.build.ExporterType.Constants.CDI_VALUE; + +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.CDI; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.quarkus.opentelemetry.runtime.exporter.otlp.metrics.NoopMetricExporter; + +public class MetricsExporterCDIProvider implements ConfigurableMetricExporterProvider { + @Override + public MetricExporter createExporter(ConfigProperties configProperties) { + Instance exporters = CDI.current().select(MetricExporter.class, Any.Literal.INSTANCE); + if (exporters.isUnsatisfied()) { + return NoopMetricExporter.INSTANCE; + } else { + return exporters.get(); + } + } + + @Override + public String getName() { + return CDI_VALUE; // FIXME add META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java index 6c44fb6edbca3..b17c611cfd6e5 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/cdi/TracerProducer.java @@ -2,10 +2,6 @@ import static io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig.INSTRUMENTATION_NAME; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; - import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.RequestScoped; import jakarta.enterprise.inject.Produces; @@ -13,13 +9,7 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.baggage.Baggage; -import io.opentelemetry.api.baggage.BaggageBuilder; -import io.opentelemetry.api.baggage.BaggageEntry; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.quarkus.arc.DefaultBean; import io.quarkus.opentelemetry.runtime.tracing.DelayedAttributes; @@ -43,93 +33,13 @@ public Tracer getTracer() { @RequestScoped @DefaultBean public Span getSpan() { - return new Span() { - @Override - public Span setAttribute(final AttributeKey key, final T value) { - return Span.current().setAttribute(key, value); - } - - @Override - public Span addEvent(final String name, final Attributes attributes) { - return Span.current().addEvent(name, attributes); - } - - @Override - - public Span addEvent( - final String name, - final Attributes attributes, - final long timestamp, - final TimeUnit unit) { - return Span.current().addEvent(name, attributes, timestamp, unit); - } - - @Override - public Span setStatus(final StatusCode statusCode, final String description) { - return Span.current().setStatus(statusCode, description); - } - - @Override - public Span recordException(final Throwable exception, final Attributes additionalAttributes) { - return Span.current().recordException(exception, additionalAttributes); - } - - @Override - public Span updateName(final String name) { - return Span.current().updateName(name); - } - - @Override - public void end() { - Span.current().end(); - } - - @Override - public void end(final long timestamp, final TimeUnit unit) { - Span.current().end(timestamp, unit); - } - - @Override - public SpanContext getSpanContext() { - return Span.current().getSpanContext(); - } - - @Override - public boolean isRecording() { - return Span.current().isRecording(); - } - }; + return Span.current(); } @Produces @RequestScoped @DefaultBean public Baggage getBaggage() { - return new Baggage() { - @Override - public int size() { - return Baggage.current().size(); - } - - @Override - public void forEach(final BiConsumer consumer) { - Baggage.current().forEach(consumer); - } - - @Override - public Map asMap() { - return Baggage.current().asMap(); - } - - @Override - public String getEntryValue(final String entryKey) { - return Baggage.current().getEntryValue(entryKey); - } - - @Override - public BaggageBuilder toBuilder() { - return Baggage.current().toBuilder(); - } - }; + return Baggage.current(); } } diff --git a/extensions/opentelemetry/runtime/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider b/extensions/opentelemetry/runtime/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider new file mode 100644 index 0000000000000..124be714f4e1f --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider @@ -0,0 +1 @@ +io.quarkus.opentelemetry.runtime.metrics.spi.MetricsExporterCDIProvider \ No newline at end of file diff --git a/extensions/opentelemetry/runtime/src/main/resources/META-INF/services/org.jboss.resteasy.spi.concurrent.ThreadContext b/extensions/opentelemetry/runtime/src/main/resources/META-INF/services/org.jboss.resteasy.spi.concurrent.ThreadContext index c2b7e3c4bf1e7..972c5e8b0ec4f 100644 --- a/extensions/opentelemetry/runtime/src/main/resources/META-INF/services/org.jboss.resteasy.spi.concurrent.ThreadContext +++ b/extensions/opentelemetry/runtime/src/main/resources/META-INF/services/org.jboss.resteasy.spi.concurrent.ThreadContext @@ -1 +1 @@ -io.quarkus.opentelemetry.runtime.tracing.intrumentation.resteasy.OpenTelemetryClassicThreadContext +io.quarkus.opentelemetry.runtime.tracing.intrumentation.resteasy.OpenTelemetryClassicThreadContext \ No newline at end of file diff --git a/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OtlpExporterProviderTest.java b/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OtlpExporterProviderTest.java index d9c749311967a..c6b33e7f385ac 100644 --- a/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OtlpExporterProviderTest.java +++ b/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/exporter/otlp/OtlpExporterProviderTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test; import io.quarkus.opentelemetry.runtime.config.runtime.exporter.CompressionType; +import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterMetricsConfig; import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig; import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterTracesConfig; @@ -162,6 +163,67 @@ public Optional host() { } }; } + + @Override + public OtlpExporterMetricsConfig metrics() { + return new OtlpExporterMetricsConfig() { + @Override + public Optional endpoint() { + return Optional.ofNullable(newTrace); + } + + @Override + public Optional> headers() { + return Optional.empty(); + } + + @Override + public Optional compression() { + return Optional.empty(); + } + + @Override + public Duration timeout() { + return null; + } + + @Override + public Optional protocol() { + return Optional.empty(); + } + + @Override + public KeyCert keyCert() { + return new KeyCert() { + @Override + public Optional> keys() { + return Optional.empty(); + } + + @Override + public Optional> certs() { + return Optional.empty(); + } + }; + } + + @Override + public TrustCert trustCert() { + return new TrustCert() { + @Override + public Optional> certs() { + return Optional.empty(); + } + }; + } + + @Override + public ProxyConfig proxyOptions() { + return null; + } + }; + + } }; } } diff --git a/integration-tests/opentelemetry-vertx-exporter/src/main/java/io/quarkus/it/opentelemetry/vertx/exporter/HelloResource.java b/integration-tests/opentelemetry-vertx-exporter/src/main/java/io/quarkus/it/opentelemetry/vertx/exporter/HelloResource.java index f8c3ccc5a605b..a98fe5bfa1b10 100644 --- a/integration-tests/opentelemetry-vertx-exporter/src/main/java/io/quarkus/it/opentelemetry/vertx/exporter/HelloResource.java +++ b/integration-tests/opentelemetry-vertx-exporter/src/main/java/io/quarkus/it/opentelemetry/vertx/exporter/HelloResource.java @@ -1,13 +1,22 @@ package io.quarkus.it.opentelemetry.vertx.exporter; +import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; + @Path("hello") public class HelloResource { + @Inject + Meter meter; + @GET public String get() { + meter.counterBuilder("hello").build().add(1, Attributes.of(AttributeKey.stringKey("key"), "value")); return "get"; } diff --git a/integration-tests/opentelemetry-vertx-exporter/src/main/resources/application.properties b/integration-tests/opentelemetry-vertx-exporter/src/main/resources/application.properties index edb424258ea0a..714111c582b85 100644 --- a/integration-tests/opentelemetry-vertx-exporter/src/main/resources/application.properties +++ b/integration-tests/opentelemetry-vertx-exporter/src/main/resources/application.properties @@ -1 +1,2 @@ quarkus.application.name=integration test +quarkus.otel.metric.export.interval=1000ms \ No newline at end of file diff --git a/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/AbstractExporterTest.java b/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/AbstractExporterTest.java index 0dc4e49f6413f..9fde0e87bf056 100644 --- a/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/AbstractExporterTest.java +++ b/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/AbstractExporterTest.java @@ -1,18 +1,29 @@ package io.quarkus.it.opentelemetry.vertx.exporter; +import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME; import static io.restassured.RestAssured.when; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.common.v1.AnyValue; import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.metrics.v1.AggregationTemporality; +import io.opentelemetry.proto.metrics.v1.Metric; +import io.opentelemetry.proto.metrics.v1.NumberDataPoint; +import io.opentelemetry.proto.metrics.v1.ResourceMetrics; +import io.opentelemetry.proto.metrics.v1.Sum; import io.opentelemetry.proto.trace.v1.ResourceSpans; import io.opentelemetry.proto.trace.v1.ScopeSpans; import io.opentelemetry.proto.trace.v1.Span; @@ -21,17 +32,20 @@ public abstract class AbstractExporterTest { Traces traces; + Metrics metrics; @BeforeEach @AfterEach void setUp() { traces.reset(); + metrics.reset(); } @Test void test() { verifyHttpResponse(); verifyTraces(); + verifyMetrics(); } private void verifyHttpResponse() { @@ -53,7 +67,7 @@ private void verifyTraces() { assertThat(resourceSpans.getResource().getAttributesList()) .contains( KeyValue.newBuilder() - .setKey(ResourceAttributes.SERVICE_NAME.getKey()) + .setKey(SERVICE_NAME.getKey()) .setValue(AnyValue.newBuilder() .setStringValue("integration test").build()) .build()) @@ -76,4 +90,46 @@ private void verifyTraces() { .setStringValue("GET").build()) .build()); } + + private void verifyMetrics() { + await() + .atMost(Duration.ofSeconds(30)) + .untilAsserted(() -> assertThat(metrics.getMetricRequests()).hasSizeGreaterThan(1)); + ExportMetricsServiceRequest request = metrics.getMetricRequests().get(0); + assertEquals(1, request.getResourceMetricsCount()); + + ResourceMetrics resourceMetrics = request.getResourceMetrics(0); + assertThat(resourceMetrics.getResource().getAttributesList()) + .contains( + KeyValue.newBuilder() + .setKey(SERVICE_NAME.getKey()) + .setValue(AnyValue.newBuilder().setStringValue("integration test").build()) + .build()); + assertThat(resourceMetrics.getScopeMetricsCount()).isEqualTo(2); + + Optional helloMetric = resourceMetrics.getScopeMetricsList().stream() + .map(scopeMetrics -> scopeMetrics.getMetricsList()) + .filter(metrics -> metrics.stream().anyMatch(metric -> metric.getName().equals("hello"))) + .flatMap(List::stream) + .findFirst(); + + assertThat(helloMetric).isPresent(); + assertThat(helloMetric.get().getDataCase()).isEqualTo(Metric.DataCase.SUM); + + Sum sum = helloMetric.get().getSum(); + assertThat(sum.getAggregationTemporality()) + .isEqualTo(AggregationTemporality.AGGREGATION_TEMPORALITY_CUMULATIVE); + assertThat(sum.getDataPointsCount()).isEqualTo(1); + + NumberDataPoint dataPoint = sum.getDataPoints(0); + assertThat(dataPoint.getAsInt()).isEqualTo(1); + assertThat(dataPoint.getAttributesList()) + .isEqualTo( + Collections.singletonList( + KeyValue.newBuilder() + .setKey("key") + .setValue(AnyValue.newBuilder().setStringValue("value").build()) + .build())); + assertThat(dataPoint.getExemplarsCount()).isEqualTo(1); + } } diff --git a/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/Metrics.java b/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/Metrics.java new file mode 100644 index 0000000000000..7448563dbd3af --- /dev/null +++ b/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/Metrics.java @@ -0,0 +1,19 @@ +package io.quarkus.it.opentelemetry.vertx.exporter; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; + +public final class Metrics { + + private final List metricRequests = new CopyOnWriteArrayList<>(); + + public List getMetricRequests() { + return metricRequests; + } + + public void reset() { + metricRequests.clear(); + } +} diff --git a/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/OtelCollectorLifecycleManager.java b/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/OtelCollectorLifecycleManager.java index 51b2900a7a428..4cef96d6ab352 100644 --- a/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/OtelCollectorLifecycleManager.java +++ b/integration-tests/opentelemetry-vertx-exporter/src/test/java/io/quarkus/it/opentelemetry/vertx/exporter/OtelCollectorLifecycleManager.java @@ -1,6 +1,6 @@ package io.quarkus.it.opentelemetry.vertx.exporter; -import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterTracesConfig.Protocol.GRPC; +import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterConfig.Protocol.GRPC; import static org.testcontainers.Testcontainers.exposeHostPorts; import java.util.HashMap; @@ -18,6 +18,7 @@ import com.google.protobuf.InvalidProtocolBufferException; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; @@ -41,7 +42,9 @@ public class OtelCollectorLifecycleManager implements QuarkusTestResourceLifecyc private static final Integer COLLECTOR_HEALTH_CHECK_PORT = 13133; private static final ServiceName TRACE_SERVICE_NAME = ServiceName .create("opentelemetry.proto.collector.trace.v1.TraceService"); - private static final String TRACE_METHOD_NAME = "Export"; + private static final ServiceName METRIC_SERVICE_NAME = ServiceName + .create("opentelemetry.proto.collector.metrics.v1.MetricsService"); + private static final String EXPORT_METHOD_NAME = "Export"; private SelfSignedCertificate serverTls; private SelfSignedCertificate clientTlS; @@ -56,6 +59,7 @@ public class OtelCollectorLifecycleManager implements QuarkusTestResourceLifecyc private GenericContainer collector; private Traces collectedTraces; + private Metrics collectedMetrics; @Override public void init(Map initArgs) { @@ -122,6 +126,7 @@ public Map start() { Map result = new HashMap<>(); result.put("quarkus.otel.exporter.otlp.traces.protocol", protocol); + result.put("quarkus.otel.exporter.otlp.metrics.protocol", protocol); boolean isGrpc = GRPC.equals(protocol); int secureEndpointPort = isGrpc ? COLLECTOR_OTLP_GRPC_MTLS_PORT : COLLECTOR_OTLP_HTTP_MTLS_PORT; @@ -130,18 +135,26 @@ public Map start() { if (enableTLS) { result.put("quarkus.otel.exporter.otlp.traces.endpoint", "https://" + collector.getHost() + ":" + collector.getMappedPort(secureEndpointPort)); + result.put("quarkus.otel.exporter.otlp.metrics.endpoint", + "https://" + collector.getHost() + ":" + collector.getMappedPort(secureEndpointPort)); if (!preventTrustCert) { result.put("quarkus.otel.exporter.otlp.traces.trust-cert.certs", serverTls.certificatePath()); + result.put("quarkus.otel.exporter.otlp.metrics.trust-cert.certs", serverTls.certificatePath()); } result.put("quarkus.otel.exporter.otlp.traces.key-cert.certs", clientTlS.certificatePath()); result.put("quarkus.otel.exporter.otlp.traces.key-cert.keys", clientTlS.privateKeyPath()); + result.put("quarkus.otel.exporter.otlp.metrics.key-cert.certs", clientTlS.certificatePath()); + result.put("quarkus.otel.exporter.otlp.metrics.key-cert.keys", clientTlS.privateKeyPath()); } else { result.put("quarkus.otel.exporter.otlp.traces.endpoint", "http://" + collector.getHost() + ":" + collector.getMappedPort(inSecureEndpointPort)); + result.put("quarkus.otel.exporter.otlp.metrics.endpoint", + "http://" + collector.getHost() + ":" + collector.getMappedPort(inSecureEndpointPort)); } if (enableCompression) { result.put("quarkus.otel.exporter.otlp.traces.compression", "gzip"); + result.put("quarkus.otel.exporter.otlp.metrics.compression", "gzip"); } return result; @@ -150,15 +163,17 @@ public Map start() { @Override public void inject(TestInjector testInjector) { testInjector.injectIntoFields(collectedTraces, f -> f.getType().equals(Traces.class)); + testInjector.injectIntoFields(collectedMetrics, f -> f.getType().equals(Metrics.class)); } private void setupVertxGrpcServer() { vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(1).setEventLoopPoolSize(1)); GrpcServer grpcServer = GrpcServer.server(vertx); collectedTraces = new Traces(); + collectedMetrics = new Metrics(); grpcServer.callHandler(request -> { - if (request.serviceName().equals(TRACE_SERVICE_NAME) && request.methodName().equals(TRACE_METHOD_NAME)) { + if (request.serviceName().equals(TRACE_SERVICE_NAME) && request.methodName().equals(EXPORT_METHOD_NAME)) { request.handler(message -> { try { @@ -170,12 +185,25 @@ private void setupVertxGrpcServer() { .end(); } }); + } else if (request.serviceName().equals(METRIC_SERVICE_NAME) && request.methodName().equals(EXPORT_METHOD_NAME)) { + + request.handler(message -> { + try { + collectedMetrics.getMetricRequests().add(ExportMetricsServiceRequest.parseFrom(message.getBytes())); + request.response().end(Buffer.buffer(ExportMetricsServiceRequest.getDefaultInstance().toByteArray())); + } catch (InvalidProtocolBufferException e) { + request.response() + .status(GrpcStatus.INVALID_ARGUMENT) + .end(); + } + }); } else { request.response() .status(GrpcStatus.NOT_FOUND) .end(); } }); + server = vertx.createHttpServer(new HttpServerOptions().setPort(0)); try { server.requestHandler(grpcServer).listen().toCompletionStage().toCompletableFuture().get(20, TimeUnit.SECONDS); diff --git a/integration-tests/opentelemetry-vertx-exporter/src/test/resources/otel-config.yaml b/integration-tests/opentelemetry-vertx-exporter/src/test/resources/otel-config.yaml index bab60e41819c7..427a292121c46 100644 --- a/integration-tests/opentelemetry-vertx-exporter/src/test/resources/otel-config.yaml +++ b/integration-tests/opentelemetry-vertx-exporter/src/test/resources/otel-config.yaml @@ -32,9 +32,9 @@ exporters: service: extensions: [health_check] pipelines: -# metrics: -# receivers: [otlp, otlp/mtls] -# exporters: [logging, otlp] + metrics: + receivers: [otlp, otlp/mtls] + exporters: [logging, otlp] traces: receivers: [otlp, otlp/mtls] exporters: [logging, otlp] diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/AbstractEndUserTest.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/AbstractEndUserTest.java index 410188105c27d..ba68d9ce78826 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/AbstractEndUserTest.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/AbstractEndUserTest.java @@ -22,7 +22,7 @@ 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.opentelemetry.runtime.exporter.otlp.tracing.EndUserSpanProcessor; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.security.TestSecurity;