Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4.x] - MP OpenTelemetry and Helidon Tracing API #8073

Merged
merged 18 commits into from
Dec 1, 2023
Merged
75 changes: 70 additions & 5 deletions docs/mp/telemetry.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ For Automatic Instrumentation, OpenTelemetry provides a JavaAgent. The Tracing A

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.
`@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:

Expand Down Expand Up @@ -100,7 +100,10 @@ class HelidonBean {
<1> Simple `@WithSpan` annotation usage.
<2> Additional attributes can be set on a method.

You can also inject OpenTelemetry `Tracer` using the regular `@Inject` annotation and use `SpanBuilder` to manually create, star, and stop Spans.

=== Working With Tracers

You can inject OpenTelemetry `Tracer` using the regular `@Inject` annotation and use `SpanBuilder` to manually create, star and stop spans.

.SpanBuilder usage
[source, java]
Expand Down Expand Up @@ -128,6 +131,66 @@ public class HelidonEndpoint {
<1> Inject `Tracer`.
<2> Use `Tracer.spanBuilder` to create and start new `Span`.

Helidon Microprofile Telemetry is integrated with xref:tracing.adoc[Helidon Tracing API]. This means that both APIs can be mixed, and all parent hierarchies will be kept. In the case below, `@WithSpan` annotated method is mixed with manually created `io.helidon.tracing.Span`:

.Inject Helidon Tracer
[source, java]
----
private io.helidon.tracing.Tracer helidonTracerInjected;

@Inject
GreetResource(io.helidon.tracing.Tracer helidonTracerInjected) {
this.helidonTracerInjected = helidonTracerInjected; <1>
}

@GET
@Path("mixed_injected")
@Produces(MediaType.APPLICATION_JSON)
@WithSpan("mixed_parent_injected")
public GreetingMessage mixedSpanInjected() {
io.helidon.tracing.Span mixedSpan = helidonTracerInjected.spanBuilder("mixed_injected") <2>
.kind(io.helidon.tracing.Span.Kind.SERVER)
.tag("attribute", "value")
.start();
mixedSpan.end();

return new GreetingMessage("Mixed Span Injected" + span);
}
----
<1> Inject `io.helidon.tracing.Tracer`.
<2> Use the injected tracer to create `io.helidon.tracing.Span` using the `spanBuilder()` method.

The span is then started and ended manually. Span parent relations will be preserved. This means that span named "mixed_injected" with have parent span named "mixed_parent_injected", which will have parent span named "mixed_injected".

Another option is to use the Global Tracer:

.Obtain the Global tracer
[source, java]
----
@GET
@Path("mixed")
@Produces(MediaType.APPLICATION_JSON)
@WithSpan("mixed_parent")
public GreetingMessage mixedSpan() {

io.helidon.tracing.Tracer helidonTracer = io.helidon.tracing.Tracer.global(); <1>
io.helidon.tracing.Span mixedSpan = helidonTracer.spanBuilder("mixed") <2>
.kind(io.helidon.tracing.Span.Kind.SERVER)
.tag("attribute", "value")
.start();
mixedSpan.end();

return new GreetingMessage("Mixed Span" + span);
}
----
<1> Obtain tracer using the `io.helidon.tracing.Tracer.global()` method;
<2> Use the created tracer to create a span.

The span is then started and ended manually. Span parent relations will be preserved.


=== Working With Spans

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
Expand Down Expand Up @@ -156,6 +219,8 @@ public class HelidonEndpoint {
<2> Use the injected span.
<3> Use `Span.current()` to access the current span.

=== Working With Baggage

The same functionality is available for the `Baggage` API:

.Inject the current baggage
Expand Down Expand Up @@ -205,7 +270,7 @@ The OpenTelemetry Java Agent may influence the work of MicroProfile Telemetry, o

`otel.agent.present=true`

This way, Helidon will explicitly get all the configuration and objects from the Agent, thus allowing correct Span hierarchy settings.
This way, Helidon will explicitly get all the configuration and objects from the Agent, thus allowing correct span hierarchy settings.

== Examples

Expand Down Expand Up @@ -335,7 +400,7 @@ public JsonObject useCustomSpan(){
}
----
<1> Inject Opentelemetry `Tracer`.
<2> Create Span around the method `useCustomSpan()`.
<2> Create a span around the method `useCustomSpan()`.
<3> Create a custom `INTERNAL` span and start it.
<4> End the custom span.

Expand Down Expand Up @@ -391,7 +456,7 @@ 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.
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).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import jakarta.ws.rs.core.MediaType;
import org.glassfish.jersey.server.Uri;


/**
* A simple JAX-RS resource to greet you. Examples:
*
Expand All @@ -40,6 +39,8 @@
*
* Call secondary service:
* curl -X GET http://localhost:8080/greet/outbound
*
* Explore traces in Jaeger UI.
*/
@Path("/greet")
public class GreetResource {
Expand Down Expand Up @@ -85,6 +86,7 @@ public GreetingMessage useCustomSpan() {

return new GreetingMessage("Custom Span" + span);
}

/**
* Get Span info.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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.
*/

package io.helidon.examples.microprofile.telemetry;

import io.opentelemetry.instrumentation.annotations.WithSpan;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

/**
* A simple JAX-RS resource to use `io.opentelemetry` and `io.helidon.api` in mixed mode. Examples:
*
* Get mixed traces with Global tracer:
* curl -X GET http://localhost:8080/mixed
*
* Get mixed traces with an injected Helidon tracer:
* curl -X GET http://localhost:8080/mixed/injected
*
* Explore traces in Jaeger UI.
*/
@Path("/mixed")
public class MixedTelemetryGreetResource {

private io.helidon.tracing.Tracer helidonTracerInjected;

@Inject
MixedTelemetryGreetResource(io.helidon.tracing.Tracer helidonTracerInjected) {
this.helidonTracerInjected = helidonTracerInjected;
}


/**
* Create a helidon mixed span using Helidon Global tracer.
* @return {@link GreetingMessage}
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
@WithSpan("mixed_parent")
public GreetingMessage mixedSpan() {

io.helidon.tracing.Tracer helidonTracer = io.helidon.tracing.Tracer.global();
io.helidon.tracing.Span mixedSpan = helidonTracer.spanBuilder("mixed_inner")
.kind(io.helidon.tracing.Span.Kind.SERVER)
.tag("attribute", "value")
.start();
mixedSpan.end();

return new GreetingMessage("Mixed Span");
}

/**
* Create a helidon mixed span using injected Helidon Tracer.
* @return {@link GreetingMessage}
*/
@GET
@Path("injected")
@Produces(MediaType.APPLICATION_JSON)
@WithSpan("mixed_parent_injected")
public GreetingMessage mixedSpanInjected() {
io.helidon.tracing.Span mixedSpan = helidonTracerInjected.spanBuilder("mixed_injected_inner")
.kind(io.helidon.tracing.Span.Kind.SERVER)
.tag("attribute", "value")
.start();
mixedSpan.end();

return new GreetingMessage("Mixed Span Injected");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ metrics.rest-request.enabled=true
otel.sdk.disabled=false
otel.traces.exporter=jaeger
otel.service.name=greeting-service

#telemetry.span.full.url=true
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ class HelidonTelemetryContainerFilter implements ContainerRequestFilter, Contain
private static final String SPAN_SCOPE = "otel.span.server.scope";
private static final String HTTP_TARGET = "http.target";

private static final String SPAN_NAME_FULL_URL = "telemetry.span.full.url";

private static boolean spanNameFullUrl = false;

// Extract OpenTelemetry Parent Context from Request headers.
private static final TextMapGetter<ContainerRequestContext> CONTEXT_HEADER_INJECTOR;

Expand Down Expand Up @@ -78,9 +82,11 @@ public Iterable<String> keys(ContainerRequestContext containerRequestContext) {
private ResourceInfo resourceInfo;

@Inject
HelidonTelemetryContainerFilter(Tracer tracer, OpenTelemetry openTelemetry) {
HelidonTelemetryContainerFilter(Tracer tracer, OpenTelemetry openTelemetry, org.eclipse.microprofile.config.Config mpConfig) {
this.tracer = tracer;
this.openTelemetry = openTelemetry;

mpConfig.getOptionalValue(SPAN_NAME_FULL_URL, Boolean.class).ifPresent(e -> spanNameFullUrl = e);
}

@Override
Expand All @@ -100,10 +106,15 @@ public void filter(ContainerRequestContext requestContext) {
parentContext = extractedContext;
}

String annotatedPath = requestContext.getUriInfo().getPath();
Path pathAnnotation = resourceInfo.getResourceMethod().getAnnotation(Path.class);
if (pathAnnotation != null) {
annotatedPath = pathAnnotation.value();
String annotatedPath;
if (spanNameFullUrl) {
annotatedPath = requestContext.getUriInfo().getAbsolutePath().toString();
} else {
annotatedPath = requestContext.getUriInfo().getPath();
Path pathAnnotation = resourceInfo.getResourceMethod().getAnnotation(Path.class);
if (pathAnnotation != null) {
annotatedPath = pathAnnotation.value();
}
}

//Start new span for container request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.helidon.microprofile.telemetry;

import java.io.Serial;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand All @@ -37,6 +38,8 @@
// Literal to create HelidonWithSpan annotation.
class Literal extends AnnotationLiteral<HelidonWithSpan> implements HelidonWithSpan {
static final Literal INSTANCE = new Literal();
@Serial
private static final long serialVersionUID = 5910339603347723544L;

private Literal() {
}
Expand Down
Loading