From cd3707336d983024059b481549e1714b95ca174c Mon Sep 17 00:00:00 2001 From: brunobat Date: Wed, 2 Oct 2024 17:34:10 +0100 Subject: [PATCH] OTel metrics for Microprofile 2.0 --- .../main/asciidoc/opentelemetry-metrics.adoc | 18 +- docs/src/main/asciidoc/opentelemetry.adoc | 5 - .../deployment/OpenTelemetryProcessor.java | 5 +- .../deployment/metric/MetricProcessor.java | 10 ++ .../OpenTelemetryDisabledSdkTest.java | 12 +- .../exporter/InMemoryMetricExporter.java | 9 + .../metrics/HttpServerMetricsTest.java | 109 ++++++++++++ .../metrics/JvmMetricsServiceTest.java | 161 ++++++++++++++++++ extensions/opentelemetry/runtime/pom.xml | 4 + .../runtime/InstrumentRuntimeConfig.java | 11 ++ .../instrumentation/JvmMetricsService.java | 57 +++++++ .../InstrumentationRecorder.java | 5 +- .../vertx/HttpInstrumenterVertxTracer.java | 30 ++-- .../src/main/resources/application.properties | 3 +- .../vertx/exporter/AbstractExporterTest.java | 59 ++++--- .../src/main/resources/application.properties | 2 + .../quarkus/it/opentelemetry/MetricsTest.java | 51 +++++- tcks/microprofile-opentelemetry/pom.xml | 111 +++++++++++- .../tck/opentelemetry/RedirectOutHandler.java | 47 +++++ .../tck/opentelemetry/TestApplication.java | 29 ++++ tcks/pom.xml | 2 +- 21 files changed, 675 insertions(+), 65 deletions(-) create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/HttpServerMetricsTest.java create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/JvmMetricsServiceTest.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/instrumentation/JvmMetricsService.java create mode 100644 tcks/microprofile-opentelemetry/src/test/java/io/quarkus/tck/opentelemetry/RedirectOutHandler.java diff --git a/docs/src/main/asciidoc/opentelemetry-metrics.adoc b/docs/src/main/asciidoc/opentelemetry-metrics.adoc index 78afc9c7e754d..47be8933cb0cf 100644 --- a/docs/src/main/asciidoc/opentelemetry-metrics.adoc +++ b/docs/src/main/asciidoc/opentelemetry-metrics.adoc @@ -529,9 +529,23 @@ We only show 1 of 3 exemplars for brevity. === Resource See the main xref:opentelemetry.adoc#resource[OpenTelemetry Guide resources] section. -== Additional instrumentation +== Automatic instrumentation + +We provide automatic instrumentation JVM Metrics and HTTP server requests according to the https://github.com/eclipse/microprofile-telemetry/blob/2.0/spec/src/main/asciidoc/metrics.adoc[Microprofile Metrics 2.0 specification]. + +These metrics can be disabled by setting the following properties to `false`: + +[source,properties] +---- +quarkus.otel.instrument.jvm-metrics=false +quarkus.otel.instrument.http-server-metrics=false +---- + +[NOTE] +==== +- It is recommended to disable these instrumentations if you are using the Micrometer extension as well. +==== -Automatic metrics are not yet provided by the Quarkus OpenTelemetry extension. We plan to bridge the existing Quarkus Micrometer extension metrics to OpenTelemetry in the future. == Exporters diff --git a/docs/src/main/asciidoc/opentelemetry.adoc b/docs/src/main/asciidoc/opentelemetry.adoc index aea471d1c878d..21ce9a0ed30fe 100644 --- a/docs/src/main/asciidoc/opentelemetry.adoc +++ b/docs/src/main/asciidoc/opentelemetry.adoc @@ -58,11 +58,6 @@ quarkus.otel.metrics.enabled=true ---- At build time on your `application.properties` file. -==== Manual instrumentation only -For now only manual instrumentation is supported. You can use the OpenTelemetry API to create and record metrics but Quarkus is not yet providing automatic instrumentation for metrics. - -In the future, we plan to bridge current Micrometer metrics to OpenTelemetry and maintain compatibility when possible. - === xref:opentelemetry-logging.adoc[OpenTelemetry Logging Guide] ==== Enable Logs 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 dc317d03c9da6..5d34b1c7c4e69 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 @@ -274,7 +274,7 @@ void createOpenTelemetry( @BuildStep @Record(ExecutionTime.RUNTIME_INIT) void setupVertx(InstrumentationRecorder recorder, BeanContainerBuildItem beanContainerBuildItem, - Capabilities capabilities) { + Capabilities capabilities, OTelBuildConfig config) { boolean sqlClientAvailable = capabilities.isPresent(Capability.REACTIVE_DB2_CLIENT) || capabilities.isPresent(Capability.REACTIVE_MSSQL_CLIENT) || capabilities.isPresent(Capability.REACTIVE_MYSQL_CLIENT) @@ -283,7 +283,8 @@ void setupVertx(InstrumentationRecorder recorder, BeanContainerBuildItem beanCon boolean redisClientAvailable = capabilities.isPresent(Capability.REDIS_CLIENT); recorder.setupVertxTracer(beanContainerBuildItem.getValue(), sqlClientAvailable, - redisClientAvailable); + redisClientAvailable, + config); } @BuildStep 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 index e4af5ce8cfeae..1224d28e0c2c8 100644 --- 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 @@ -22,8 +22,10 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem; import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig; import io.quarkus.opentelemetry.runtime.metrics.cdi.MetricsProducer; +import io.quarkus.opentelemetry.runtime.metrics.instrumentation.JvmMetricsService; @BuildSteps(onlyIf = MetricProcessor.MetricEnabled.class) public class MetricProcessor { @@ -39,6 +41,7 @@ UnremovableBeanBuildItem ensureProducersAreRetained( additionalBeans.produce(AdditionalBeanBuildItem.builder() .setUnremovable() .addBeanClass(MetricsProducer.class) + .addBeanClass(JvmMetricsService.class) .build()); IndexView index = indexBuildItem.getIndex(); @@ -84,6 +87,13 @@ UnremovableBeanBuildItem ensureProducersAreRetained( return new UnremovableBeanBuildItem(new UnremovableBeanBuildItem.BeanClassNamesExclusion(retainProducers)); } + @BuildStep + void runtimeInit(BuildProducer runtimeReinitialized) { + runtimeReinitialized.produce( + new RuntimeReinitializedClassBuildItem( + "io.opentelemetry.instrumentation.runtimemetrics.java8.internal.CpuMethods")); + } + public static class MetricEnabled implements BooleanSupplier { OTelBuildConfig otelBuildConfig; 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 015bff58543fc..a547f90bef18f 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 @@ -10,6 +10,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig; import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig; import io.quarkus.opentelemetry.runtime.exporter.otlp.tracing.LateBoundBatchSpanProcessor; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.HttpInstrumenterVertxTracer; @@ -33,6 +34,9 @@ public class OpenTelemetryDisabledSdkTest { @Inject OTelRuntimeConfig runtimeConfig; + @Inject + OTelBuildConfig buildConfig; + @Test void testNoTracer() { // The OTel API doesn't provide a clear way to check if a tracer is an effective NOOP tracer. @@ -41,7 +45,7 @@ void testNoTracer() { @Test void noReceiveRequestInstrumenter() { - HttpInstrumenterVertxTracer instrumenter = new HttpInstrumenterVertxTracer(openTelemetry, runtimeConfig); + HttpInstrumenterVertxTracer instrumenter = new HttpInstrumenterVertxTracer(openTelemetry, runtimeConfig, buildConfig); Instrumenter receiveRequestInstrumenter = instrumenter.getReceiveRequestInstrumenter(); assertFalse(receiveRequestInstrumenter.shouldStart(null, null), @@ -50,7 +54,7 @@ void noReceiveRequestInstrumenter() { @Test void noReceiveResponseInstrumenter() { - HttpInstrumenterVertxTracer instrumenter = new HttpInstrumenterVertxTracer(openTelemetry, runtimeConfig); + HttpInstrumenterVertxTracer instrumenter = new HttpInstrumenterVertxTracer(openTelemetry, runtimeConfig, buildConfig); Instrumenter receiveRequestInstrumenter = instrumenter.getReceiveResponseInstrumenter(); assertFalse(receiveRequestInstrumenter.shouldStart(null, null), @@ -59,7 +63,7 @@ void noReceiveResponseInstrumenter() { @Test void noSendRequestInstrumenter() { - HttpInstrumenterVertxTracer instrumenter = new HttpInstrumenterVertxTracer(openTelemetry, runtimeConfig); + HttpInstrumenterVertxTracer instrumenter = new HttpInstrumenterVertxTracer(openTelemetry, runtimeConfig, buildConfig); Instrumenter receiveRequestInstrumenter = instrumenter.getSendRequestInstrumenter(); assertFalse(receiveRequestInstrumenter.shouldStart(null, null), @@ -68,7 +72,7 @@ void noSendRequestInstrumenter() { @Test void noSendResponseInstrumenter() { - HttpInstrumenterVertxTracer instrumenter = new HttpInstrumenterVertxTracer(openTelemetry, runtimeConfig); + HttpInstrumenterVertxTracer instrumenter = new HttpInstrumenterVertxTracer(openTelemetry, runtimeConfig, buildConfig); Instrumenter receiveRequestInstrumenter = instrumenter.getSendResponseInstrumenter(); assertFalse(receiveRequestInstrumenter.shouldStart(null, null), diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/exporter/InMemoryMetricExporter.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/exporter/InMemoryMetricExporter.java index 64774ae5097b9..5acbbb3993cd5 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/exporter/InMemoryMetricExporter.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/common/exporter/InMemoryMetricExporter.java @@ -124,6 +124,15 @@ public void assertCountAtLeast(final String name, final String target, final int .untilAsserted(() -> Assertions.assertTrue(count < getFinishedMetricItems(name, target).size())); } + public void assertCountPointsAtLeast(final String name, final String target, final int countPoints) { + Awaitility.await().atMost(5, SECONDS) + .untilAsserted(() -> { + List metricData = getFinishedMetricItems(name, target); + Assertions.assertTrue(1 <= metricData.size()); + Assertions.assertTrue(countPoints <= metricData.get(0).getData().getPoints().size()); + }); + } + /** * Returns a {@code List} of the finished {@code Metric}s, represented by {@code MetricData}. * diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/HttpServerMetricsTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/HttpServerMetricsTest.java new file mode 100644 index 0000000000000..31da620a25891 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/HttpServerMetricsTest.java @@ -0,0 +1,109 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE; +import static io.opentelemetry.semconv.UrlAttributes.URL_SCHEME; +import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static org.hamcrest.Matchers.is; + +import java.net.URL; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +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.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; +import io.restassured.RestAssured; + +public class HttpServerMetricsTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.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.metrics.enabled=true\n" + + "quarkus.otel.traces.exporter=none\n" + + "quarkus.otel.logs.exporter=none\n" + + "quarkus.otel.metrics.exporter=in-memory\n" + + "quarkus.otel.metric.export.interval=300ms\n"), + "application.properties")); + + @Inject + protected InMemoryMetricExporter metricExporter; + + @TestHTTPResource + URL url; + + @AfterEach + void tearDown() { + metricExporter.reset(); + } + + @Test + void collectsHttpRouteFromEndAttributes() { + RestAssured.when() + .get("/span").then() + .statusCode(200) + .body(is("hello")); + + RestAssured.when() + .get("/fail").then() + .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()); + + metricExporter.assertCountPointsAtLeast("http.server.request.duration", null, 2); + MetricData metric = metricExporter.getFinishedMetricItems("http.server.request.duration", null).get(0); + + assertThat(metric) + .hasName("http.server.request.duration") + .hasDescription("Duration of HTTP server requests.") + .hasUnit("s") + .hasHistogramSatisfying(histogram -> histogram.isCumulative() + .hasPointsSatisfying( + point -> point.hasCount(1) + .hasAttributesSatisfying( + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(URL_SCHEME, "http"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + equalTo(HTTP_ROUTE, url.getPath() + "span")), + point -> point.hasCount(1) + .hasAttributesSatisfying( + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(URL_SCHEME, "http"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 500), + equalTo(HTTP_ROUTE, url.getPath() + "fail")))); + } + + @Path("/") + public static class SpanResource { + @GET + @Path("/span") + public Response span() { + return Response.ok("hello").build(); + } + + @GET + @Path("/fail") + public Response fail() { + return Response.status(INTERNAL_SERVER_ERROR).build(); + } + } +} diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/JvmMetricsServiceTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/JvmMetricsServiceTest.java new file mode 100644 index 0000000000000..b156a5c79c519 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/metrics/JvmMetricsServiceTest.java @@ -0,0 +1,161 @@ +package io.quarkus.opentelemetry.deployment.metrics; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.io.IOException; + +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.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.data.MetricDataType; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporter; +import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class JvmMetricsServiceTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.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.metrics.enabled=true\n" + + "quarkus.otel.traces.exporter=none\n" + + "quarkus.otel.logs.exporter=none\n" + + "quarkus.otel.metrics.exporter=in-memory\n" + + "quarkus.otel.metric.export.interval=300ms\n"), + "application.properties")); + + @Inject + protected InMemoryMetricExporter metricExporter; + + // No need to reset between tests. Data is test independent. Will also run faster. + + @Test + void testClassLoadedMetrics() throws IOException { + assertMetric("jvm.class.loaded", "Number of classes loaded since JVM start.", "{class}", + MetricDataType.LONG_SUM); + } + + @Test + void testClassUnloadedMetrics() throws IOException { + assertMetric("jvm.class.unloaded", "Number of classes unloaded since JVM start.", + "{class}", MetricDataType.LONG_SUM); + } + + @Test + void testClassCountMetrics() { + assertMetric("jvm.class.count", "Number of classes currently loaded.", + "{class}", MetricDataType.LONG_SUM); + } + + @Test + void testCpuTimeMetric() throws IOException { + assertMetric("jvm.cpu.time", "CPU time used by the process as reported by the JVM.", "s", + MetricDataType.DOUBLE_SUM); + } + + @Test + void testCpuCountMetric() throws IOException { + assertMetric("jvm.cpu.count", + "Number of processors available to the Java virtual machine.", "{cpu}", + MetricDataType.LONG_SUM); + } + + @Test + void testCpuRecentUtilizationMetric() throws IOException { + assertMetric("jvm.cpu.recent_utilization", + "Recent CPU utilization for the process as reported by the JVM.", "1", + MetricDataType.DOUBLE_GAUGE); + } + + @Test + void testGarbageCollectionCountMetric() { + System.gc(); + assertMetric("jvm.gc.duration", "Duration of JVM garbage collection actions.", "s", + MetricDataType.HISTOGRAM); + } + + @Test + void testJvmMemoryUsedMetric() throws IOException { + assertMetric("jvm.memory.used", "Measure of memory used.", "By", + MetricDataType.LONG_SUM); + } + + @Test + void testJvmMemoryCommittedMetric() throws IOException { + assertMetric("jvm.memory.committed", "Measure of memory committed.", "By", + MetricDataType.LONG_SUM); + } + + @Test + void testMemoryLimitMetric() throws IOException { + assertMetric("jvm.memory.limit", "Measure of max obtainable memory.", "By", + MetricDataType.LONG_SUM); + } + + @Test + void testMemoryUsedAfterLastGcMetric() throws IOException { + assertMetric("jvm.memory.used_after_last_gc", + "Measure of memory used, as measured after the most recent garbage collection event on this pool.", + "By", + MetricDataType.LONG_SUM); + } + + @Test + void testThreadCountMetric() throws IOException { + assertMetric("jvm.thread.count", "Number of executing platform threads.", "{thread}", + MetricDataType.LONG_SUM); + } + + private void assertMetric(final String metricName, + final String metricDescription, final String metricUnit, + final MetricDataType metricType) { + + metricExporter.assertCountAtLeast(metricName, null, 1); + MetricData metric = metricExporter.getFinishedMetricItems(metricName, null).get(0); + + assertThat(metric).isNotNull(); + assertThat(metric.getName()).isEqualTo(metricName); + assertThat(metric.getDescription()).isEqualTo(metricDescription); + assertThat(metric.getType()).isEqualTo(metricType); + assertThat(metric.getUnit()).isEqualTo(metricUnit); + + // only one of them will be present per test + metric.getDoubleSumData().getPoints().stream() + .forEach(point -> { + assertThat(point.getValue()) + .withFailMessage("Double" + point.getValue() + " was not an expected result") + .isGreaterThan(0); + }); + + metric.getLongSumData().getPoints().stream() + .forEach(point -> { + assertThat(point.getValue()) + .withFailMessage("Long" + point.getValue() + " was not an expected result") + .isGreaterThanOrEqualTo(0); + }); + + metric.getDoubleGaugeData().getPoints().stream() + .forEach(point -> { + assertThat(point.getValue()) + .withFailMessage("Double" + point.getValue() + " was not an expected result") + .isGreaterThanOrEqualTo(0); + }); + + metric.getHistogramData().getPoints().stream() + .forEach(point -> { + assertThat(point.hasMin()).isTrue(); + assertThat(point.hasMax()).isTrue(); + assertThat(point.getCounts().size()).isGreaterThan(0); + }); + } +} diff --git a/extensions/opentelemetry/runtime/pom.xml b/extensions/opentelemetry/runtime/pom.xml index e6abd62301dec..ce2d2b9b1a57d 100644 --- a/extensions/opentelemetry/runtime/pom.xml +++ b/extensions/opentelemetry/runtime/pom.xml @@ -160,6 +160,10 @@ + + io.opentelemetry.instrumentation + opentelemetry-runtime-telemetry-java17 + io.vertx vertx-grpc-client diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/InstrumentRuntimeConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/InstrumentRuntimeConfig.java index c781da7e17717..6869c39ed1c95 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/InstrumentRuntimeConfig.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/runtime/InstrumentRuntimeConfig.java @@ -30,4 +30,15 @@ public interface InstrumentRuntimeConfig { @WithDefault("true") boolean vertxRedisClient(); + /** + * Enables instrumentation for JVM Metrics. + */ + @WithDefault("true") + boolean jvmMetrics(); + + /** + * Enables instrumentation for HTTP Server Metrics. + */ + @WithDefault("true") + boolean httpServerMetrics(); } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/instrumentation/JvmMetricsService.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/instrumentation/JvmMetricsService.java new file mode 100644 index 0000000000000..30750a4fe948b --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/metrics/instrumentation/JvmMetricsService.java @@ -0,0 +1,57 @@ +package io.quarkus.opentelemetry.runtime.metrics.instrumentation; + +import static io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature.CLASS_LOAD_METRICS; +import static io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature.CONTEXT_SWITCH_METRICS; +import static io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature.CPU_COUNT_METRICS; +import static io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature.CPU_UTILIZATION_METRICS; +import static io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature.GC_DURATION_METRICS; +import static io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature.LOCK_METRICS; +import static io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature.MEMORY_ALLOCATION_METRICS; +import static io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature.MEMORY_POOL_METRICS; +import static io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature.NETWORK_IO_METRICS; +import static io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature.THREAD_METRICS; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.ApplicationScoped; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; +import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig; +import io.quarkus.runtime.Startup; + +@Startup +@ApplicationScoped +public class JvmMetricsService { + + private final RuntimeMetrics runtimeMetrics; + + public JvmMetricsService(final OpenTelemetry openTelemetry, final OTelRuntimeConfig runtimeConfig) { + + if (runtimeConfig.sdkDisabled() || !runtimeConfig.instrument().jvmMetrics()) { + runtimeMetrics = RuntimeMetrics.builder(openTelemetry).disableAllMetrics().build(); + return; + } + + // Will only produce mandatory metrics for MP Telemetry 2.0 + runtimeMetrics = RuntimeMetrics.builder(openTelemetry) + .disableFeature(CONTEXT_SWITCH_METRICS) + .disableFeature(CPU_COUNT_METRICS) + .disableFeature(LOCK_METRICS) + .disableFeature(MEMORY_ALLOCATION_METRICS) + .disableFeature(NETWORK_IO_METRICS) + .enableFeature(MEMORY_POOL_METRICS) + .enableFeature(GC_DURATION_METRICS) + .enableFeature(THREAD_METRICS) + .enableFeature(CLASS_LOAD_METRICS) + .enableFeature(CPU_UTILIZATION_METRICS) + .build(); + } + + @PreDestroy + public void close() { + if (runtimeMetrics != null) { + runtimeMetrics.close(); + } + } + +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java index 3b846e98189cf..abef2653061ee 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java @@ -6,6 +6,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig; import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.EventBusInstrumenterVertxTracer; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.HttpInstrumenterVertxTracer; @@ -48,7 +49,7 @@ public Consumer getVertxTracingOptions() { /* RUNTIME INIT */ @RuntimeInit public void setupVertxTracer(BeanContainer beanContainer, boolean sqlClientAvailable, - boolean redisClientAvailable) { + boolean redisClientAvailable, OTelBuildConfig buildConfig) { if (config.getValue().sdkDisabled()) { return; @@ -57,7 +58,7 @@ public void setupVertxTracer(BeanContainer beanContainer, boolean sqlClientAvail OpenTelemetry openTelemetry = beanContainer.beanInstance(OpenTelemetry.class); List> tracers = new ArrayList<>(4); if (config.getValue().instrument().vertxHttp()) { - tracers.add(new HttpInstrumenterVertxTracer(openTelemetry, config.getValue())); + tracers.add(new HttpInstrumenterVertxTracer(openTelemetry, config.getValue(), buildConfig)); } if (config.getValue().instrument().vertxEventBus()) { tracers.add(new EventBusInstrumenterVertxTracer(openTelemetry, config.getValue())); diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java index 2d1d7c68a1273..30969d9a8a4b3 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java @@ -26,10 +26,12 @@ import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter; import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractor; import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter; +import io.opentelemetry.instrumentation.api.semconv.http.HttpServerMetrics; import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute; import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteBiGetter; import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor; +import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig; import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig; import io.smallrye.common.vertx.VertxContext; import io.vertx.core.Context; @@ -52,8 +54,10 @@ public class HttpInstrumenterVertxTracer implements InstrumenterVertxTracer serverInstrumenter; private final Instrumenter clientInstrumenter; - public HttpInstrumenterVertxTracer(final OpenTelemetry openTelemetry, final OTelRuntimeConfig runtimeConfig) { - serverInstrumenter = getServerInstrumenter(openTelemetry, runtimeConfig); + public HttpInstrumenterVertxTracer(final OpenTelemetry openTelemetry, + final OTelRuntimeConfig runtimeConfig, + final OTelBuildConfig buildConfig) { + serverInstrumenter = getServerInstrumenter(openTelemetry, runtimeConfig, buildConfig); clientInstrumenter = getClientInstrumenter(openTelemetry, runtimeConfig); } @@ -138,23 +142,29 @@ public HttpRequest writableHeaders( } static Instrumenter getServerInstrumenter(final OpenTelemetry openTelemetry, - final OTelRuntimeConfig runtimeConfig) { - ServerAttributesExtractor serverAttributesExtractor = new ServerAttributesExtractor(); + final OTelRuntimeConfig runtimeConfig, final OTelBuildConfig buildConfig) { + final ServerAttributesExtractor serverAttributesExtractor = new ServerAttributesExtractor(); - InstrumenterBuilder serverBuilder = Instrumenter.builder( + final InstrumenterBuilder serverBuilder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, HttpSpanNameExtractor.create(serverAttributesExtractor)); - serverBuilder.setEnabled(!runtimeConfig.sdkDisabled()); - - return serverBuilder + serverBuilder + .setEnabled(!runtimeConfig.sdkDisabled()) .setSpanStatusExtractor(HttpSpanStatusExtractor.create(serverAttributesExtractor)) .addAttributesExtractor( HttpServerAttributesExtractor.create(serverAttributesExtractor)) .addAttributesExtractor(new AdditionalServerAttributesExtractor()) - .addContextCustomizer(HttpServerRoute.create(serverAttributesExtractor)) - .buildServerInstrumenter(new HttpRequestTextMapGetter()); + .addContextCustomizer(HttpServerRoute.create(serverAttributesExtractor)); + + if (buildConfig.metrics().enabled().orElse(false) && + !runtimeConfig.sdkDisabled() && + runtimeConfig.instrument().httpServerMetrics()) { + serverBuilder.addOperationMetrics(HttpServerMetrics.get()); + } + + return serverBuilder.buildServerInstrumenter(new HttpRequestTextMapGetter()); } static Instrumenter getClientInstrumenter(final OpenTelemetry openTelemetry, 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 5eaae9ff6ecb6..9f5f424764083 100644 --- a/integration-tests/opentelemetry-vertx-exporter/src/main/resources/application.properties +++ b/integration-tests/opentelemetry-vertx-exporter/src/main/resources/application.properties @@ -1,5 +1,4 @@ quarkus.application.name=integration test quarkus.otel.metrics.enabled=true quarkus.otel.metric.export.interval=1000ms -quarkus.otel.logs.enabled=true -quarkus.log.handler.open-telemetry.enabled=true \ No newline at end of file +quarkus.otel.logs.enabled=true \ 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 0775897b4c4c4..1ba0b74775661 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 @@ -26,7 +26,6 @@ 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; @@ -99,32 +98,9 @@ private void verifyTraces() { } private void verifyMetrics() { - List metricRequests = metrics.getMetricRequests(); - await() - .atMost(Duration.ofSeconds(30)) - .untilAsserted(() -> assertThat(metricRequests).hasSizeGreaterThan(1)); - ExportMetricsServiceRequest request = metricRequests.get(metricRequests.size() - 1); - 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(3); - - 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(); + Metric customMetric = getMetric("hello"); + assertThat(customMetric.getDataCase()).isEqualTo(Metric.DataCase.SUM); + Sum sum = customMetric.getSum(); assertThat(sum.getAggregationTemporality()) .isEqualTo(AggregationTemporality.AGGREGATION_TEMPORALITY_CUMULATIVE); assertThat(sum.getDataPointsCount()).isEqualTo(1); @@ -138,6 +114,35 @@ private void verifyMetrics() { .setKey("key") .setValue(AnyValue.newBuilder().setStringValue("value").build()) .build())); + + Metric requestDuration = getMetric("http.server.request.duration"); + assertThat(requestDuration.getDataCase()).isEqualTo(Metric.DataCase.HISTOGRAM); + requestDuration.getHistogram().getDataPointsList().stream() + .forEach(point -> { + assertThat(point.getCount()).isGreaterThan(0); + }); + + Metric jvmMetric = getMetric("jvm.class.loaded"); + assertThat(jvmMetric.getDataCase()).isEqualTo(Metric.DataCase.SUM); + assertThat(jvmMetric.getSum().getDataPointsCount()).isGreaterThan(0); + } + + private Metric getMetric(final String metricName) { + await() + .atMost(Duration.ofSeconds(30)) + .untilAsserted(() -> { + List reqs = metrics.getMetricRequests(); + assertThat(reqs).hasSizeGreaterThan(1); + }); + + final List metricRequests = metrics.getMetricRequests(); + + return metricRequests.stream() + .flatMap(reqs -> reqs.getResourceMetricsList().stream()) + .flatMap(resourceMetrics -> resourceMetrics.getScopeMetricsList().stream()) + .flatMap(libraryMetrics -> libraryMetrics.getMetricsList().stream()) + .filter(metric -> metric.getName().equals(metricName)) + .findFirst().get(); } private void verifyLogs() { diff --git a/integration-tests/opentelemetry/src/main/resources/application.properties b/integration-tests/opentelemetry/src/main/resources/application.properties index 3df96e1b77b03..68c3b9664276b 100644 --- a/integration-tests/opentelemetry/src/main/resources/application.properties +++ b/integration-tests/opentelemetry/src/main/resources/application.properties @@ -20,3 +20,5 @@ quarkus.security.users.embedded.users.scott=reader quarkus.security.users.embedded.plain-text=true quarkus.security.users.embedded.enabled=true quarkus.http.auth.basic=true + +quarkus.native.monitoring=jfr diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/MetricsTest.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/MetricsTest.java index 8dfe4fb3fea5f..c1bf312faea98 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/MetricsTest.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/MetricsTest.java @@ -5,6 +5,9 @@ import static java.net.HttpURLConnection.HTTP_OK; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; @@ -30,10 +33,10 @@ private List> getSpans() { }); } - private List> getMetrics() { + private List> getMetrics(String metricName) { return given() .when() - .queryParam("name", "direct-trace-counter") + .queryParam("name", metricName) .get("/export/metrics") .body().as(new TypeRef<>() { }); @@ -52,10 +55,10 @@ public void directCounterTest() { .statusCode(200); await().atMost(5, SECONDS).until(() -> getSpans().size() == 2); - await().atMost(10, SECONDS).until(() -> getMetrics().size() > 2); + await().atMost(10, SECONDS).until(() -> getMetrics("direct-trace-counter").size() > 2); - List> metrics = getMetrics(); - Integer value = (Integer) ((Map) ((List) ((Map) (getMetrics() + List> metrics = getMetrics("direct-trace-counter"); + Integer value = (Integer) ((Map) ((List) ((Map) (getMetrics("direct-trace-counter") .get(metrics.size() - 1) .get("longSumData"))) .get("points")) @@ -68,4 +71,42 @@ public void directCounterTest() { .body().as(new TypeRef<>() { })); } + + @Test + void testJvmMetrics() { + await().atMost(10, SECONDS).until(() -> getMetrics("jvm.thread.count").size() > 2); + + List> metrics = getMetrics("jvm.thread.count"); + + Integer value = (Integer) ((Map) ((List) ((Map) (getMetrics("jvm.thread.count") + .get(metrics.size() - 1) + .get("longSumData"))) + .get("points")) + .get(0)) + .get("value"); + + assertThat(value, greaterThan(0)); // at least one thread is running + } + + @Test + void testServerRequestDuration() { + given() + .when() + .get("/nopath") + .then() + .statusCode(200); + + await().atMost(10, SECONDS).until(() -> getMetrics("http.server.request.duration").size() > 2); + + List> metrics = getMetrics("http.server.request.duration"); + + Integer value = (Integer) ((Map) ((List) ((Map) (getMetrics("http.server.request.duration") + .get(metrics.size() - 1) + .get("data"))) + .get("points")) + .get(0)) + .get("count"); + + assertThat(value, greaterThanOrEqualTo(1)); // at least one endpoint was called once + } } diff --git a/tcks/microprofile-opentelemetry/pom.xml b/tcks/microprofile-opentelemetry/pom.xml index 23f9bc946a5a9..7ccc5fe8c206e 100644 --- a/tcks/microprofile-opentelemetry/pom.xml +++ b/tcks/microprofile-opentelemetry/pom.xml @@ -13,7 +13,7 @@ Quarkus - TCK - MicroProfile OpenTelemetry - 1.1 + 2.0 @@ -27,11 +27,98 @@ true true - - org.eclipse.microprofile.telemetry.tracing:microprofile-telemetry-tracing-tck - src/test/resources/exclusions.txt + + + tracing + + test + + + false + ${project.build.directory}/surefire-reports-tracing + + org.eclipse.microprofile.telemetry:microprofile-telemetry-tracing-tck + + + + + metrics + + test + + + false + ${project.build.directory}/surefire-reports-metrics + + none + true + 1000 + + + org.eclipse.microprofile.telemetry:microprofile-telemetry-metrics-tck + + + **/application/**/*Test.java + + + + + metrics-jvm + + test + + + false + ${project.build.directory}/surefire-reports-metrics-jvm + + false + none + true + logging + 1000 + true + ${project.build.directory}/out.log + ${project.build.directory}/out.log + + + org.eclipse.microprofile.telemetry:microprofile-telemetry-metrics-tck + + + **/jvm/*Test.java + + + + + logs + + test + + + false + ${project.build.directory}/surefire-reports-logs + + false + false + false + true + logging + + + 100 + + ${project.build.directory}/out.log + ${project.build.directory}/out.log + + + + org.eclipse.microprofile.telemetry:microprofile-telemetry-logs-tck + + + + + @@ -54,7 +141,7 @@ quarkus-resteasy-client - org.eclipse.microprofile.telemetry.tracing + org.eclipse.microprofile.telemetry microprofile-telemetry-tracing-tck ${microprofile-opentelemetry-tck.version} @@ -64,6 +151,20 @@ + + org.eclipse.microprofile.telemetry + microprofile-telemetry-metrics-tck + ${microprofile-opentelemetry-tck.version} + + + org.eclipse.microprofile.telemetry + microprofile-telemetry-logs-tck + ${microprofile-opentelemetry-tck.version} + + + io.opentelemetry + opentelemetry-exporter-logging + diff --git a/tcks/microprofile-opentelemetry/src/test/java/io/quarkus/tck/opentelemetry/RedirectOutHandler.java b/tcks/microprofile-opentelemetry/src/test/java/io/quarkus/tck/opentelemetry/RedirectOutHandler.java new file mode 100644 index 0000000000000..eed4c371617bd --- /dev/null +++ b/tcks/microprofile-opentelemetry/src/test/java/io/quarkus/tck/opentelemetry/RedirectOutHandler.java @@ -0,0 +1,47 @@ +package io.quarkus.tck.opentelemetry; + +import java.io.PrintStream; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +public class RedirectOutHandler extends Handler { + private final PrintStream oldOut; + private final PrintStream newOut; + + public RedirectOutHandler(final String pattern) throws Exception { + if (pattern == null) { + throw new IllegalArgumentException("file path pattern must not be null"); + } + this.oldOut = System.out; + this.newOut = new PrintStream(pattern); + System.setOut(newOut); + } + + @Override + public void publish(final LogRecord record) { + + } + + @Override + public void flush() { + + } + + @Override + public void close() throws SecurityException { + newOut.close(); + System.setOut(oldOut); + } + + private T checkNotNullParam(String name, T value) throws IllegalArgumentException { + checkNotNullParamChecked("name", name); + checkNotNullParamChecked(name, value); + return value; + } + + private void checkNotNullParamChecked(String name, T value) { + if (value == null) { + throw new IllegalArgumentException(name + " must not be null"); + } + } +} diff --git a/tcks/microprofile-opentelemetry/src/test/java/io/quarkus/tck/opentelemetry/TestApplication.java b/tcks/microprofile-opentelemetry/src/test/java/io/quarkus/tck/opentelemetry/TestApplication.java index b1f5e34a21027..da72dc5903b98 100644 --- a/tcks/microprofile-opentelemetry/src/test/java/io/quarkus/tck/opentelemetry/TestApplication.java +++ b/tcks/microprofile-opentelemetry/src/test/java/io/quarkus/tck/opentelemetry/TestApplication.java @@ -5,8 +5,13 @@ import java.net.HttpURLConnection; import java.net.URL; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -14,6 +19,7 @@ import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.arquillian.testng.Arquillian; @@ -21,6 +27,8 @@ import org.jboss.shrinkwrap.api.spec.WebArchive; import org.testng.annotations.Test; +import io.quarkus.runtime.StartupEvent; + public class TestApplication extends Arquillian { @ArquillianResource URL url; @@ -42,6 +50,8 @@ public void rest() { public static class TestEndpoint { @Inject HelloBean helloBean; + @Inject + LogHandlerService logHandlerService; @GET public String hello() { @@ -55,4 +65,23 @@ public String hello() { return "hello"; } } + + @ApplicationScoped + public static class LogHandlerService { + + @Inject + @ConfigProperty(name = "quarkus.log.file.path", defaultValue = "target/out.log") + String pathPattern; + + void onStart(@Observes StartupEvent ev) { + try { + Logger rootLogger = LogManager.getLogManager().getLogger(""); + Handler handler = new RedirectOutHandler(pathPattern); + handler.setLevel(Level.FINE); + LogManager.getLogManager().getLogger("").addHandler(handler); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } } diff --git a/tcks/pom.xml b/tcks/pom.xml index 24e04d682d64a..284143d1d6786 100644 --- a/tcks/pom.xml +++ b/tcks/pom.xml @@ -116,7 +116,7 @@ microprofile-rest-client microprofile-rest-client-reactive microprofile-openapi - + microprofile-opentelemetry microprofile-lra resteasy-reactive