diff --git a/docs/images/telemetry/telemetry-custom-jaeger.png b/docs/images/telemetry/telemetry-custom-jaeger.png new file mode 100644 index 00000000000..0fca937ce92 Binary files /dev/null and b/docs/images/telemetry/telemetry-custom-jaeger.png differ diff --git a/docs/images/telemetry/telemetry-general.png b/docs/images/telemetry/telemetry-general.png new file mode 100644 index 00000000000..eb7b81d1e95 Binary files /dev/null and b/docs/images/telemetry/telemetry-general.png differ diff --git a/docs/images/telemetry/telemetry-greeting-jaeger.png b/docs/images/telemetry/telemetry-greeting-jaeger.png new file mode 100644 index 00000000000..42c6d4fb2f2 Binary files /dev/null and b/docs/images/telemetry/telemetry-greeting-jaeger.png differ diff --git a/docs/images/telemetry/telemetry-outbound-jaeger.png b/docs/images/telemetry/telemetry-outbound-jaeger.png new file mode 100644 index 00000000000..55785161c93 Binary files /dev/null and b/docs/images/telemetry/telemetry-outbound-jaeger.png differ diff --git a/docs/mp/telemetry.adoc b/docs/mp/telemetry.adoc new file mode 100644 index 00000000000..f4545834241 --- /dev/null +++ b/docs/mp/telemetry.adoc @@ -0,0 +1,408 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2023 Oracle and/or its affiliates. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +/////////////////////////////////////////////////////////////////////////////// + += Telemetry +:description: Helidon MP Telemetry Support +:feature-name: MicroProfile Telemetry +:keywords: helidon, telemetry, microprofile, micro-profile +:microprofile-bundle: true +:rootdir: {docdir}/.. + +include::{rootdir}/includes/mp.adoc[] + +== Contents + +- <> +- <> +- <> +- <> +- <> +- <> +- <> + +== Overview + +include::{rootdir}/includes/dependencies.adoc[] + +[source,xml] +---- + + io.helidon.microprofile.telemetry + helidon-microprofile-telemetry + +---- + +== Usage + +link:https://opentelemetry.io/[OpenTelemetry] comprises a collection of APIs, SDKs, integration tools, and other software components intended to facilitate the generation and control of telemetry data, including traces, metrics, and logs. In an environment where distributed tracing is enabled via OpenTelemetry (which combines OpenTracing and OpenCensus), this specification establishes the necessary behaviors for MicroProfile applications to participate seamlessly. + +MicroProfile Telemetry 1.0 allows for the exportation of the data it collects to Jaeger or Zipkin and to other systems using variety of exporter. + +In a distributed tracing system, *traces* are used to capture a series of requests and are composed of multiple *spans* that represent individual operations within those requests. Each *span* includes a name, timestamps, and metadata that provide insights into the corresponding operation. + +*Context* is included in each span to identify the specific request that it belongs to. This context information is crucial for tracking requests across various components in a distributed system, enabling developers to trace a single request as it traverses through multiple services. + +Finally, *exporters* are responsible for transmitting the collected trace data to a backend service for monitoring and visualization. This enables developers to gain a comprehensive understanding of the system's behavior and detect any issues or bottlenecks that may arise. + +image::telemetry/telemetry-general.png[General understanding of OpenTelemetry Tracing] + +There are two ways to work with Telemetry, using: + +- Automatic Instrumentation +- Manual Instrumentation + +For Automatic Instrumentation, OpenTelemetry provides a JavaAgent. The Tracing API allows for the automatic participation in distributed tracing of Jakarta RESTful Web Services (both server and client) as well as MicroProfile REST Clients, without requiring any modifications to the code. This is achieved through automatic instrumentation. + +For Manual Instrumentation there is a set of annotations and access to OpenTelemetry API. + +`@WithSpan` - By adding this annotation to a method in any Jakarta CDI aware bean, a new Span will be created and any necessary connections to the current Trace context will be established. Additionally, the `SpanAttribute` annotation can be used to mark method parameters that should be included in the Trace. + +Helidon provides full access to OpenTelemetry Tracing API: + +* `io.opentelemetry.api.OpenTelemetry` +* `io.opentelemetry.api.trace.Tracer` +* `io.opentelemetry.api.trace.Span` +* `io.opentelemetry.api.baggage.Baggage` + +Accessing and using these objects can be done as follows. For span: + +.Span sample +[source, java] +---- +@ApplicationScoped +class HelidonBean { + + @WithSpan <1> + void doSomethingWithinSpan() { + // do something here + } + + @WithSpan("name", kind = SpanKind.SERVER, @SpanAttribute(value = "arg") String arg) <2> + void complexSpan() { + // do something here + } +} +---- +<1> Simple `@WithSpan` annotation usage. +<2> Additional attributes can be set to the annotation. + +You can also inject OpenTelemetry `Tracer` using the regular `@Inject` annotation and use `SpanBuilder` to manually create, star, and stop Spans. + +.SpanBuilder usage +[source, java] +---- +@Path("/") +public class HelidonEndpoint { + + @Inject + Tracer tracer; <1> + + @GET + @Path("/span") + public Response span() { + Span span = tracer.spanBuilder("new") <2> + .setSpanKind(SpanKind.CLIENT) + .setAttribute("someAttribute", "someValue") + .startSpan(); + + span.end(); + + return Response.ok().build(); + } +} +---- +<1> Inject `Tracer`. +<2> Use `Tracer.spanBuilder` to create and start new `Span`. + +To obtain the current span, it can be injected by CDI. The current span can also be obtained using the static method `Span.current()`. + +.Inject the current span +[source, java] +---- +@Path("/") +public class HelidonEndpoint { + @Inject + Span span; <1> + + @GET + @Path("/current") + public Response currentSpan() { + return Response.ok(span.getAttribute("someAttribute")).build(); <2> + } + + + @GET + @Path("/current/static") + public Response currentSpanStatic() { + return Response.ok(Span.current().getAttribute("someAttribute")).build(); <3> + } +} +---- +<1> Inject the current span. +<2> Use the injected span. +<3> Use `Span.current()` to access the current span. + +The same functionality is available for the `Baggage` API: + +.Inject the current baggage +[source, java] +---- +@Path("/") +public class HelidonEndpoint { + @Inject + Baggage baggage; <1> + + @GET + @Path("/current") + public Response currentBaggage() { + return Response.ok(baggage.get("baggageKey")).build(); <2> + } + + + @GET + @Path("/current/static") + public Response currentBaggageStatic() { + return Response.ok(Baggage.current().get("baggageKey")).build(); <3> + } +} +---- +<1> Inject the current baggage. +<2> Use the injected baggage. +<3> Use `Baggage.current()` to access the current baggage. + + +== Configuration + +IMPORTANT: MicroProfile Telemetry is not activated by default. To activate this feature, you need to specify the configuration `otel.sdk.disabled=false` in one of the MicroProfile Config or other config sources. + +To configure OpenTelemetry, MicroProfile Config must be used, and the configuration properties outlined in the following sections must be followed: + +- link:https://github.com/open-telemetry/opentelemetry-java/tree/v1.19.0/sdk-extensions/autoconfigure[OpenTelemetry SDK Autoconfigure] (excluding properties related to Metrics and Logging) +- link:https://opentelemetry.io/docs/instrumentation/java/manual/[Manual Instrumentation] + +Please consult with the links above for all configurations properties usage. + +The property should be declared in `microprofile-config.properties` file in order to be processed correctly. + + +=== OpenTelemetry Java Agent + +The OpenTelemetry Java Agent may influence the work of MicroProfile Telemetry, on how the objects are created and configured. Helidon will do "best effort" detect the use of the agent. But, if there is a decision to run the Helidon app with the agent, a configuration property should be set: + +`otel.agent.present=true` + +This way, Helidon will explicitly get all the configuration and objects from the Agent, thus allowing correct Span hierarchy settings. + +== Examples + +This guide demonstrates how to incorporate MicroProfile Telemetry into Helidon and provides illustrations of how to view traces. Jaeger is employed in all the examples, and the Jaeger UI is used to view the traces. + +=== Set Up Jaeger + +For the examples Jaeger will be used for gathering of the tracing information. + +.Run Jaeger in a docker container. +[source, bash] +---- +docker run -d --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -e COLLECTOR_OTLP_ENABLED=true \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.41 +---- + +All the tracing information gathered from the examples runs is accessible from the browser in the Jaeger UI under link:http://localhost:16686/[] + +=== Enable MicroProfile Telemetry in Helidon Application + +Together with Helidon Telemetry dependency, an OpenTelemetry Exporter dependency should be added to project's pom.xml file. + +[source,xml] +---- + + io.helidon.microprofile.telemetry + helidon-microprofile-telemetry <1> + + + io.opentelemetry + opentelemetry-exporter-jaeger <2> + +---- +<1> Helidon Telemetry dependency. +<2> OpenTelemetry Jaeger exporter. + +Add these lines to `META-INF/microprofile-config.properties`: + +.MicroProfile Telemetry properties +[source,properties] +---- +otel.sdk.disabled=false <1> +otel.traces.exporter=jaeger <2> +otel.exporter.name=greeting-service <3> +---- +<1> Enable MicroProfile Telemetry. +<2> Set exporter to Jaeger. +<3> Name of our service. + +Here we enable MicroProfile Telemetry, set tracer to "jaeger" and give a name, which will be used to identify our service in the tracer. + +[NOTE] +==== + +For this example, you will use Jaeger to manage data tracing. If you prefer to use Zipkin, please set `otel.traces.exporter` property to "zipkin". For more information using about Zipkin, see link:https://zipkin.io/[]. Also a corresponding Maven dependency for the exporter should be added: +---- + + io.opentelemetry + opentelemetry-exporter-zipkin + +---- +==== + + +=== Tracing at Method Level + +To create simple services, use `@WithSpan` and `Tracer` to create span and let MicroProfile OpenTelemetry handle them. + +[source, java] +---- +@Path("/greet") +public class GreetResource { + + @GET + @WithSpan("default") <1> + public String getDefaultMessage() { + return "Hello World"; + } +} +---- +<1> Use of `@WithSpan` with name "default". + +Now let's call the Greeting endpoint: + +[source,bash] +---- +curl localhost:8080/greet +Hello World +---- + +Next, launch the Jaeger UI at link:http://localhost:16686/[]. The expected output is: + +image::telemetry/telemetry-greeting-jaeger.png[Greeting service tracing output] + +.Custom method +[source,java] +---- +@Inject +private Tracer tracer; <1> + +@GET +@Path("custom") +@Produces(MediaType.APPLICATION_JSON) +@WithSpan <2> +public JsonObject useCustomSpan(){ + Span span = tracer.spanBuilder("custom") <3> + .setSpanKind(SpanKind.INTERNAL) + .setAttribute("attribute", "value") + .startSpan(); + span.end(); <4> + + return JSON.createObjectBuilder() + .add("Custom Span", span.toString()) + .build(); +} +---- +<1> Inject Opentelemetry `Tracer`. +<2> Create Span around the method `useCustomSpan()`. +<3> Create a custom `INTERNAL` span and start it. +<4> End the custom span. + +Let us call the custom endpoint: + +[source,bash] +---- +curl localhost:8080/greeting/custom +---- + +Again you can launch the Jaeger UI at link:http://localhost:16686/[]. The expected output is: + +image::telemetry/telemetry-custom-jaeger.png[Custom span usage] + +Now let us use multiple services calls. In the example below our main service will call the `secondary` services. Each method in each service will be annotated with `@WithSpan` annotation. + +.Outbound method +[source,java] +---- +@Uri("http://localhost:8081/secondary") +private WebTarget target; <1> + +@GET +@Path("/outbound") +@WithSpan("outbound") <2> +public String outbound() { + return target.request().accept(MediaType.TEXT_PLAIN).get(String.class); <3> +} +---- +<1> Inject `WebTarget` pointing to Secondary service. +<2> Wrap method using `WithSpan`. +<3> Call the secondary service. + +The secondary service is very simple, it has only one method, which is also annotated with `@WithSpan`. + +.Secondary service +[source, java] +---- +@GET +@WithSpan <1> +public String getSecondaryMessage() { + return "Secondary"; <2> +} +---- +<1> Wrap method in a span. +<2> Return a string. + +Let us call the _Outbound_ endpoint: + +[source,bash] +---- +curl localhost:8080/greet/outbound +Secondary +---- + +The `greeting-service` call `secondary-service`. Each service will create Spans with corresponding names, and a service class hierarchy will be created. + +Launch the Jaeger UI at link:http://localhost:16686/[] to see the expected output (shown below). + +image::telemetry/telemetry-outbound-jaeger.png[Secondary service outbound call] + + +== Additional Information + + +== Reference + +* link:https://download.eclipse.org/microprofile/microprofile-telemetry-1.0/tracing/microprofile-telemetry-tracing-spec-1.0.pdf[MicroProfile Telemetry Specification] +* link:https://opentelemetry.io/docs/[OpenTelemetry Documentation] \ No newline at end of file