From 8ef35a8931d849c9099b9fe2d7e15d3363b77ee1 Mon Sep 17 00:00:00 2001 From: brunobat Date: Fri, 5 Jul 2024 10:10:43 +0100 Subject: [PATCH] OTel Metrics documentation. Base OTel connection config and overrides. --- .../_includes/opentelemetry-config.adoc | 36 + docs/src/main/asciidoc/datasource.adoc | 4 +- .../main/asciidoc/opentelemetry-metrics.adoc | 502 +++++++++++++ .../main/asciidoc/opentelemetry-tracing.adoc | 622 ++++++++++++++++ docs/src/main/asciidoc/opentelemetry.adoc | 702 ++++-------------- .../deployment/OpenTelemetryProcessor.java | 3 +- .../otlp/OtlpExporterBadEndpointTest.java | 2 +- .../exporter/otlp/OtlpExporterConfigTest.java | 10 +- .../otlp/OtlpExporterOverrideConfigTest.java | 34 + ...uredOpenTelemetrySdkBuilderCustomizer.java | 27 +- ...chicalOTelConnectionConfigInterceptor.java | 92 +++ .../runtime/exporter/OtlpExporterConfig.java | 12 - .../exporter/OtlpExporterRuntimeConfig.java | 74 +- .../exporter/OtlpExporterTracesConfig.java | 15 - .../exporter/otlp/OTelExporterRecorder.java | 37 +- ...io.smallrye.config.ConfigSourceInterceptor | 1 + ...alOTelConnectionConfigInterceptorTest.java | 62 ++ .../otlp/HttpClientOptionsConsumerTest.java | 5 - .../otlp/OtlpExporterProviderTest.java | 110 ++- 19 files changed, 1709 insertions(+), 641 deletions(-) create mode 100644 docs/src/main/asciidoc/_includes/opentelemetry-config.adoc create mode 100644 docs/src/main/asciidoc/opentelemetry-metrics.adoc create mode 100644 docs/src/main/asciidoc/opentelemetry-tracing.adoc create mode 100644 extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterOverrideConfigTest.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/HierarchicalOTelConnectionConfigInterceptor.java create mode 100644 extensions/opentelemetry/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor create mode 100644 extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/config/HierarchicalOTelConnectionConfigInterceptorTest.java diff --git a/docs/src/main/asciidoc/_includes/opentelemetry-config.adoc b/docs/src/main/asciidoc/_includes/opentelemetry-config.adoc new file mode 100644 index 0000000000000..98e9507f6f8ca --- /dev/null +++ b/docs/src/main/asciidoc/_includes/opentelemetry-config.adoc @@ -0,0 +1,36 @@ +There are no mandatory configurations for the extension to work. + +By default, the exporters will send out data in batches, using the gRPC protocol and endpoint `http://localhost:4317`. + +If you need to change any of the default property values, here is an example on how to configure the default OTLP gRPC Exporter within the application, using the `src/main/resources/application.properties` file: + +[source,properties] +---- +quarkus.application.name=myservice // <1> +quarkus.otel.exporter.otlp.endpoint=http://localhost:4317 // <2> +quarkus.otel.exporter.otlp.headers=authorization=Bearer my_secret // <3> +quarkus.log.console.format=%d{HH:mm:ss} %-5p traceId=%X{traceId}, parentId=%X{parentId}, spanId=%X{spanId}, sampled=%X{sampled} [%c{2.}] (%t) %s%e%n // <4> + +# Alternative to the console log +quarkus.http.access-log.pattern="...traceId=%{X,traceId} spanId=%{X,spanId}" // <5> +---- + +<1> All telemetry created from the application will include an OpenTelemetry `Resource` attribute indicating the telemetry was created by the `myservice` application. If not set, it will default to the artifact id. +<2> gRPC endpoint to send the telemetry. If not set, it will default to `http://localhost:4317`. +<3> Optional gRPC headers commonly used for authentication +<4> Add tracing information into log messages. +<5> You can also only put the trace info into the access log. In this case you must omit the info in the console log format. + +We provide signal agnostic configurations for the connection related properties, meaning that you can use the same properties for both tracing and metrics when you set: +[source,properties] +---- +quarkus.otel.exporter.otlp.endpoint=http://localhost:4317 +---- +If you need different configurations for each signal, you can use the specific properties: +[source,properties] +---- +quarkus.otel.exporter.otlp.traces.endpoint=http://trace-uri:4317 // <1> +quarkus.otel.exporter.otlp.metrics.endpoint=http://metrics-uri:4317 // <2> +---- +<1> The endpoint for the traces exporter. +<2> The endpoint for the metrics exporter. diff --git a/docs/src/main/asciidoc/datasource.adoc b/docs/src/main/asciidoc/datasource.adoc index eaf0e84cc44b2..46d9f291a6692 100644 --- a/docs/src/main/asciidoc/datasource.adoc +++ b/docs/src/main/asciidoc/datasource.adoc @@ -638,9 +638,9 @@ If the metrics collection for this datasource is disabled, all values result in [[datasource-tracing]] === Datasource tracing -To use tracing with a datasource, you need to add the xref:opentelemetry.adoc[`quarkus-opentelemetry`] extension to your project. +To use tracing with a datasource, you need to add the xref:opentelemetry-tracing.adoc[`quarkus-opentelemetry`] extension to your project. -You don't need to declare a different driver because you need tracing. If you use a JDBC driver, you need to follow the instructions in the OpenTelemetry extension xref:opentelemetry.adoc#jdbc[here]. +You don't need to declare a different driver because you need tracing. If you use a JDBC driver, you need to follow the instructions in the OpenTelemetry extension xref:opentelemetry-tracing.adoc#jdbc[here]. Even with all the tracing infrastructure in place the datasource tracing is not enabled by default, and you need to enable it by setting this property: [source, properties] diff --git a/docs/src/main/asciidoc/opentelemetry-metrics.adoc b/docs/src/main/asciidoc/opentelemetry-metrics.adoc new file mode 100644 index 0000000000000..d2b93e98d33be --- /dev/null +++ b/docs/src/main/asciidoc/opentelemetry-metrics.adoc @@ -0,0 +1,502 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// += Using OpenTelemetry Metrics +include::_attributes.adoc[] +:categories: observability +:summary: This guide explains how your Quarkus application can utilize OpenTelemetry to provide metrics for interactive web applications. +:topics: observability,opentelemetry,metrics +:extensions: io.quarkus:quarkus-opentelemetry + +This guide explains how your Quarkus application can utilize https://opentelemetry.io/[OpenTelemetry] (OTel) to provide +metrics for interactive web applications. + +[NOTE] +==== +- The xref:opentelemetry.adoc[OpenTelemetry Guide] is available with signal independent information about the OpenTelemetry extension. +- If you search more information about OpenTelemetry Tracing, please refer to the xref:opentelemetry-tracing.adoc[OpenTelemetry Tracing Guide]. +==== + + +== Prerequisites + +:prerequisites-docker-compose: +include::{includes}/prerequisites.adoc[] + +== Architecture + +In this guide, we create a straightforward REST application to demonstrate distributed tracing. + +== Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can skip right to the completed example. + +Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. + +The solution is located in the `opentelemetry-quickstart` link:{quickstarts-tree-url}/opentelemetry-quickstart[directory]. + +== Creating the Maven project + +First, we need a new project. Create a new project with the following command: + +:create-app-artifact-id: opentelemetry-quickstart +:create-app-extensions: rest,quarkus-opentelemetry +include::{includes}/devtools/create-app.adoc[] + +This command generates the Maven project and imports the `quarkus-opentelemetry` extension, +which includes the default OpenTelemetry support, +and a gRPC span exporter for https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md[OTLP]. + +If you already have your Quarkus project configured, you can add the `quarkus-opentelemetry` extension +to your project by running the following command in your project base directory: + +:add-extension-extensions: opentelemetry +include::{includes}/devtools/extension-add.adoc[] + +This will add the following to your build file: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-opentelemetry + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.quarkus:quarkus-opentelemetry") +---- + +=== Examine the Jakarta REST resource + +Create a `src/main/java/org/acme/opentelemetry/MetricResource.java` file with the following content: + +[[metric-resource-class]] +[source,java] +---- +package org.acme; + +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.jboss.logging.Logger; + +@Path("/hello-metrics") +public class MetricResource { + + private static final Logger LOG = Logger.getLogger(MetricResource.class); + + private final LongCounter counter; + + public MetricResource(Meter meter) { <1> + counter = meter.counterBuilder("hello-metrics") <2> + .setDescription("hello-metrics") + .setUnit("invocations") + .build(); + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + counter.add(1); <3> + LOG.info("hello-metrics"); + return "hello-metrics"; + } +} +---- + +Quarkus is not currently producing metrics out of the box. Here we are creating a counter for the number of invocations of the `hello()` method. + +<1> Constructor injection of the `Meter` instance. +<2> Create a `LongCounter` named `hello-metrics` with a description and unit. +<3> Increment the counter by one for each invocation of the `hello()` method. + + +=== Create the configuration + +There are no mandatory configurations for the extension to work. + +If you need to change any of the default property values, here is an example on how to configure the default OTLP gRPC Exporter within the application, using the `src/main/resources/application.properties` file: + +[source,properties] +---- +quarkus.application.name=myservice // <1> +quarkus.otel.metrics.enabled=true // <2> +quarkus.otel.exporter.otlp.metrics.endpoint=http://localhost:4317 // <3> +quarkus.otel.exporter.otlp.metrics.headers=authorization=Bearer my_secret // <4> +---- + +<1> All metrics created from the application will include an OpenTelemetry `Resource` indicating the metrics was created by the `myservice` application. If not set, it will default to the artifact id. +<2> Enable the OpenTelemetry metrics. Must be set at build time. +<3> gRPC endpoint to send the metrics. If not set, it will default to `http://localhost:4317`. +<4> Optional gRPC headers commonly used for authentication. + +To configure the connection using the same properties for all signals, please check the base xref:opentelemetry.adoc#create-the-configuration[configuration section of the OpenTelemetry guide]. + +To disable particular parts of OpenTelemetry, you can set the properties listed in this xref:opentelemetry.adoc#disable-all-or-parts-of-the-opentelemetry-extension[section of the OpenTelemetry guide]. + +== Run the application + +First we need to start a system to visualise the OpenTelemetry data. + +=== See the data + +==== Grafana-OTel-LGTM dev service +You can use the xref:observability-devservices-lgtm.adoc[Grafana-OTel-LGTM] devservice. + +This Dev service includes a Grafana for visualizing data, Loki to store logs, Tempo to store traces and Prometheus to store metrics. Also provides and OTel collector to receive the data. + +==== Logging exporter + +You can output all metrics to the console by setting the exporter to `logging` in the `application.properties` file: +[source, properties] +---- +quarkus.otel.metrics.exporter=logging <1> +quarkus.otel.metric.export.interval=10000ms <2> +---- + +<1> Set the exporter to `logging`. Normally you don't need to set this. The default is `cdi`. +<2> Set the interval to export the metrics. The default is `1m`, which is too long for debugging. + +=== Start the application + +Now we are ready to run our application. If using `application.properties` to configure the tracer: + +include::{includes}/devtools/dev.adoc[] + +or if configuring the OTLP gRPC endpoint via JVM arguments: + +:dev-additional-parameters: -Djvm.args="-Dquarkus.otel.exporter.otlp.endpoint=http://localhost:4317" +include::{includes}/devtools/dev.adoc[] +:!dev-additional-parameters: + +With the OpenTelemetry Collector, the Jaeger system and the application running, you can make a request to the provided endpoint: + +[source,shell] +---- +$ curl http://localhost:8080/hello-metrics +hello-metrics +---- + +When using the logger exporter, metrics will be printed to the console. This is a pretty printed example: +[source,json] +---- +{ + "metric": "ImmutableMetricData", + "resource": { + "Resource": { + "schemaUrl": null, + "attributes": { <1> + "host.name": "myhost", + "service.name": "myservice ", + "service.version": "1.0.0-SNAPSHOT", + "telemetry.sdk.language": "java", + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.version": "1.32.0", + "webengine.name": "Quarkus", + "webengine.version": "999-SNAPSHOT" + } + }, + "instrumentationScopeInfo": { + "InstrumentationScopeInfo": { <2> + "name": "io.quarkus.opentelemetry", + "version": null, + "schemaUrl": null, + "attributes": {} + } + }, + "name": "hello-metrics", <3> + "description": "hello-metrics", + "unit": "invocations", + "type": "LONG_SUM", + "data": { + "ImmutableSumData": { + "points": [ + { + "ImmutableLongPointData": { + "startEpochNanos": 1720622136612378000, + "epochNanos": 1720622246618331000, + "attributes": {}, + "value": 3, <4> + "exemplars": [ <5> + { + "ImmutableLongExemplarData": { + "filteredAttributes": {}, + "epochNanos": 1720622239362357000, + "spanContext": { + "ImmutableSpanContext": { + "traceId": "d91951e50b0641552a76889c5356467c", + "spanId": "168af8b7102d0556", + "traceFlags": "01", + "traceState": "ArrayBasedTraceState", + "entries": [], + "remote": false, + "valid": true + }, + "value": 1 + } + } + } + ] + } + } + ], + "monotonic": true, + "aggregationTemporality": "CUMULATIVE" + } + } + } +} +---- +<1> Resource attributes common to all telemetry data. +<2> Instrumentation scope is allways `io.quarkus.opentelemetry` +<3> The name, description and unit of the metric you defined in the constructor of the `MetricResource` class. +<4> The value of the metric. 3 invocations were made until now. +<5> Exemplars additional tracing information about the metric. In this case, the traceId and spanId of one os the request that triggered the metric, since it was last sent. + +Hit `CTRL+C` or type `q` to stop the application. + +== Create your own metrics + +=== OpenTelemetry Metrics vs Micrometer Metrics + +Metrics are single numerical measurements, often have additional data captured with them. This ancillary data is used to group or aggregate metrics for analysis. + +Pretty much like in the xref:telemetry-micrometer.adoc#create-your-own-metrics[Quarkus Micrometer extension], you can create your own metrics using the OpenTelemetry API and the concepts are analogous. + +The OpenTelemetry API provides a `Meter` interface to create metrics instead of a Registry. The `Meter` interface is the entry point for creating metrics. It provides methods to create counters, gauges, and histograms. + +Attributes can be added to metrics to add dimensions, pretty much like tags in Micrometer. + +=== Obtain a reference to the Meter + +Use one of the following methods to obtain a reference to a Meter: + +==== Use CDI Constructor injection + +[source,java] +---- +@Path("/hello-metrics") +public class MetricResource { + + private final Meter meter; + + public MetricResource(Meter meter) { + this.meter = meter; + } +} +---- +Pretty much like in the xref:metric-resource-class[example above]. + +==== Member variable using the `@Inject` annotation + +[source,java] +---- +@Inject +Meter meter; +---- + +=== Counters + +Counters can be used to measure non-negative, increasing values. + +[source, java] +---- +LongCounter counter = meter.counterBuilder("hello-metrics") // <1> + .setDescription("hello-metrics") // optional + .setUnit("invocations") // optional + .build(); + +counter.add(1, // <2> + Attributes.of(AttributeKey.stringKey("attribute.name"), "attribute value")); // optional <3> +---- + +<1> Create a `LongCounter` named `hello-metrics` with a description and unit. +<2> Increment the counter by one. +<3> Add an attribute to the counter. This will create a dimension called `attribute.name` with value `attribute value`. + +IMPORTANT: Each unique combination of metric name and dimension produces a unique time series. Using an unbounded set of dimensional data (many different values like a userId) can lead to a "cardinality explosion", an exponential increase in the creation of new time series. Avoid! + +OpenTelemetry provides many other types of Counters: `LongUpDownCounter`, `DoubleCounter`, `DoubleUpDownCounter` and also Observable, async counters like `ObservableLongCounter`, `ObservableDoubleCounter`, `ObservableLongUpDownCounter` and `ObservableDoubleUpDownCounter`. + +For more details please refer to the https://opentelemetry.io/docs/languages/java/instrumentation/#using-counters[OpenTelemetry Java documentation about Counters]. + +=== Gauges +Observable Gauges should be used to measure non-additive values. A value that can increase or decrease over time, like the speedometer on a car. Gauges can be useful when monitoring the statistics for a cache or collection. + +With this metric you provide a function to be periodically probed by a callback. The value returned by the function is the value of the gauge. + +The default gauge records `Double`values, but if you want to record `Long` values, you can use + +[source, java] +---- +meter.gaugeBuilder("jvm.memory.total") // <1> + .setDescription("Reports JVM memory usage.") + .setUnit("byte") + .ofLongs() // <2> + .buildWithCallback( // <3> + result -> result.record( + Runtime.getRuntime().totalMemory(), // <4> + Attributes.empty())); // optional <5> + +---- +<1> Create a `Gauge` named `jvm.memory.total` with a description and unit. +<2> If you want to record `Long` values you need this builder method because the default gauge records `Double` values. +<3> Build the gauge with a callback. An imperative builder is also available. +<4> Register the function to call to get the value of the gauge. +<5> No added attributes, this time. + +=== Histograms +Histograms are synchronous instruments used to measure a distribution of values over time. +It is intended for statistics such as histograms, summaries, and percentile. +The request duration and response payload size are good uses for a histogram. + + +On this section we have a new class, the `HistogramResource` that will create a `LongHistogram`. + +[source, java] +---- +package org.acme; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongHistogram; +import io.opentelemetry.api.metrics.Meter; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.jboss.logging.Logger; + +import java.util.Arrays; + +@Path("/roll-dice") +public class HistogramResource { + + private static final Logger LOG = Logger.getLogger(HistogramResource.class); + + private final LongHistogram rolls; + + public HistogramResource(Meter meter) { + rolls = meter.histogramBuilder("hello.roll.dice") // <1> + .ofLongs() // <2> + .setDescription("A distribution of the value of the rolls.") + .setExplicitBucketBoundariesAdvice(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L, 7L)) // <3> + .setUnit("points") + .build(); + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String helloGauge() { + var roll = roll(); + rolls.record(roll, // <4> + Attributes.of(AttributeKey.stringKey("attribute.name"), "value")); // <5> + LOG.info("roll-dice: " + roll); + return "" + roll; + } + + public long roll() { + return (long) (Math.random() * 6) + 1; + } +} +---- +<1> Create a `LongHistogram` named `hello.roll.dice` with a description and unit. +<2> If you want to record `Long` values you need this builder method because the default histogram records `Double` values. +<3> Set the explicit bucket boundaries for the histogram. The boundaries are inclusive. +<4> Record the value of the roll. +<5> Add an attribute to the histogram. This will create a dimension called `attribute.name` with value `value`. + +IMPORTANT: Beware of cardinality explosion. + +We can invoke the endpoint with a curl command. +[source,shell] +---- +$ curl http://localhost:8080/roll-dice +2 +---- + +If we execute 4 consecutive requests, with results *2,2,3 and 4* this will produce the following output. The `Resource` and `InstrumentationScopeInfo` data are ignored for brevity. +[source,json] +---- +//... +name=hello.roll.dice, +description=A distribution of the value of the rolls., // <1> +unit=points, +type=HISTOGRAM, +data=ImmutableHistogramData{ + aggregationTemporality=CUMULATIVE, // <2> + points=[ + ImmutableHistogramPointData{ + getStartEpochNanos=1720632058272341000, + getEpochNanos=1720632068279567000, + getAttributes={attribute.name="value"}, // <3> + getSum=11.0, // <4> + getCount=4, // <5> + hasMin=true, + getMin=2.0, // <6> + hasMax=true, + getMax=4.0, // <7> + getBoundaries=[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0], // <8> + getCounts=[0, 2, 1, 1, 0, 0, 0, 0], // <9> + getExemplars=[ // <10> + ImmutableDoubleExemplarData{ + filteredAttributes={}, + epochNanos=1720632063049392000, + spanContext=ImmutableSpanContext{ + traceId=a22b43a600682ca7320516081eca998b, + spanId=645aa49f219181d0, + traceFlags=01, + traceState=ArrayBasedTraceState{entries=[]}, + remote=false, + valid=true + }, + value=2.0 // <11> + }, + //... exemplars for values 3 and 4 omitted for brevity + ] + } + ] +} +---- +<1> The name, description and unit of the metric you defined in the constructor of the `HistogramResource` class. +<2> The aggregation temporality of the histogram. +<3> The attribute added to the histogram when the values were recorded. +<4> The sum of the values recorded. +<5> The number of values recorded. +<6> The minimum value recorded. +<7> The maximum value recorded. +<8> The explicit bucket boundaries for the histogram. +<9> The number of values recorded in each bucket. +<10> The list of exemplars with tracing data for the values recorded. We only show 1 of 3 exemplars for brevity. +<11> One of the 2 calls made with the value 2. + +=== Differences with the Micrometer API + +- Timers and Distribution Summaries are not available in the OpenTelemetry API. Instead, use Histograms. +- The OpenTelemetry API does not define annotations for metrics like Micrometer's `@Counted`, `@Timed` or `@MeterTag` You need to manually create the metrics. +- OpenTelemetry uses their own https://opentelemetry.io/docs/specs/semconv/[Semantic Conventions] to name metrics and attributes. + +=== Resource +See the main xref:opentelemetry.adoc#resource[OpenTelemetry Guide resources] section. + +== Additional instrumentation + +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 +See the main xref:opentelemetry.adoc#exporters[OpenTelemetry Guide exporters] section. + +[[configuration-reference]] +== OpenTelemetry Configuration Reference + +See the main xref:opentelemetry.adoc#configuration-reference[OpenTelemetry Guide configuration] reference. \ No newline at end of file diff --git a/docs/src/main/asciidoc/opentelemetry-tracing.adoc b/docs/src/main/asciidoc/opentelemetry-tracing.adoc new file mode 100644 index 0000000000000..088dbbf14a020 --- /dev/null +++ b/docs/src/main/asciidoc/opentelemetry-tracing.adoc @@ -0,0 +1,622 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// += Using OpenTelemetry Tracing +include::_attributes.adoc[] +:categories: observability +:summary: This guide explains how your Quarkus application can utilize OpenTelemetry Tracing to provide distributed tracing for interactive web applications. +:topics: observability,opentelemetry,tracing +:extensions: io.quarkus:quarkus-opentelemetry + +This guide explains how your Quarkus application can utilize https://opentelemetry.io/[OpenTelemetry] (OTel) to provide +distributed tracing for interactive web applications. + +[NOTE] +==== +- The xref:opentelemetry.adoc[OpenTelemetry Guide] is available with signal independent information about the OpenTelemetry extension. +- If you search more information about OpenTelemetry Metrics, please refer to the xref:opentelemetry-metrics.adoc[OpenTelemetry Metrics Guide]. +==== + +== Prerequisites + +:prerequisites-docker-compose: +include::{includes}/prerequisites.adoc[] + +== Architecture + +In this guide, we create a straightforward REST application to demonstrate distributed tracing. + +== Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can skip right to the completed example. + +Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. + +The solution is located in the `opentelemetry-quickstart` link:{quickstarts-tree-url}/opentelemetry-quickstart[directory]. + +== Creating the Maven project + +First, we need a new project. Create a new project with the following command: + +:create-app-artifact-id: opentelemetry-quickstart +:create-app-extensions: rest,quarkus-opentelemetry +include::{includes}/devtools/create-app.adoc[] + +This command generates the Maven project and imports the `quarkus-opentelemetry` extension, +which includes the default OpenTelemetry support, +and a gRPC span exporter for https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md[OTLP]. + +If you already have your Quarkus project configured, you can add the `quarkus-opentelemetry` extension +to your project by running the following command in your project base directory: + +:add-extension-extensions: opentelemetry +include::{includes}/devtools/extension-add.adoc[] + +This will add the following to your build file: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-opentelemetry + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.quarkus:quarkus-opentelemetry") +---- + +=== Examine the Jakarta REST resource + +Create a `src/main/java/org/acme/opentelemetry/TracedResource.java` file with the following content: + +[source,java] +---- +package org.acme.opentelemetry; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.jboss.logging.Logger; + +@Path("/hello") +public class TracedResource { + + private static final Logger LOG = Logger.getLogger(TracedResource.class); + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + LOG.info("hello"); + return "hello"; + } +} +---- + +Notice that there is no tracing specific code included in the application. By default, requests sent to this +endpoint will be traced without any required code changes. + +=== Create the configuration + + +:opentelemetry-config: +include::{includes}/opentelemetry-config.adoc[] + +== Run the application + +First we need to start a system to visualise the OpenTelemetry data. +We have 2 options: + +* Start an all-in-one Grafana OTel LGTM system for traces and metrics. +* Jaeger system just for traces. + +=== Grafana OTel LGTM option + +* Take a look at: xref:observability-devservices-lgtm.adoc[Getting Started with Grafana-OTel-LGTM]. + +This features a Quarkus Dev service including a Grafana for visualizing data, Loki to store logs, Tempo to store traces and Prometheus to store metrics. Also provides and OTel collector to receive the data. + +=== Jaeger to see traces option + +Configure and start the https://opentelemetry.io/docs/collector/[OpenTelemetry Collector] to receive, process and export telemetry data to https://www.jaegertracing.io/[Jaeger] that will display the captured traces. + +[NOTE] +==== +Jaeger-all-in-one includes the Jaeger agent, an OTel collector, and the query service/UI. +You do not need to install a separated collector. You can directly send the trace data to Jaeger (after enabling OTLP receivers there, see e.g. this +https://medium.com/jaegertracing/introducing-native-support-for-opentelemetry-in-jaeger-eb661be8183c[blog entry] for details). +==== + +Start the OpenTelemetry Collector and Jaeger system via the following `docker-compose.yml` file that you can launch via `docker-compose up -d`: + +[source,yaml,subs="attributes"] +---- +version: "2" +services: + + # Jaeger + jaeger-all-in-one: + image: jaegertracing/all-in-one:latest + ports: + - "16686:16686" # Jaeger UI + - "14268:14268" # Receive legacy OpenTracing traces, optional + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP HTTP receiver, not yet used by Quarkus, optional + - "14250:14250" # Receive from external otel-collector, optional + environment: + - COLLECTOR_OTLP_ENABLED=true +---- +You should remove the optional ports you don't need them. + +=== Start the application + +Now we are ready to run our application. If using `application.properties` to configure the tracer: + +include::{includes}/devtools/dev.adoc[] + +or if configuring the OTLP gRPC endpoint via JVM arguments: + +:dev-additional-parameters: -Djvm.args="-Dquarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4317" +include::{includes}/devtools/dev.adoc[] +:!dev-additional-parameters: + +With the OpenTelemetry Collector, the Jaeger system and the application running, you can make a request to the provided endpoint: + +[source,shell] +---- +$ curl http://localhost:8080/hello +hello +---- + +When the first request has been submitted, you will be able to see the tracing information in the logs: + +[source] +---- +10:49:02 INFO traceId=, parentId=, spanId=, sampled= [io.quarkus] (main) Installed features: [cdi, opentelemetry, resteasy-client, resteasy, smallrye-context-propagation, vertx] +10:49:03 INFO traceId=17ceb8429b9f25b0b879fa1503259456, parentId=3125c8bee75b7ad6, spanId=58ce77c86dd23457, sampled=true [or.ac.op.TracedResource] (executor-thread-1) hello +10:49:03 INFO traceId=ad23acd6d9a4ed3d1de07866a52fa2df, parentId=, spanId=df13f5b45cf4d1e2, sampled=true [or.ac.op.TracedResource] (executor-thread-0) hello +---- + + +Then visit the http://localhost:16686[Jaeger UI] to see the tracing information. + +Hit `CTRL+C` or type `q` to stop the application. + +=== JDBC + +The https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/jdbc/library[JDBC instrumentation] will add a span for each JDBC queries done by your application, to enable it, add the following dependency to your build file: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.opentelemetry.instrumentation + opentelemetry-jdbc + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.opentelemetry.instrumentation:opentelemetry-jdbc") +---- + +As it uses a dedicated JDBC datasource wrapper, you must enable telemetry for your datasource: + +[source, properties] +---- +# enable tracing +quarkus.datasource.jdbc.telemetry=true + +# configure datasource +quarkus.datasource.db-kind=postgresql +quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydatabase +---- + +== Additional configuration +Some use cases will require custom configuration of OpenTelemetry. +These sections will outline what is necessary to properly configure it. + +=== ID Generator +The OpenTelemetry extension will use by default a random https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#id-generators[ID Generator] +when creating the trace and span identifier. + +Some vendor-specific protocols need a custom ID Generator, you can override the default one by creating a producer. +The OpenTelemetry extension will detect the `IdGenerator` CDI bean and will use it when configuring the tracer producer. + +[source,java] +---- +@Singleton +public class CustomConfiguration { + + /** Creates a custom IdGenerator for OpenTelemetry */ + @Produces + @Singleton + public IdGenerator idGenerator() { + return AwsXrayIdGenerator.getInstance(); + } +} +---- + +=== Propagators +OpenTelemetry propagates cross-cutting concerns through https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md[propagators] that will share an underlying `Context` for storing state and accessing +data across the lifespan of a distributed transaction. + +By default, the OpenTelemetry extension enables the https://www.w3.org/TR/trace-context/[W3C Trace Context] and the https://www.w3.org/TR/baggage/[W3C Baggage] +propagators, you can however choose any of the supported OpenTelemetry propagators by setting the `propagators` config that is described in the <>. + +==== Additional Propagators + +* The `b3`, `b3multi`, `jaeger` and `ottrace` propagators will need the https://github.com/open-telemetry/opentelemetry-java/tree/main/extensions/trace-propagators[trace-propagators] +extension to be added as a dependency to your project. + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.opentelemetry + opentelemetry-extension-trace-propagators + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.opentelemetry:opentelemetry-extension-trace-propagators") +---- + +* The `xray` propagator will need the https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/aws-xray-propagator[aws] +extension to be added as a dependency to your project. + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.opentelemetry.contrib + opentelemetry-aws-xray-propagator + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") +---- + +==== Customise Propagator + +To customise the propagation header you can implement the `TextMapPropagatorCustomizer` interface. This can be used, as an example, to restrict propagation of OpenTelemetry trace headers and prevent potentially sensitive data to be sent to third party systems. + +```java +/** + * /** + * Meant to be implemented by a CDI bean that provides arbitrary customization for the TextMapPropagator + * that are to be registered with OpenTelemetry + */ +public interface TextMapPropagatorCustomizer { + + TextMapPropagator customize(Context context); + + interface Context { + TextMapPropagator propagator(); + + ConfigProperties otelConfigProperties(); + } +} +``` + +=== Resource + +See the main xref:opentelemetry.adoc#resource[OpenTelemetry Guide resources] section. + +==== End User attributes + +When enabled, Quarkus adds OpenTelemetry End User attributes as Span attributes. +Before you enable this feature, verify that Quarkus Security extension is present and configured. +More information about the Quarkus Security can be found in the xref:security-overview.adoc[Quarkus Security overview]. + +The attributes are only added when authentication has already happened on a best-efforts basis. +Whether the End User attributes are added as Span attributes depends on authentication and authorization configuration of your Quarkus application. +If you create custom Spans prior to the authentication, Quarkus cannot add the End User attributes to them. +Quarkus is only able to add the attributes to the Span that is current after the authentication has been finished. +Another important consideration regarding custom Spans is active CDI request context that is used to propagate Quarkus `SecurityIdentity`. +In principle, Quarkus is able to add the End User attributes when the CDI request context has been activated for you before the custom Spans are created. + +[source,application.properties] +---- +quarkus.otel.traces.eusp.enabled=true <1> +quarkus.http.auth.proactive=true <2> +---- +<1> Enable the End User Attributes feature so that the `SecurityIdentity` principal and roles are added as Span attributes. +The End User attributes are personally identifiable information, therefore make sure you want to export them before you enable this feature. +<2> Optionally enable proactive authentication. +The best possible results are achieved when proactive authentication is enabled because the authentication happens sooner. +A good way to determine whether proactive authentication should be enabled in your Quarkus application is to read the Quarkus xref:security-proactive-authentication.adoc[Proactive authentication] guide. + +IMPORTANT: This feature is not supported when a custom xref:security-customization.adoc#jaxrs-security-context[Jakarta REST SecurityContexts] is used. + +[[sampler]] +=== Sampler +A https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling[sampler] decides whether a trace should be discarded or forwarded, effectively managing noise and reducing overhead by limiting the number of collected traces sent to the collector. + +Quarkus comes equipped with a https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#built-in-samplers[built-in sampler], and you also have the option to create your custom sampler. + +To use the built-in sampler, you can configure it by setting the desired sampler parameters as detailed in the <>. As an example, you can configure the sampler to retain 50% of the traces: +[source,application.properties] +---- +# build time property only: +quarkus.otel.traces.sampler=traceidratio +# Runtime property: +quarkus.otel.traces.sampler.arg=0.5 +---- +[TIP] +==== + +An interesting use case for the sampler is to activate and deactivate tracing export at runtime, acording to this example: +[source,application.properties] +---- +# build time property only: +quarkus.otel.traces.sampler=traceidratio +# On (default). All traces are exported: +quarkus.otel.traces.sampler.arg=1.0 +# Off. No traces are exported: +quarkus.otel.traces.sampler.arg=0.0 +---- +==== + +[NOTE] +==== +Quarkus 3.0 introduced breaking changes on the configuration. + +Sampler related property names and values change to comply with the latest Java OpenTelemetry SDK. During a transition period it will be possible to set the new configuration values in the old property because we are mapping `quarkus.opentelemetry.tracer.sampler` -> `quarkus.otel.traces.sampler`. + +If the sampler is parent based, there is no need to set, the now dropped property, `quarkus.opentelemetry.tracer.sampler.parent-based`. + +The values you need to set on `quarkus.opentelemetry.tracer.sampler` are now: + +|=== +|Old Sampler config value |New Sampler config value|New Sampler config value (Parent based) + +|`on` +|`always_on` +|`parentbased_always_on` + +|`off` +|`always_off` +|`parentbased_always_off` + +|`ratio` +|`traceidratio` +|`parentbased_traceidratio` +|=== +==== + +If you need to use a custom sampler there are now 2 different ways: + +==== Sampler CDI Producer + +You can create a sampler CDI producer. The Quarkus OpenTelemetry extension will detect the `Sampler` CDI bean and will use it when configuring the Tracer. + +[source,java] +---- +@Singleton +public class CustomConfiguration { + + /** Creates a custom sampler for OpenTelemetry */ + @Produces + @Singleton + public Sampler sampler() { + return JaegerRemoteSampler.builder() + .setServiceName("my-service") + .build(); + } +} +---- + +==== OTel Sampler SPI + +This will use the SPI hooks available with the OTel Autoconfiguration. +You can create a simple Sampler class: +[source,java] +---- +public class CustomSPISampler implements Sampler { + @Override + public SamplingResult shouldSample(Context context, + String s, + String s1, + SpanKind spanKind, + Attributes attributes, + List list) { + // Do some sampling here + return Sampler.alwaysOn().shouldSample(context, s, s1, spanKind, attributes, list); + } + + @Override + public String getDescription() { + return "custom-spi-sampler-description"; + } +} + +---- +Then a Sampler Provider: +[source,java] +---- +public class CustomSPISamplerProvider implements ConfigurableSamplerProvider { + @Override + public Sampler createSampler(ConfigProperties configProperties) { + return new CustomSPISampler(); + } + + @Override + public String getName() { + return "custom-spi-sampler"; + } +} +---- +Write the SPI loader text file at `resources/META-INF/services` with name `io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProvider` containing the full qualified name of the `CustomSPISamplerProvider` class. + +Then activate on the configuration: +[source,properties] +---- +quarkus.otel.traces.sampler=custom-spi-sampler +---- + +As you can see, CDI is much simpler to work with. + +== Additional instrumentation + +Some Quarkus extensions will require additional code to ensure traces are propagated to subsequent execution. +These sections will outline what is necessary to propagate traces across process boundaries. + +The instrumentation documented in this section has been tested with Quarkus and works in both standard and native mode. + +=== CDI + +Annotating a method in any CDI aware bean with the `io.opentelemetry.instrumentation.annotations.WithSpan` +annotation will create a new Span and establish any required relationships with the current Trace context. + +Annotating a method in any CDI aware bean with the `io.opentelemetry.instrumentation.annotations.AddingSpanAttributes` will not create a new span but will add annotated method parameters to attributes in the current span. + +If a method is annotated by mistake with `@AddingSpanAttributes` and `@WithSpan` annotations, the `@WithSpan` annotation will take precedence. + +Method parameters can be annotated with the `io.opentelemetry.instrumentation.annotations.SpanAttribute` annotation to +indicate which method parameters should be part of the span. The parameter name can be customized as well. + +Example: +[source,java] +---- +@ApplicationScoped +class SpanBean { + @WithSpan + void span() { + + } + + @WithSpan("name") + void spanName() { + + } + + @WithSpan(kind = SERVER) + void spanKind() { + + } + + @WithSpan + void spanArgs(@SpanAttribute(value = "arg") String arg) { + + } + + @AddingSpanAttributes + void addArgumentToExistingSpan(@SpanAttribute(value = "arg") String arg) { + + } +} +---- + +=== Available OpenTelemetry CDI injections + +As per MicroProfile Telemetry Tracing specification, Quarkus supports the CDI injections of the +following classes: + +* `io.opentelemetry.api.OpenTelemetry` +* `io.opentelemetry.api.trace.Tracer` +* `io.opentelemetry.api.trace.Span` +* `io.opentelemetry.api.baggage.Baggage` + +You can inject these classes in any CDI enabled bean. For instance, the `Tracer` is particularly useful to start custom spans: + +[source,java] +---- +@Inject +Tracer tracer; + +... + +public void tracedWork() { + Span span = tracer.spanBuilder("My custom span") + .setAttribute("attr", "attr.value") + .setParent(Context.current().with(Span.current())) + .setSpanKind(SpanKind.INTERNAL) + .startSpan(); + + // traced work + + span.end(); +} +---- + +=== Quarkus Messaging - Kafka + +When using the Quarkus Messaging extension for Kafka, +we are able to propagate the span into the Kafka Record with: + +[source,java] +---- +TracingMetadata tm = TracingMetadata.withPrevious(Context.current()); +Message out = Message.of(...).withMetadata(tm); +---- + +The above creates a `TracingMetadata` object we can add to the `Message` being produced, +which retrieves the OpenTelemetry `Context` to extract the current span for propagation. + +=== Quarkus Security Events + +Quarkus supports exporting of the xref:security-customization.adoc#observe-security-events[Security events] as OpenTelemetry Span events. + +[source,application.properties] +---- +quarkus.otel.security-events.enabled=true <1> +---- +<1> Export Quarkus Security events as OpenTelemetry Span events. + +== Exporters + +See the main xref:opentelemetry.adoc#exporters[OpenTelemetry Guide exporters] section. + +[[quarkus-extensions-using-opentelemetry]] +== Quarkus core extensions instrumented with OpenTelemetry tracing + +* https://quarkus.io/extensions/io.quarkus/quarkus-agroal[`quarkus-agroal`] +* https://quarkus.io/guides/grpc-getting-started[`quarkus-grpc`] +* https://quarkus.io/guides/redis[`quarkus-redis-client`] +* https://quarkus.io/extensions/io.quarkus/quarkus-rest-client-jaxrs[`quarkus-rest-client-jaxrs`] +* https://quarkus.io/guides/rest[`quarkus-rest`] +* https://quarkus.io/guides/resteasy[`quarkus-resteasy-jackson`] +* https://quarkus.io/guides/resteasy-client[`quarkus-resteasy-client`] +* https://quarkus.io/guides/scheduler[`quarkus-scheduler`] +* https://quarkus.io/guides/smallrye-graphql[`quarkus-smallrye-graphql`] +* https://quarkus.io/extensions/io.quarkus/quarkus-mongodb-client[`quarkus-mongodb-client`] +* https://quarkus.io/extensions/io.quarkus/quarkus-messaging[`quarkus-messaging`] +** AMQP 1.0 +** RabbitMQ +** Kafka +** Pulsar +* https://quarkus.io/guides/vertx[`quarkus-vertx`] (http requests) + + +=== Disable parts of the automatic tracing + +Automatic tracing instrumentation parts can be disabled by setting `quarkus.otel.instrument.*` properties to `false`. + +Examples: +[source,properties] +---- +quarkus.otel.instrument.grpc=false +quarkus.otel.instrument.messaging=false +quarkus.otel.instrument.resteasy-client=false +quarkus.otel.instrument.rest=false +quarkus.otel.instrument.resteasy=false +---- + +[[configuration-reference]] +== OpenTelemetry Configuration Reference + +See the main xref:opentelemetry.adoc#configuration-reference[OpenTelemetry Guide configuration] reference. diff --git a/docs/src/main/asciidoc/opentelemetry.adoc b/docs/src/main/asciidoc/opentelemetry.adoc index bc5cb1e2c72fa..f3a79b928b7dd 100644 --- a/docs/src/main/asciidoc/opentelemetry.adoc +++ b/docs/src/main/asciidoc/opentelemetry.adoc @@ -5,57 +5,68 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// = Using OpenTelemetry include::_attributes.adoc[] +:diataxis-type: reference :categories: observability -:summary: This guide explains how your Quarkus application can utilize OpenTelemetry to provide distributed tracing for interactive web applications. +:summary: This guide explains how your Quarkus application can utilize OpenTelemetry to provide observability for interactive web applications. :topics: observability,opentelemetry :extensions: io.quarkus:quarkus-opentelemetry This guide explains how your Quarkus application can utilize https://opentelemetry.io/[OpenTelemetry] (OTel) to provide -distributed tracing for interactive web applications. +Observability for interactive web applications. + +On these page we show the signal independent features of the extension. [NOTE] ==== -- OpenTelemetry Metrics and Logging are not yet supported. -- Quarkus now supports the OpenTelemetry Autoconfiguration for Traces. The configurations match what you can see at -https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md[OpenTelemetry SDK Autoconfigure] -with the `quarkus.*` prefix. -- Extensions and the libraries they provide, are directly instrumented in Quarkus. The *use of the https://opentelemetry.io/docs/instrumentation/java/automatic/[OpenTelemetry Agent] is not needed nor recommended* due to context propagation issues between imperative and reactive libraries. -- If you come from the legacy OpenTracing extension, there is a xref:telemetry-opentracing-to-otel-tutorial.adoc[guide to help with the migration]. -- Current Semantic Conventions for HTTP will soon change and the current conventions are deprecated for removal soon. Please move to the new conventions by seetinh the new property `quarkus.otel.semconv-stability.opt-in` to `http`, for the new conventions or `http/dup` to produce duplicated old and new conventions. Please check the <> for more details and full set of changes at the https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/migration-guide.md#summary-of-changes[HTTP semantic convention stability migration guide]. +- The old OpenTelemetry guide has been split into this generic guide, the xref:opentelemetry-tracing.adoc[OpenTelemetry Tracing Guide] and the new xref:opentelemetry-metrics.adoc[OpenTelemetry Metrics Guide] has been created. +- OpenTelemetry Logging is not yet supported. +- The use of *the https://opentelemetry.io/docs/instrumentation/java/automatic/[OpenTelemetry Agent] is not needed nor recommended*. Quarkus Extensions and the libraries they provide, are directly instrumented. That agent doesn't work with native mode. ==== -== Prerequisites +== Introduction +https://opentelemetry.io/docs/what-is-opentelemetry/[OpenTelemetry] is an Observability framework and toolkit designed to create and manage telemetry data such as traces, metrics, and logs. Crucially, OpenTelemetry is vendor- and tool-agnostic. + +Quarkus provides manual and automatic instrumentation for tracing and manual instrumentation capabilities for metrics. + +This will allow Quarkus based applications to be observable by tools and services supporting OpenTelemetry. + +[NOTE] +==== +Automatic metrics instrumentation in Quarkus is done by the xref:telemetry-micrometer.adoc[Quarkus Micrometer extension]. We plan to provide, in the future, a bridge for those metrics to be available in OpenTelemetry as well. +==== -:prerequisites-docker-compose: -include::{includes}/prerequisites.adoc[] +Quarkus supports the OpenTelemetry Autoconfiguration. The configurations match what you can see at +https://opentelemetry.io/docs/languages/java/configuration/[OpenTelemetry SDK Autoconfigure] +with the `quarkus.*` prefix. -== Architecture +This guide provides a crosscutting explanation of the OpenTelemetry extension and how to use it. If you need details about any particular signal (tracing or metrics), please refer to the signal specific guide. -In this guide, we create a straightforward REST application to demonstrate distributed tracing. +With the introduction of OpenTelemetry Metrics, the original, single page guide had to be split according to signal types, as follows: -== Solution +=== xref:opentelemetry-tracing.adoc[OpenTelemetry Tracing Guide] -We recommend that you follow the instructions in the next sections and create the application step by step. -However, you can skip right to the completed example. +The tracing functionality is supported and *on* by default. -Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. +=== xref:opentelemetry-metrics.adoc[OpenTelemetry Metrics Guide] -The solution is located in the `opentelemetry-quickstart` link:{quickstarts-tree-url}/opentelemetry-quickstart[directory]. +==== Enable Metrics +The metrics functionality is experimental and *off* by default. You will need to activate it by setting: -== Creating the Maven project +[source,properties] +---- +quarkus.otel.metrics.enabled=true +---- +At build time on your `application.properties` file. -First, we need a new project. Create a new project with the following command: +==== 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. -:create-app-artifact-id: opentelemetry-quickstart -:create-app-extensions: rest,quarkus-opentelemetry -include::{includes}/devtools/create-app.adoc[] +In the future, we plan to bridge current Micrometer metrics to OpenTelemetry and maintain compatibility when possible. -This command generates the Maven project and imports the `quarkus-opentelemetry` extension, -which includes the default OpenTelemetry support, -and a gRPC span exporter for https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md[OTLP]. +== Using the extension -If you already have your Quarkus project configured, you can add the `quarkus-opentelemetry` extension -to your project by running the following command in your project base directory: +If you already have your Quarkus project, you can add the `quarkus-opentelemetry` extension +to it by running the following command in your project base directory: :add-extension-extensions: opentelemetry include::{includes}/devtools/extension-add.adoc[] @@ -77,307 +88,119 @@ This will add the following to your build file: implementation("io.quarkus:quarkus-opentelemetry") ---- -=== Examine the Jakarta REST resource - -Create a `src/main/java/org/acme/opentelemetry/TracedResource.java` file with the following content: - -[source,java] ----- -package org.acme.opentelemetry; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import org.jboss.logging.Logger; - -@Path("/hello") -public class TracedResource { - - private static final Logger LOG = Logger.getLogger(TracedResource.class); - - @GET - @Produces(MediaType.TEXT_PLAIN) - public String hello() { - LOG.info("hello"); - return "hello"; - } -} ----- - -Notice that there is no tracing specific code included in the application. By default, requests sent to this -endpoint will be traced without any required code changes. === Create the configuration -There are no mandatory configurations for the extension to work. - -If you need to change any of the default property values, here is an example on how to configure the default OTLP gRPC Exporter within the application, using the `src/main/resources/application.properties` file: - -[source,properties] ----- -quarkus.application.name=myservice // <1> -quarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4317 // <2> -quarkus.otel.exporter.otlp.traces.headers=authorization=Bearer my_secret // <3> -quarkus.log.console.format=%d{HH:mm:ss} %-5p traceId=%X{traceId}, parentId=%X{parentId}, spanId=%X{spanId}, sampled=%X{sampled} [%c{2.}] (%t) %s%e%n // <4> - -# Alternative to the console log -quarkus.http.access-log.pattern="...traceId=%{X,traceId} spanId=%{X,spanId}" // <5> ----- - -<1> All spans created from the application will include an OpenTelemetry `Resource` indicating the span was created by the `myservice` application. If not set, it will default to the artifact id. -<2> gRPC endpoint to send spans. If not set, it will default to `http://localhost:4317`. -<3> Optional gRPC headers commonly used for authentication -<4> Add tracing information into log messages. -<5> You can also only put the trace info into the access log. In this case you must omit the info in the console log format. - -[NOTE] -==== -All configurations have been updated from `quarkus.opentelemetry.\*` -> `quarkus.otel.*` - -The legacy configurations are now deprecated but will still work during a transition period. -==== +:opentelemetry-config: +include::{includes}/opentelemetry-config.adoc[] === Disable all or parts of the OpenTelemetry extension -Once you add the dependency, the extension will be enabled by default but there are a few ways to disable the OpenTelemetry extension globally or partially. +Once you add the dependency, the extension will generate tracing data by default. To enable metrics or disable the OpenTelemetry extension globally or partially these are the properties to use (they are extracted from the config reference bellow): |=== -|Property name |Default value |Description +|Affected Signal | Property name |Default value |Description +| All |`quarkus.otel.enabled` |true |If false, disable the OpenTelemetry usage at *build* time. +| All |`quarkus.otel.sdk.disabled` |false |Comes from the OpenTelemetry autoconfiguration. If true, will disable the OpenTelemetry SDK usage at *runtime*. -|`quarkus.otel.traces.enabled` -|true -|If false, disable the OpenTelemetry tracing usage at *build* time. - +| All output |`quarkus.otel.exporter.otlp.enabled` |true -|If false will disable the default OTLP exporter at *build* time. -|=== - -If you need to enable or disable the exporter at runtime, you can use the <> because it has the ability to filter out all the spans if needed. - - -== Run the application - -First we need to start a system to visualise the OpenTelemetry data. -We have 2 options: - -* Start an all-in-one Grafana OTel LGTM system for traces and metrics. -* Jaeger system just for traces. - -=== Grafana OTel LGTM option - -* Take a look at: xref:observability-devservices-lgtm.adoc[Getting Started with Grafana-OTel-LGTM]. - -This features a Quarkus Dev service including a Grafana for visualizing data, Loki to store logs, Tempo to store traces and Prometheus to store metrics. Also provides and OTel collector to receive the data. - -=== Jaeger to see traces option - -Configure and start the https://opentelemetry.io/docs/collector/[OpenTelemetry Collector] to receive, process and export telemetry data to https://www.jaegertracing.io/[Jaeger] that will display the captured traces. - -[NOTE] -==== -Jaeger-all-in-one includes the Jaeger agent, an OTel collector, and the query service/UI. -You do not need to install a separated collector. You can directly send the trace data to Jaeger (after enabling OTLP receivers there, see e.g. this -https://medium.com/jaegertracing/introducing-native-support-for-opentelemetry-in-jaeger-eb661be8183c[blog entry] for details). -==== - -Start the OpenTelemetry Collector and Jaeger system via the following `docker-compose.yml` file that you can launch via `docker-compose up -d`: - -[source,yaml,subs="attributes"] ----- -version: "2" -services: - - # Jaeger - jaeger-all-in-one: - image: jaegertracing/all-in-one:latest - ports: - - "16686:16686" # Jaeger UI - - "14268:14268" # Receive legacy OpenTracing traces, optional - - "4317:4317" # OTLP gRPC receiver - - "4318:4318" # OTLP HTTP receiver, not yet used by Quarkus, optional - - "14250:14250" # Receive from external otel-collector, optional - environment: - - COLLECTOR_OTLP_ENABLED=true ----- -You should remove the optional ports you don't need them. - -=== Start the application - -Now we are ready to run our application. If using `application.properties` to configure the tracer: - -include::{includes}/devtools/dev.adoc[] +|Deprecated for removal. -or if configuring the OTLP gRPC endpoint via JVM arguments: +If false will disable the default OTLP exporter at *build* time. -:dev-additional-parameters: -Djvm.args="-Dquarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4317" -include::{includes}/devtools/dev.adoc[] -:!dev-additional-parameters: - -With the OpenTelemetry Collector, the Jaeger system and the application running, you can make a request to the provided endpoint: - -[source,shell] ----- -$ curl http://localhost:8080/hello -hello ----- - -When the first request has been submitted, you will be able to see the tracing information in the logs: - -[source] ----- -10:49:02 INFO traceId=, parentId=, spanId=, sampled= [io.quarkus] (main) Installed features: [cdi, opentelemetry, resteasy-client, resteasy, smallrye-context-propagation, vertx] -10:49:03 INFO traceId=17ceb8429b9f25b0b879fa1503259456, parentId=3125c8bee75b7ad6, spanId=58ce77c86dd23457, sampled=true [or.ac.op.TracedResource] (executor-thread-1) hello -10:49:03 INFO traceId=ad23acd6d9a4ed3d1de07866a52fa2df, parentId=, spanId=df13f5b45cf4d1e2, sampled=true [or.ac.op.TracedResource] (executor-thread-0) hello ----- - - -Then visit the http://localhost:16686[Jaeger UI] to see the tracing information. - -Hit `CTRL+C` or type `q` to stop the application. - -=== JDBC - -The https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/jdbc/library[JDBC instrumentation] will add a span for each JDBC queries done by your application, to enable it, add the following dependency to your build file: - -[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] -.pom.xml ----- - - io.opentelemetry.instrumentation - opentelemetry-jdbc - ----- +| Traces +|`quarkus.otel.traces.enabled` +|true +|If false, disable the OpenTelemetry tracing usage at *build* time. -[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] -.build.gradle ----- -implementation("io.opentelemetry.instrumentation:opentelemetry-jdbc") ----- +| Traces output +|`quarkus.otel.traces.exporter` +|cdi +|List of exporters to be used for tracing, separated by commas. Has one of the values from _ExporterType_: `otlp`, `cdi`, `none`. This is a *build* time property and setting it to `none` will disable tracing data output. -As it uses a dedicated JDBC datasource wrapper, you must enable telemetry for your datasource: +| Metrics +|`quarkus.otel.metrics.enabled` +|false +|Metrics are disabled by default at *build* time because they are experimental. -[source, properties] ----- -# enable tracing -quarkus.datasource.jdbc.telemetry=true +| Metrics output +|`quarkus.otel.metrics.exporter` +|cdi +|List of exporters to be used for metrics, separated by commas. Has one of the values from _ExporterType_: `otlp`, `cdi`, `none`. This is a *build* time property and setting it to `none` will disable metrics data output. +|=== -# configure datasource -quarkus.datasource.db-kind=postgresql -quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydatabase ----- +If you need to enable or disable the exporter at runtime, you can use the xref:opentelemetry-tracing.adoc#sampler[sampler] because it has the ability to filter out all the spans if needed. -== Additional configuration -Some use cases will require custom configuration of OpenTelemetry. -These sections will outline what is necessary to properly configure it. +Particular instrumentation components can be disabled in tracing, like ignore client requests but keep server ones. For more details, please check the xref:opentelemetry-tracing.adoc[OpenTelemetry Tracing Guide]. -=== ID Generator -The OpenTelemetry extension will use by default a random https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#id-generators[ID Generator] -when creating the trace and span identifier. +=== Resource +A https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#resources[resource] is a representation +of the entity that is producing telemetry, it adds attributes to the exported trace or metric to characterize who is producing the telemetry. Quarkus follows the https://opentelemetry.io/docs/languages/java/configuration/#resources[resources auto-configuration] specified by the Java OpenTelemetry SDK. -Some vendor-specific protocols need a custom ID Generator, you can override the default one by creating a producer. -The OpenTelemetry extension will detect the `IdGenerator` CDI bean and will use it when configuring the tracer producer. +==== Default +The following attributes are added by default to resources. -[source,java] ----- -@Singleton -public class CustomConfiguration { +|=== +|Attribute name|Content example|Origin - /** Creates a custom IdGenerator for OpenTelemetry */ - @Produces - @Singleton - public IdGenerator idGenerator() { - return AwsXrayIdGenerator.getInstance(); - } -} ----- +|service.name +|"opentelemetry-quickstart" +|Value comes from the artifactId, from the `quarkus.application.name` property or from `quarkus.otel.resource.attributes=service.name=cart` property. -=== Propagators -OpenTelemetry propagates cross-cutting concerns through https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md[propagators] that will share an underlying `Context` for storing state and accessing -data across the lifespan of a distributed transaction. +|host.name +|"myHost" +|Resolved at startup -By default, the OpenTelemetry extension enables the https://www.w3.org/TR/trace-context/[W3C Trace Context] and the https://www.w3.org/TR/baggage/[W3C Baggage] -propagators, you can however choose any of the supported OpenTelemetry propagators by setting the `propagators` config that is described in the <>. +|service.version +|"1.0-SNAPSHOT" +|Resolved at build time from the artifact version -==== Additional Propagators +|telemetry.sdk.language +|"java" +|Static value -* The `b3`, `b3multi`, `jaeger` and `ottrace` propagators will need the https://github.com/open-telemetry/opentelemetry-java/tree/main/extensions/trace-propagators[trace-propagators] -extension to be added as a dependency to your project. +|telemetry.sdk.name +|"opentelemetry" +|Resolved at build time -[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] -.pom.xml ----- - - io.opentelemetry - opentelemetry-extension-trace-propagators - ----- +|telemetry.sdk.version +|"1.32.0" +|Resolved at build time -[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] -.build.gradle ----- -implementation("io.opentelemetry:opentelemetry-extension-trace-propagators") ----- +|webengine.name +|"Quarkus" +|Static value -* The `xray` propagator will need the https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/aws-xray-propagator[aws] -extension to be added as a dependency to your project. +|webengine.version +|"999-SNAPSHOT" +|Quarkus version resolved at build time +|=== -[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] -.pom.xml ----- - - io.opentelemetry.contrib - opentelemetry-aws-xray-propagator - ----- +==== Using configuration +You can add additional attributes by setting the `quarkus.otel.resource.attributes` config property that is described in the <>. +Since this property can be overridden at runtime, the OpenTelemetry extension will pick up its value following the order of precedence that +is described in the xref:config-reference.adoc#configuration-sources[Quarkus Configuration Reference]. -[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] -.build.gradle +[source,properties] ---- -implementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") +quarkus.otel.resource.attributes=deployment.environment=dev,service.name=cart,service.namespace=shopping ---- -==== Customise Propagator - -To customise the propagation header you can implement the `TextMapPropagatorCustomizer` interface. This can be used, as an example, to restrict propagation of OpenTelemetry trace headers and prevent potentially sensitive data to be sent to third party systems. - -```java -/** - * /** - * Meant to be implemented by a CDI bean that provides arbitrary customization for the TextMapPropagator - * that are to be registered with OpenTelemetry - */ -public interface TextMapPropagatorCustomizer { - - TextMapPropagator customize(Context context); - - interface Context { - TextMapPropagator propagator(); - - ConfigProperties otelConfigProperties(); - } -} -``` - -=== Resource -A https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#resources[resource] is a representation -of the entity that is producing telemetry, it adds attributes to the exported trace to characterize who is producing the trace. - -You can add attributes by setting the `resource-attributes` tracer config that is described in the <>. -Since this property can be overridden at runtime, the OpenTelemetry extension will pick up its value following the order of precedence that -is described in the xref:config-reference.adoc#configuration-sources[Quarkus Configuration Reference]. +This will add the attributes for `deployment.environment`, `service.name` and `service.namespace` to the resource and be included in traces and metrics. +==== Using CDI beans If by any means you need to use a custom resource or one that is provided by one of the https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions[OpenTelemetry SDK Extensions] -you can create multiple resource producers. The OpenTelemetry extension will detect the `Resource` CDI beans and will merge them when configuring the tracer producer. +you can create multiple resource producers. The OpenTelemetry extension will detect the `Resource` CDI beans and will merge them when configuring the OTel SDK. [source,java] ---- @@ -398,299 +221,81 @@ public class CustomConfiguration { } ---- -==== End User attributes +=== Semantic conventions -When enabled, Quarkus adds OpenTelemetry End User attributes as Span attributes. -Before you enable this feature, verify that Quarkus Security extension is present and configured. -More information about the Quarkus Security can be found in the xref:security-overview.adoc[Quarkus Security overview]. +OpenTelemetry provides a set of https://opentelemetry.io/docs/specs/semconv/http/http-spans/[semantic conventions] to standardize the data collected by the instrumentation. -The attributes are only added when authentication has already happened on a best-efforts basis. -Whether the End User attributes are added as Span attributes depends on authentication and authorization configuration of your Quarkus application. -If you create custom Spans prior to the authentication, Quarkus cannot add the End User attributes to them. -Quarkus is only able to add the attributes to the Span that is current after the authentication has been finished. -Another important consideration regarding custom Spans is active CDI request context that is used to propagate Quarkus `SecurityIdentity`. -In principle, Quarkus is able to add the End User attributes when the CDI request context has been activated for you before the custom Spans are created. - -[source,application.properties] ----- -quarkus.otel.traces.eusp.enabled=true <1> -quarkus.http.auth.proactive=true <2> ----- -<1> Enable the End User Attributes feature so that the `SecurityIdentity` principal and roles are added as Span attributes. -The End User attributes are personally identifiable information, therefore make sure you want to export them before you enable this feature. -<2> Optionally enable proactive authentication. -The best possible results are achieved when proactive authentication is enabled because the authentication happens sooner. -A good way to determine whether proactive authentication should be enabled in your Quarkus application is to read the Quarkus xref:security-proactive-authentication.adoc[Proactive authentication] guide. - -IMPORTANT: This feature is not supported when a custom xref:security-customization.adoc#jaxrs-security-context[Jakarta REST SecurityContexts] is used. - -[[sampler]] -=== Sampler -A https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling[sampler] decides whether a trace should be discarded or forwarded, effectively managing noise and reducing overhead by limiting the number of collected traces sent to the collector. - -Quarkus comes equipped with a https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#built-in-samplers[built-in sampler], and you also have the option to create your custom sampler. - -To use the built-in sampler, you can configure it by setting the desired sampler parameters as detailed in the <>. As an example, you can configure the sampler to retain 50% of the traces: -[source,application.properties] ----- -# build time property only: -quarkus.otel.traces.sampler=traceidratio -# Runtime property: -quarkus.otel.traces.sampler.arg=0.5 ----- -[TIP] -==== - -An interesting use case for the sampler is to activate and deactivate tracing export at runtime, acording to this example: -[source,application.properties] ----- -# build time property only: -quarkus.otel.traces.sampler=traceidratio -# On (default). All traces are exported: -quarkus.otel.traces.sampler.arg=1.0 -# Off. No traces are exported: -quarkus.otel.traces.sampler.arg=0.0 ----- -==== - -[NOTE] -==== -Quarkus 3.0 introduced breaking changes on the configuration. - -Sampler related property names and values change to comply with the latest Java OpenTelemetry SDK. During a transition period it will be possible to set the new configuration values in the old property because we are mapping `quarkus.opentelemetry.tracer.sampler` -> `quarkus.otel.traces.sampler`. - -If the sampler is parent based, there is no need to set, the now dropped property, `quarkus.opentelemetry.tracer.sampler.parent-based`. - -The values you need to set on `quarkus.opentelemetry.tracer.sampler` are now: - -|=== -|Old Sampler config value |New Sampler config value|New Sampler config value (Parent based) - -|`on` -|`always_on` -|`parentbased_always_on` - -|`off` -|`always_off` -|`parentbased_always_off` - -|`ratio` -|`traceidratio` -|`parentbased_traceidratio` -|=== -==== - -If you need to use a custom sampler there are now 2 different ways: - -==== Sampler CDI Producer - -You can create a sampler CDI producer. The Quarkus OpenTelemetry extension will detect the `Sampler` CDI bean and will use it when configuring the Tracer. - -[source,java] ----- -@Singleton -public class CustomConfiguration { - - /** Creates a custom sampler for OpenTelemetry */ - @Produces - @Singleton - public Sampler sampler() { - return JaegerRemoteSampler.builder() - .setServiceName("my-service") - .build(); - } -} ----- - -==== OTel Sampler SPI - -This will use the SPI hooks available with the OTel Autoconfiguration. -You can create a simple Sampler class: -[source,java] ----- -public class CustomSPISampler implements Sampler { - @Override - public SamplingResult shouldSample(Context context, - String s, - String s1, - SpanKind spanKind, - Attributes attributes, - List list) { - // Do some sampling here - return Sampler.alwaysOn().shouldSample(context, s, s1, spanKind, attributes, list); - } - - @Override - public String getDescription() { - return "custom-spi-sampler-description"; - } -} +When creating manual instrumentation, while naming metrics or attributes you should follow those conventions and not create new names to represent existing conventions. This will make data correlation easier to perform across services. ----- -Then a Sampler Provider: -[source,java] ----- -public class CustomSPISamplerProvider implements ConfigurableSamplerProvider { - @Override - public Sampler createSampler(ConfigProperties configProperties) { - return new CustomSPISampler(); - } - - @Override - public String getName() { - return "custom-spi-sampler"; - } -} ----- -Write the SPI loader text file at `resources/META-INF/services` with name `io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProvider` containing the full qualified name of the `CustomSPISamplerProvider` class. - -Then activate on the configuration: -[source,properties] ----- -quarkus.otel.traces.sampler=custom-spi-sampler ----- - -As you can see, CDI is much simpler to work with. - -== Additional instrumentation - -Some Quarkus extensions will require additional code to ensure traces are propagated to subsequent execution. -These sections will outline what is necessary to propagate traces across process boundaries. - -The instrumentation documented in this section has been tested with Quarkus and works in both standard and native mode. - -=== CDI - -Annotating a method in any CDI aware bean with the `io.opentelemetry.instrumentation.annotations.WithSpan` -annotation will create a new Span and establish any required relationships with the current Trace context. - -Annotating a method in any CDI aware bean with the `io.opentelemetry.instrumentation.annotations.AddingSpanAttributes` will not create a new span but will add annotated method parameters to attributes in the current span. - -If a method is annotated by mistake with `@AddingSpanAttributes` and `@WithSpan` annotations, the `@WithSpan` annotation will take precedence. - -Method parameters can be annotated with the `io.opentelemetry.instrumentation.annotations.SpanAttribute` annotation to -indicate which method parameters should be part of the span. The parameter name can be customized as well. - -Example: -[source,java] ----- -@ApplicationScoped -class SpanBean { - @WithSpan - void span() { - - } +== Exporters - @WithSpan("name") - void spanName() { +=== Default - } +The Quarkus OpenTelemetry extension uses its own signal exporters built on top of Vert.x for optimal performance and maintainability. - @WithSpan(kind = SERVER) - void spanKind() { +The exporter is automatically wired with CDI, that's why the `quarkus.otel.traces.exporter` and `quarkus.otel.metrics.exporter` properties default to `cdi`. - } +The `quarkus.otel.exporter.otlp.protocol` defaults to `grpc` but `http/protobuf` can also be used. - @WithSpan - void spanArgs(@SpanAttribute(value = "arg") String arg) { +NOTE: If you change the protocol, you also need to change the port in the endpoint. The default port for `grpc` is `4317` and for `http/protobuf` is `4318`. - } +=== On Quarkiverse +Additional exporters will be available in the Quarkiverse https://docs.quarkiverse.io/quarkus-opentelemetry-exporter/dev/index.html[quarkus-opentelemetry-exporter] project. - @AddingSpanAttributes - void addArgumentToExistingSpan(@SpanAttribute(value = "arg") String arg) { +Currently, are available the following exporters (may be outdated) for: - } -} ----- +- Legacy Jaeger +- Microsoft Azure +- Google Cloud -=== Available OpenTelemetry CDI injections +Also on Quarkiverse, the https://docs.quarkiverse.io/quarkus-amazon-services/dev/opentelemetry.html[Quarkus AWS SDK has integration with OpenTelemetry]. -As per MicroProfile Telemetry Tracing specification, Quarkus supports the CDI injections of the -following classes: +=== Logging exporter (for debugging) -* `io.opentelemetry.api.OpenTelemetry` -* `io.opentelemetry.api.trace.Tracer` -* `io.opentelemetry.api.trace.Span` -* `io.opentelemetry.api.baggage.Baggage` +You can output all metrics to the console, for debugging/development purposes. -You can inject these classes in any CDI enabled bean. For instance, the `Tracer` is particularly useful to start custom spans: +IMPORTANT: Don't use this in production. -[source,java] +You will need to add the following dependency to your project: +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml ---- -@Inject -Tracer tracer; - -... - -public void tracedWork() { - Span span = tracer.spanBuilder("My custom span") - .setAttribute("attr", "attr.value") - .setParent(Context.current().with(Span.current())) - .setSpanKind(SpanKind.INTERNAL) - .startSpan(); - - // traced work - - span.end(); -} + + io.opentelemetry + opentelemetry-exporter-logging + ---- -=== Quarkus Messaging - Kafka - -When using the Quarkus Messaging extension for Kafka, -we are able to propagate the span into the Kafka Record with: - -[source,java] +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle ---- -TracingMetadata tm = TracingMetadata.withPrevious(Context.current()); -Message out = Message.of(...).withMetadata(tm); +implementation("io.opentelemetry:opentelemetry-exporter-logging") ---- -The above creates a `TracingMetadata` object we can add to the `Message` being produced, -which retrieves the OpenTelemetry `Context` to extract the current span for propagation. - -=== Quarkus Security Events - -Quarkus supports exporting of the xref:security-customization.adoc#observe-security-events[Security events] as OpenTelemetry Span events. -[source,application.properties] +Then, setting the exporter to `logging` in the `application.properties` file: +[source, properties] ---- -quarkus.otel.security-events.enabled=true <1> +quarkus.otel.metrics.exporter=logging <1> +quarkus.otel.metric.export.interval=10000ms <2> +quarkus.otel.traces.exporter=logging <3> ---- -<1> Export Quarkus Security events as OpenTelemetry Span events. -== Exporters +<1> Set the metrics exporter to `logging`. Normally you don't need to set this. The default is `cdi`. +<2> Set the interval to export the metrics. The default is `1m`, which is too long for debugging. +<3> Set the traces exporter to `logging`. Normally you don't need to set this. The default is `cdi`. -=== Default +== Visualizing the data -The Quarkus OpenTelemetry extension uses its own exporter built on top of Vert.x for optimal performance and maintainability. +We recommend the xref:observability-devservices-lgtm.adoc[Getting Started with Grafana-OTel-LGTM]. -The exporter is automatically wired with CDI, that's why the `quarkus.otel.traces.exporter` property defaults to `cdi`. +This provides a Quarkus Dev service using an "all-in-one" https://github.com/grafana/docker-otel-lgtm[Grafana OTel LGTM]. -The `quarkus.otel.exporter.otlp.traces.protocol` default to `grpc` and `http/protobuf` can also be used. +Grafana is used to visualize data, Loki to store logs, Tempo to store traces and Prometheus to store metrics. Also provides and OTel collector to receive the data. -=== On Quarkiverse -Additional exporters will be available in the Quarkiverse https://docs.quarkiverse.io/quarkus-opentelemetry-exporter/dev/index.html[quarkus-opentelemetry-exporter] project. +This provides an easy way to visualize all OpenTelemetry data generated by the application. -[[quarkus-extensions-using-opentelemetry]] -== Quarkus core extensions instrumented with OpenTelemetry tracing - -* https://quarkus.io/extensions/io.quarkus/quarkus-agroal[`quarkus-agroal`] -* https://quarkus.io/guides/grpc-getting-started[`quarkus-grpc`] -* https://quarkus.io/guides/redis[`quarkus-redis-client`] -* https://quarkus.io/extensions/io.quarkus/quarkus-rest-client-jaxrs[`quarkus-rest-client-jaxrs`] -* https://quarkus.io/guides/rest[`quarkus-rest`] -* https://quarkus.io/guides/resteasy[`quarkus-resteasy-jackson`] -* https://quarkus.io/guides/resteasy-client[`quarkus-resteasy-client`] -* https://quarkus.io/guides/scheduler[`quarkus-scheduler`] -* https://quarkus.io/guides/smallrye-graphql[`quarkus-smallrye-graphql`] -* https://quarkus.io/extensions/io.quarkus/quarkus-mongodb-client[`quarkus-mongodb-client`] -* https://quarkus.io/extensions/io.quarkus/quarkus-messaging[`quarkus-messaging`] -** AMQP 1.0 -** RabbitMQ -** Kafka -** Pulsar -* https://quarkus.io/guides/vertx[`quarkus-vertx`] (http requests) +You can also use the xref:logging-exporter-for-debugging[logging exporter] to output all traces and metrics to the console. [[configuration-reference]] == OpenTelemetry Configuration Reference @@ -702,7 +307,4 @@ adding the usual `quarkus.*` prefix. Quarkus OpenTelemetry configuration properties now have the `quarkus.otel.*` prefix. -*The legacy properties* with prefix `quarkus.opentelemetry.*` are currently being mapped to the new ones as a default, during a transition period. See Default column in the details below. - - include::{generated-dir}/config/quarkus-opentelemetry.adoc[leveloffset=+1, opts=optional] 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 ab745941977be..4664cb0d67faf 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 @@ -2,7 +2,6 @@ import static io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem.SPI_ROOT; import static io.quarkus.opentelemetry.runtime.OpenTelemetryRecorder.OPEN_TELEMETRY_DRIVER; -import static io.quarkus.opentelemetry.runtime.OpenTelemetryUtil.*; import static java.util.stream.Collectors.toList; import java.io.IOException; @@ -104,7 +103,7 @@ AdditionalBeanBuildItem ensureProducerIsRetained() { return AdditionalBeanBuildItem.builder() .setUnremovable() .addBeanClasses( - AutoConfiguredOpenTelemetrySdkBuilderCustomizer.ResourceCustomizer.class, + AutoConfiguredOpenTelemetrySdkBuilderCustomizer.TracingResourceCustomizer.class, AutoConfiguredOpenTelemetrySdkBuilderCustomizer.SamplerCustomizer.class, AutoConfiguredOpenTelemetrySdkBuilderCustomizer.TracerProviderCustomizer.class, AutoConfiguredOpenTelemetrySdkBuilderCustomizer.MetricProviderCustomizer.class, diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterBadEndpointTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterBadEndpointTest.java index 7d4048c0501c0..231c31457bec7 100644 --- a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterBadEndpointTest.java +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterBadEndpointTest.java @@ -15,7 +15,7 @@ public class OtlpExporterBadEndpointTest { static final QuarkusUnitTest config = new QuarkusUnitTest() .withEmptyApplication() .overrideConfigKey("quarkus.otel.traces.exporter", "cdi") - .overrideConfigKey("quarkus.otel.exporter.otlp.traces.legacy-endpoint", "httz://nada:zero") + .overrideConfigKey("quarkus.otel.exporter.otlp.endpoint", "httz://nada:zero") .setExpectedException(IllegalArgumentException.class); @Inject 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 3692683973ad0..4c252cb11f7af 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 @@ -16,8 +16,10 @@ public class OtlpExporterConfigTest { static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withEmptyApplication() .overrideConfigKey("quarkus.otel.traces.exporter", "cdi") + .overrideConfigKey("quarkus.otel.exporter.otlp.protocol", "wrong") .overrideConfigKey("quarkus.otel.exporter.otlp.traces.protocol", "http/protobuf") - .overrideConfigKey("quarkus.otel.exporter.otlp.traces.legacy-endpoint", "http://localhost ") + .overrideConfigKey("quarkus.otel.exporter.otlp.traces.endpoint", "http://localhost ") + .overrideConfigKey("quarkus.otel.metrics.exporter", "none") .overrideConfigKey("quarkus.otel.bsp.schedule.delay", "50") .overrideConfigKey("quarkus.otel.bsp.export.timeout", "PT1S"); @@ -26,7 +28,9 @@ public class OtlpExporterConfigTest { @Test void config() { - assertTrue(config.traces().legacyEndpoint().isPresent()); - assertEquals("http://localhost", config.traces().legacyEndpoint().get().trim()); + assertTrue(config.traces().protocol().isPresent()); + assertEquals("http/protobuf", config.traces().protocol().get().trim()); + assertTrue(config.traces().endpoint().isPresent()); + assertEquals("http://localhost", config.traces().endpoint().get().trim()); } } diff --git a/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterOverrideConfigTest.java b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterOverrideConfigTest.java new file mode 100644 index 0000000000000..ff982fcd7de08 --- /dev/null +++ b/extensions/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/exporter/otlp/OtlpExporterOverrideConfigTest.java @@ -0,0 +1,34 @@ +package io.quarkus.opentelemetry.deployment.exporter.otlp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig; +import io.quarkus.test.QuarkusUnitTest; + +public class OtlpExporterOverrideConfigTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withEmptyApplication() + .overrideConfigKey("quarkus.otel.traces.exporter", "cdi") + .overrideConfigKey("quarkus.otel.exporter.otlp.protocol", "http/protobuf") + .overrideConfigKey("quarkus.otel.exporter.otlp.endpoint", "http://localhost ") + .overrideConfigKey("quarkus.otel.bsp.schedule.delay", "50") + .overrideConfigKey("quarkus.otel.bsp.export.timeout", "PT1S"); + + @Inject + OtlpExporterRuntimeConfig config; + + @Test + void config() { + assertTrue(config.traces().protocol().isPresent()); + assertEquals("http/protobuf", config.traces().protocol().get().trim()); + assertTrue(config.traces().endpoint().isPresent()); + assertEquals("http://localhost", config.traces().endpoint().get().trim()); + } +} 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 aa7360206490b..ccc5ec9d172d2 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 @@ -41,7 +41,7 @@ public interface AutoConfiguredOpenTelemetrySdkBuilderCustomizer { void customize(AutoConfiguredOpenTelemetrySdkBuilder builder); @Singleton - final class ResourceCustomizer implements AutoConfiguredOpenTelemetrySdkBuilderCustomizer { + final class TracingResourceCustomizer implements AutoConfiguredOpenTelemetrySdkBuilderCustomizer { private final ApplicationConfig appConfig; private final OTelBuildConfig oTelBuildConfig; @@ -49,7 +49,7 @@ final class ResourceCustomizer implements AutoConfiguredOpenTelemetrySdkBuilderC private final Instance delayedAttributes; private final List resources; - public ResourceCustomizer(ApplicationConfig appConfig, + public TracingResourceCustomizer(ApplicationConfig appConfig, OTelBuildConfig oTelBuildConfig, OTelRuntimeConfig oTelRuntimeConfig, @Any Instance delayedAttributes, @@ -66,7 +66,8 @@ public void customize(AutoConfiguredOpenTelemetrySdkBuilder builder) { builder.addResourceCustomizer(new BiFunction<>() { @Override public Resource apply(Resource existingResource, ConfigProperties configProperties) { - if (oTelBuildConfig.traces().enabled().orElse(TRUE)) { + if (oTelBuildConfig.traces().enabled().orElse(TRUE) || + oTelBuildConfig.metrics().enabled().orElse(TRUE)) { Resource consolidatedResource = existingResource.merge( Resource.create(delayedAttributes.get())); @@ -82,13 +83,7 @@ public boolean test(String sn) { }) .orElse(null); - // must be resolved at startup, once. - String hostname = null; - try { - hostname = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - hostname = "unknown"; - } + String hostname = getHostname(); // Merge resource instances with env attributes Resource resource = resources.stream() @@ -259,4 +254,16 @@ public ConfigProperties otelConfigProperties() { } } } + + private static String getHostname() { + // must be resolved at startup, once. + String hostname = null; + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + hostname = "unknown"; + } + return hostname; + } + } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/HierarchicalOTelConnectionConfigInterceptor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/HierarchicalOTelConnectionConfigInterceptor.java new file mode 100644 index 0000000000000..bd011e5a4c684 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/HierarchicalOTelConnectionConfigInterceptor.java @@ -0,0 +1,92 @@ +package io.quarkus.opentelemetry.runtime.config; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +import jakarta.annotation.Priority; + +import io.smallrye.config.ConfigSourceInterceptorContext; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.FallbackConfigSourceInterceptor; +import io.smallrye.config.Priorities; + +@Priority(Priorities.LIBRARY + 300 + 5) +public class HierarchicalOTelConnectionConfigInterceptor extends FallbackConfigSourceInterceptor { + + // The properties that are shared between the traces and metrics configuration + // string after "quarkus.otel.exporter.otlp." + private final static List PROPERTY_NAMES = List.of( + "endpoint", + "headers", + "compression", + "timeout", + "protocol", + "key-cert.keys", + "key-cert.certs", + "trust-cert.certs", + "tls-configuration-name", + "proxy-options.enabled", + "proxy-options.username", + "proxy-options.password", + "proxy-options.port", + "proxy-options.host"); + + static final String BASE = "quarkus.otel.exporter.otlp."; + static final String TRACES = BASE + "traces."; + static final String METRICS = BASE + "metrics."; + + private static final MappingFunction mappingFunction = new MappingFunction(); + + public HierarchicalOTelConnectionConfigInterceptor() { + super(mappingFunction); + } + + @Override + public Iterator iterateNames(final ConfigSourceInterceptorContext context) { + Set names = new HashSet<>(); + Iterator namesIterator = context.iterateNames(); + while (namesIterator.hasNext()) { + String name = namesIterator.next(); + String fallback = mappingFunction.apply(name); + // We only include the used property, so if it is a fallback (not mapped), it will be reported as unknown + if (fallback != null) { + ConfigValue nameValue = context.proceed(name); + ConfigValue fallbackValue = context.proceed(fallback); + if (nameValue == null) { + names.add(fallback); + } else if (fallbackValue == null) { + names.add(name); + } else if (nameValue.getConfigSourceOrdinal() >= fallbackValue.getConfigSourceOrdinal()) { + names.add(name); + } else { + names.add(fallback); + } + } else { + names.add(name); + } + } + return names.iterator(); + } + + static class MappingFunction implements Function { + @Override + public String apply(String name) { + if (name.startsWith(TRACES)) { + String property = name.substring(TRACES.length()); + if (PROPERTY_NAMES.contains(property)) { + return BASE + property; + } + } + if (name.startsWith(METRICS)) { + String property = name.substring(METRICS.length()); + if (PROPERTY_NAMES.contains(property)) { + return BASE + property; + } + } + return name; + } + } +} 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 index 8c6ac92c6c330..0c9075b32c9bf 100644 --- 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 @@ -1,16 +1,12 @@ 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 { @@ -18,10 +14,7 @@ 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(); /** @@ -41,7 +34,6 @@ public interface OtlpExporterConfig { * 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(); /** @@ -50,19 +42,16 @@ public interface OtlpExporterConfig { *

* 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(); /** @@ -99,7 +88,6 @@ interface ProxyConfig { /** * Set proxy port. */ - @ConfigDocDefault("3128") OptionalInt port(); /** 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 d2d308c841296..111e6550e014b 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 @@ -1,11 +1,14 @@ package io.quarkus.opentelemetry.runtime.config.runtime.exporter; +import java.time.Duration; +import java.util.List; import java.util.Optional; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithDefault; +import io.smallrye.config.WithName; /** * From quarkus.otel.exporter.otlp.traces.endpoint - * is recommended. + * Sets the OTLP endpoint for connecting all signals. If unset, defaults to + * {@value OtlpExporterRuntimeConfig#DEFAULT_GRPC_BASE_URI}. */ + @Override @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 + */ + @Override + Optional> headers(); + + /** + * Sets the method used to compress payloads. If unset, compression is disabled. Currently + * supported compression methods include `gzip` and `none`. + */ + @Override + 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. + */ + @Override + @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. + */ + @Override + @WithDefault(OtlpExporterConfig.Protocol.GRPC) + Optional protocol(); + + /** + * Key/cert configuration in the PEM format. + */ + @Override + @WithName("key-cert") + OtlpExporterConfig.KeyCert keyCert(); + + /** + * Trust configuration in the PEM format. + */ + @Override + @WithName("trust-cert") + OtlpExporterConfig.TrustCert trustCert(); + + /** + * The name of the TLS configuration to use. + *

+ * If not set and the default TLS configuration is configured ({@code quarkus.tls.*}) then that will be used. + * If a name is configured, it uses the configuration from {@code quarkus.tls..*} + * If a name is configured, but no TLS configuration is found with that name then an error will be thrown. + */ + @Override + Optional tlsConfigurationName(); + + /** + * Set proxy options + */ + @Override + OtlpExporterConfig.ProxyConfig proxyOptions(); + /** * OTLP traces 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 12eac3685da91..4371c78b44111 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 @@ -1,22 +1,7 @@ package io.quarkus.opentelemetry.runtime.config.runtime.exporter; -import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig.DEFAULT_GRPC_BASE_URI; - -import java.util.Optional; - import io.quarkus.runtime.annotations.ConfigGroup; -import io.smallrye.config.WithDefault; -import io.smallrye.config.WithName; @ConfigGroup public interface OtlpExporterTracesConfig extends OtlpExporterConfig { - - /** - * See {@link OtlpExporterTracesConfig#endpoint} - */ - // @WithConverter(TrimmedStringConverter.class) - @Deprecated - @WithName("legacy-endpoint") - @WithDefault(DEFAULT_GRPC_BASE_URI) - Optional legacyEndpoint(); } 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 607c5f03ab95e..4d7d4956b61d5 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,6 +1,5 @@ package io.quarkus.opentelemetry.runtime.exporter.otlp; -import static io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram; import static io.quarkus.opentelemetry.runtime.config.build.ExporterType.Constants.OTLP_VALUE; 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; @@ -67,13 +66,12 @@ public class OTelExporterRecorder { public static final String BASE2EXPONENTIAL_AGGREGATION_NAME = AggregationUtil .aggregationName(Aggregation.base2ExponentialBucketHistogram()); - public static final String EXPLICIT_BUCKET_AGGREGATION_NAME = AggregationUtil.aggregationName(explicitBucketHistogram()); public Function, LateBoundBatchSpanProcessor> batchSpanProcessorForOtlp( OTelRuntimeConfig otelRuntimeConfig, OtlpExporterRuntimeConfig exporterRuntimeConfig, Supplier vertx) { - URI baseUri = getBaseUri(exporterRuntimeConfig); // do the creation and validation here in order to preserve backward compatibility + URI baseUri = getTracesUri(exporterRuntimeConfig); // do the creation and validation here in order to preserve backward compatibility return new Function<>() { @Override public LateBoundBatchSpanProcessor apply( @@ -180,7 +178,7 @@ public Function, MetricExporter> crea OtlpExporterRuntimeConfig exporterRuntimeConfig, Supplier vertx) { - final URI baseUri = getBaseUri(exporterRuntimeConfig); + final URI baseUri = getMetricsUri(exporterRuntimeConfig); return new Function<>() { @Override @@ -311,22 +309,37 @@ private static Map populateTracingExportHttpHeaders(OtlpExporter return headersMap; } - private URI getBaseUri(OtlpExporterRuntimeConfig exporterRuntimeConfig) { - String endpoint = resolveEndpoint(exporterRuntimeConfig).trim(); // FIXME must be signal independent + private URI getTracesUri(OtlpExporterRuntimeConfig exporterRuntimeConfig) { + String endpoint = resolveTraceEndpoint(exporterRuntimeConfig); if (endpoint.isEmpty()) { return null; } return ExporterBuilderUtil.validateEndpoint(endpoint); } - static String resolveEndpoint(final OtlpExporterRuntimeConfig runtimeConfig) { - String endpoint = runtimeConfig.traces().legacyEndpoint() + private URI getMetricsUri(OtlpExporterRuntimeConfig exporterRuntimeConfig) { + String endpoint = resolveTraceEndpoint(exporterRuntimeConfig); + if (endpoint.isEmpty()) { + return null; + } + return ExporterBuilderUtil.validateEndpoint(endpoint); + } + + static String resolveTraceEndpoint(final OtlpExporterRuntimeConfig runtimeConfig) { + String endpoint = runtimeConfig.traces().endpoint() + .filter(OTelExporterRecorder::excludeDefaultEndpoint) + .orElse(runtimeConfig.endpoint() + .filter(OTelExporterRecorder::excludeDefaultEndpoint) + .orElse(DEFAULT_GRPC_BASE_URI)); + return endpoint.trim(); + } + + static String resolveMetricEndpoint(final OtlpExporterRuntimeConfig runtimeConfig) { + String endpoint = runtimeConfig.metrics().endpoint() .filter(OTelExporterRecorder::excludeDefaultEndpoint) - .orElse(runtimeConfig.traces().endpoint() + .orElse(runtimeConfig.endpoint() .filter(OTelExporterRecorder::excludeDefaultEndpoint) - .orElse(runtimeConfig.endpoint() - .filter(OTelExporterRecorder::excludeDefaultEndpoint) - .orElse(DEFAULT_GRPC_BASE_URI))); + .orElse(DEFAULT_GRPC_BASE_URI)); return endpoint.trim(); } diff --git a/extensions/opentelemetry/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor b/extensions/opentelemetry/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor new file mode 100644 index 0000000000000..55782f5c4d6ec --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor @@ -0,0 +1 @@ +io.quarkus.opentelemetry.runtime.config.HierarchicalOTelConnectionConfigInterceptor diff --git a/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/config/HierarchicalOTelConnectionConfigInterceptorTest.java b/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/config/HierarchicalOTelConnectionConfigInterceptorTest.java new file mode 100644 index 0000000000000..e509ebb35805a --- /dev/null +++ b/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/config/HierarchicalOTelConnectionConfigInterceptorTest.java @@ -0,0 +1,62 @@ +package io.quarkus.opentelemetry.runtime.config; + +import static io.quarkus.opentelemetry.runtime.config.HierarchicalOTelConnectionConfigInterceptor.BASE; +import static io.quarkus.opentelemetry.runtime.config.HierarchicalOTelConnectionConfigInterceptor.METRICS; +import static io.quarkus.opentelemetry.runtime.config.HierarchicalOTelConnectionConfigInterceptor.MappingFunction; +import static io.quarkus.opentelemetry.runtime.config.HierarchicalOTelConnectionConfigInterceptor.TRACES; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +class HierarchicalOTelConnectionConfigInterceptorTest { + + private final static Map FALLBACKS = new HashMap<>(); + + static { + FALLBACKS.put(TRACES + "endpoint", BASE + "endpoint"); + FALLBACKS.put(METRICS + "endpoint", BASE + "endpoint"); + FALLBACKS.put(TRACES + "headers", BASE + "headers"); + FALLBACKS.put(METRICS + "headers", BASE + "headers"); + FALLBACKS.put(TRACES + "compression", BASE + "compression"); + FALLBACKS.put(METRICS + "compression", BASE + "compression"); + FALLBACKS.put(TRACES + "timeout", BASE + "timeout"); + FALLBACKS.put(METRICS + "timeout", BASE + "timeout"); + FALLBACKS.put(TRACES + "protocol", BASE + "protocol"); + FALLBACKS.put(METRICS + "protocol", BASE + "protocol"); + FALLBACKS.put(TRACES + "key-cert.keys", BASE + "key-cert.keys"); + FALLBACKS.put(METRICS + "key-cert.keys", BASE + "key-cert.keys"); + FALLBACKS.put(TRACES + "key-cert.certs", BASE + "key-cert.certs"); + FALLBACKS.put(METRICS + "key-cert.certs", BASE + "key-cert.certs"); + FALLBACKS.put(TRACES + "trust-cert.certs", BASE + "trust-cert.certs"); + FALLBACKS.put(METRICS + "trust-cert.certs", BASE + "trust-cert.certs"); + FALLBACKS.put(TRACES + "tls-configuration-name", BASE + "tls-configuration-name"); + FALLBACKS.put(METRICS + "tls-configuration-name", BASE + "tls-configuration-name"); + FALLBACKS.put(TRACES + "proxy-options.enabled", BASE + "proxy-options.enabled"); + FALLBACKS.put(METRICS + "proxy-options.enabled", BASE + "proxy-options.enabled"); + FALLBACKS.put(TRACES + "proxy-options.username", BASE + "proxy-options.username"); + FALLBACKS.put(METRICS + "proxy-options.username", BASE + "proxy-options.username"); + FALLBACKS.put(TRACES + "proxy-options.password", BASE + "proxy-options.password"); + FALLBACKS.put(METRICS + "proxy-options.password", BASE + "proxy-options.password"); + FALLBACKS.put(TRACES + "proxy-options.port", BASE + "proxy-options.port"); + FALLBACKS.put(METRICS + "proxy-options.port", BASE + "proxy-options.port"); + FALLBACKS.put(TRACES + "proxy-options.host", BASE + "proxy-options.host"); + FALLBACKS.put(METRICS + "proxy-options.host", BASE + "proxy-options.host"); + } + + @Test + void testMapping() { + MappingFunction mappingFunction = new MappingFunction(); + for (String propertyName : FALLBACKS.keySet()) { + assertEquals(FALLBACKS.get(propertyName), mappingFunction.apply(propertyName)); + } + } + + @Test + void testNotMapping() { + MappingFunction mappingFunction = new MappingFunction(); + assertEquals("something", mappingFunction.apply("something")); + } +} diff --git a/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/exporter/otlp/HttpClientOptionsConsumerTest.java b/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/exporter/otlp/HttpClientOptionsConsumerTest.java index 16b7a6c815499..907c467ad6900 100644 --- a/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/exporter/otlp/HttpClientOptionsConsumerTest.java +++ b/extensions/opentelemetry/runtime/src/test/java/io/quarkus/opentelemetry/runtime/exporter/otlp/HttpClientOptionsConsumerTest.java @@ -56,11 +56,6 @@ public Optional endpoint() { return Optional.of("http://localhost:4317"); } - @Override - public Optional legacyEndpoint() { - return Optional.empty(); - } - @Override public Optional> headers() { return Optional.empty(); 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 7275aecf8b81b..625baaa74a337 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 @@ -18,67 +18,132 @@ class OtlpExporterProviderTest { @Test - public void resolveEndpoint_legacyWins() { + public void resolveTraceEndpoint_newWins() { + assertEquals("http://localhost:2222/", + OTelExporterRecorder.resolveTraceEndpoint(createOtlpExporterRuntimeConfig( + "http://localhost:1111/", + "http://localhost:2222/"))); + } + + @Test + public void resolveTraceEndpoint_globalWins() { assertEquals("http://localhost:1111/", - OTelExporterRecorder.resolveEndpoint(createOtlpExporterRuntimeConfig( - DEFAULT_GRPC_BASE_URI, + OTelExporterRecorder.resolveTraceEndpoint(createOtlpExporterRuntimeConfig( "http://localhost:1111/", + DEFAULT_GRPC_BASE_URI))); + } + + @Test + public void resolveTraceEndpoint_legacyTraceWins() { + assertEquals("http://localhost:2222/", + OTelExporterRecorder.resolveTraceEndpoint(createOtlpExporterRuntimeConfig( + DEFAULT_GRPC_BASE_URI, "http://localhost:2222/"))); } @Test - public void resolveEndpoint_newWins() { + public void resolveTraceEndpoint_legacyGlobalWins() { + assertEquals(DEFAULT_GRPC_BASE_URI, + OTelExporterRecorder.resolveTraceEndpoint(createOtlpExporterRuntimeConfig( + DEFAULT_GRPC_BASE_URI, + null))); + } + + @Test + public void resolveTraceEndpoint_testIsSet() { + assertEquals(DEFAULT_GRPC_BASE_URI, + OTelExporterRecorder.resolveTraceEndpoint(createOtlpExporterRuntimeConfig( + null, + null))); + } + + @Test + public void resolveMetricEndpoint_newWins() { assertEquals("http://localhost:2222/", - OTelExporterRecorder.resolveEndpoint(createOtlpExporterRuntimeConfig( + OTelExporterRecorder.resolveMetricEndpoint(createOtlpExporterRuntimeConfig( "http://localhost:1111/", - DEFAULT_GRPC_BASE_URI, "http://localhost:2222/"))); } @Test - public void resolveEndpoint_globalWins() { + public void resolveMetricEndpoint_globalWins() { assertEquals("http://localhost:1111/", - OTelExporterRecorder.resolveEndpoint(createOtlpExporterRuntimeConfig( + OTelExporterRecorder.resolveMetricEndpoint(createOtlpExporterRuntimeConfig( "http://localhost:1111/", - DEFAULT_GRPC_BASE_URI, DEFAULT_GRPC_BASE_URI))); } @Test - public void resolveEndpoint_legacyTraceWins() { + public void resolveMetricEndpoint_legacyTraceWins() { assertEquals("http://localhost:2222/", - OTelExporterRecorder.resolveEndpoint(createOtlpExporterRuntimeConfig( + OTelExporterRecorder.resolveMetricEndpoint(createOtlpExporterRuntimeConfig( DEFAULT_GRPC_BASE_URI, - null, "http://localhost:2222/"))); } @Test - public void resolveEndpoint_legacyGlobalWins() { + public void resolveMetricEndpoint_legacyGlobalWins() { assertEquals(DEFAULT_GRPC_BASE_URI, - OTelExporterRecorder.resolveEndpoint(createOtlpExporterRuntimeConfig( + OTelExporterRecorder.resolveMetricEndpoint(createOtlpExporterRuntimeConfig( DEFAULT_GRPC_BASE_URI, - null, null))); } @Test - public void resolveEndpoint_testIsSet() { + public void resolveMetricEndpoint_testIsSet() { assertEquals(DEFAULT_GRPC_BASE_URI, - OTelExporterRecorder.resolveEndpoint(createOtlpExporterRuntimeConfig( - null, + OTelExporterRecorder.resolveMetricEndpoint(createOtlpExporterRuntimeConfig( null, null))); } - private OtlpExporterRuntimeConfig createOtlpExporterRuntimeConfig(String exporterGlobal, String legacyTrace, - String newTrace) { + private OtlpExporterRuntimeConfig createOtlpExporterRuntimeConfig(String exporterGlobal, String newTrace) { return new OtlpExporterRuntimeConfig() { @Override public Optional endpoint() { return Optional.ofNullable(exporterGlobal); } + @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 null; + } + + @Override + public TrustCert trustCert() { + return null; + } + + @Override + public Optional tlsConfigurationName() { + return Optional.empty(); + } + + @Override + public ProxyConfig proxyOptions() { + return null; + } + @Override public OtlpExporterTracesConfig traces() { return new OtlpExporterTracesConfig() { @@ -87,11 +152,6 @@ public Optional endpoint() { return Optional.ofNullable(newTrace); } - @Override - public Optional legacyEndpoint() { - return Optional.ofNullable(legacyTrace); - } - @Override public Optional> headers() { return Optional.empty();