Skip to content

Commit a5764d1

Browse files
authored
[4.x] - MP OpenTelemetry and Helidon Tracing API (helidon-io#8073)
* Initial fix Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Refactor, add full url in span name support Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Checkstyle fixes Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Add arquillian test Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Hierarchy test Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Clean up Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Document Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Correct Helidon Tracing API set up in case of Agent Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Minor fix Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Minor fixes. Documentation. Tests. Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Extend timeout Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Extend timeout Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Refactor URL Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Remove arquillian Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Clean up Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Clean up Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> * Clean up Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com> --------- Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com>
1 parent 08fb215 commit a5764d1

7 files changed

Lines changed: 218 additions & 35 deletions

File tree

docs/mp/telemetry.adoc

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ For Automatic Instrumentation, OpenTelemetry provides a JavaAgent. The Tracing A
6969
7070
For Manual Instrumentation, there is a set of annotations and access to OpenTelemetry API.
7171
72-
`@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.
72+
`@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.
7373
7474
Helidon provides full access to OpenTelemetry Tracing API:
7575
@@ -100,7 +100,10 @@ class HelidonBean {
100100
<1> Simple `@WithSpan` annotation usage.
101101
<2> Additional attributes can be set on a method.
102102
103-
You can also inject OpenTelemetry `Tracer` using the regular `@Inject` annotation and use `SpanBuilder` to manually create, star, and stop Spans.
103+
104+
=== Working With Tracers
105+
106+
You can inject OpenTelemetry `Tracer` using the regular `@Inject` annotation and use `SpanBuilder` to manually create, star and stop spans.
104107
105108
.SpanBuilder usage
106109
[source, java]
@@ -128,6 +131,66 @@ public class HelidonEndpoint {
128131
<1> Inject `Tracer`.
129132
<2> Use `Tracer.spanBuilder` to create and start new `Span`.
130133
134+
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`:
135+
136+
.Inject Helidon Tracer
137+
[source, java]
138+
----
139+
private io.helidon.tracing.Tracer helidonTracerInjected;
140+
141+
@Inject
142+
GreetResource(io.helidon.tracing.Tracer helidonTracerInjected) {
143+
this.helidonTracerInjected = helidonTracerInjected; <1>
144+
}
145+
146+
@GET
147+
@Path("mixed_injected")
148+
@Produces(MediaType.APPLICATION_JSON)
149+
@WithSpan("mixed_parent_injected")
150+
public GreetingMessage mixedSpanInjected() {
151+
io.helidon.tracing.Span mixedSpan = helidonTracerInjected.spanBuilder("mixed_injected") <2>
152+
.kind(io.helidon.tracing.Span.Kind.SERVER)
153+
.tag("attribute", "value")
154+
.start();
155+
mixedSpan.end();
156+
157+
return new GreetingMessage("Mixed Span Injected" + span);
158+
}
159+
----
160+
<1> Inject `io.helidon.tracing.Tracer`.
161+
<2> Use the injected tracer to create `io.helidon.tracing.Span` using the `spanBuilder()` method.
162+
163+
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".
164+
165+
Another option is to use the Global Tracer:
166+
167+
.Obtain the Global tracer
168+
[source, java]
169+
----
170+
@GET
171+
@Path("mixed")
172+
@Produces(MediaType.APPLICATION_JSON)
173+
@WithSpan("mixed_parent")
174+
public GreetingMessage mixedSpan() {
175+
176+
io.helidon.tracing.Tracer helidonTracer = io.helidon.tracing.Tracer.global(); <1>
177+
io.helidon.tracing.Span mixedSpan = helidonTracer.spanBuilder("mixed") <2>
178+
.kind(io.helidon.tracing.Span.Kind.SERVER)
179+
.tag("attribute", "value")
180+
.start();
181+
mixedSpan.end();
182+
183+
return new GreetingMessage("Mixed Span" + span);
184+
}
185+
----
186+
<1> Obtain tracer using the `io.helidon.tracing.Tracer.global()` method;
187+
<2> Use the created tracer to create a span.
188+
189+
The span is then started and ended manually. Span parent relations will be preserved.
190+
191+
192+
=== Working With Spans
193+
131194
To obtain the current span, it can be injected by CDI. The current span can also be obtained using the static method `Span.current()`.
132195
133196
.Inject the current span
@@ -156,6 +219,8 @@ public class HelidonEndpoint {
156219
<2> Use the injected span.
157220
<3> Use `Span.current()` to access the current span.
158221
222+
=== Working With Baggage
223+
159224
The same functionality is available for the `Baggage` API:
160225
161226
.Inject the current baggage
@@ -205,7 +270,7 @@ The OpenTelemetry Java Agent may influence the work of MicroProfile Telemetry, o
205270
206271
`otel.agent.present=true`
207272
208-
This way, Helidon will explicitly get all the configuration and objects from the Agent, thus allowing correct Span hierarchy settings.
273+
This way, Helidon will explicitly get all the configuration and objects from the Agent, thus allowing correct span hierarchy settings.
209274
210275
== Examples
211276
@@ -335,7 +400,7 @@ public JsonObject useCustomSpan(){
335400
}
336401
----
337402
<1> Inject Opentelemetry `Tracer`.
338-
<2> Create Span around the method `useCustomSpan()`.
403+
<2> Create a span around the method `useCustomSpan()`.
339404
<3> Create a custom `INTERNAL` span and start it.
340405
<4> End the custom span.
341406
@@ -391,7 +456,7 @@ curl localhost:8080/greet/outbound
391456
Secondary
392457
----
393458
394-
The `greeting-service` call `secondary-service`. Each service will create Spans with corresponding names, and a service class hierarchy will be created.
459+
The `greeting-service` call `secondary-service`. Each service will create spans with corresponding names, and a service class hierarchy will be created.
395460
396461
Launch the Jaeger UI at link:http://localhost:16686/[] to see the expected output (shown below).
397462

examples/microprofile/telemetry/greeting/src/main/java/io/helidon/examples/microprofile/telemetry/GreetResource.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import jakarta.ws.rs.core.MediaType;
2929
import org.glassfish.jersey.server.Uri;
3030

31-
3231
/**
3332
* A simple JAX-RS resource to greet you. Examples:
3433
*
@@ -40,6 +39,8 @@
4039
*
4140
* Call secondary service:
4241
* curl -X GET http://localhost:8080/greet/outbound
42+
*
43+
* Explore traces in Jaeger UI.
4344
*/
4445
@Path("/greet")
4546
public class GreetResource {
@@ -85,6 +86,7 @@ public GreetingMessage useCustomSpan() {
8586

8687
return new GreetingMessage("Custom Span" + span);
8788
}
89+
8890
/**
8991
* Get Span info.
9092
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2023 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.examples.microprofile.telemetry;
18+
19+
import io.opentelemetry.instrumentation.annotations.WithSpan;
20+
import jakarta.inject.Inject;
21+
import jakarta.ws.rs.GET;
22+
import jakarta.ws.rs.Path;
23+
import jakarta.ws.rs.Produces;
24+
import jakarta.ws.rs.core.MediaType;
25+
26+
/**
27+
* A simple JAX-RS resource to use `io.opentelemetry` and `io.helidon.api` in mixed mode. Examples:
28+
*
29+
* Get mixed traces with Global tracer:
30+
* curl -X GET http://localhost:8080/mixed
31+
*
32+
* Get mixed traces with an injected Helidon tracer:
33+
* curl -X GET http://localhost:8080/mixed/injected
34+
*
35+
* Explore traces in Jaeger UI.
36+
*/
37+
@Path("/mixed")
38+
public class MixedTelemetryGreetResource {
39+
40+
private io.helidon.tracing.Tracer helidonTracerInjected;
41+
42+
@Inject
43+
MixedTelemetryGreetResource(io.helidon.tracing.Tracer helidonTracerInjected) {
44+
this.helidonTracerInjected = helidonTracerInjected;
45+
}
46+
47+
48+
/**
49+
* Create a helidon mixed span using Helidon Global tracer.
50+
* @return {@link GreetingMessage}
51+
*/
52+
@GET
53+
@Produces(MediaType.APPLICATION_JSON)
54+
@WithSpan("mixed_parent")
55+
public GreetingMessage mixedSpan() {
56+
57+
io.helidon.tracing.Tracer helidonTracer = io.helidon.tracing.Tracer.global();
58+
io.helidon.tracing.Span mixedSpan = helidonTracer.spanBuilder("mixed_inner")
59+
.kind(io.helidon.tracing.Span.Kind.SERVER)
60+
.tag("attribute", "value")
61+
.start();
62+
mixedSpan.end();
63+
64+
return new GreetingMessage("Mixed Span");
65+
}
66+
67+
/**
68+
* Create a helidon mixed span using injected Helidon Tracer.
69+
* @return {@link GreetingMessage}
70+
*/
71+
@GET
72+
@Path("injected")
73+
@Produces(MediaType.APPLICATION_JSON)
74+
@WithSpan("mixed_parent_injected")
75+
public GreetingMessage mixedSpanInjected() {
76+
io.helidon.tracing.Span mixedSpan = helidonTracerInjected.spanBuilder("mixed_injected_inner")
77+
.kind(io.helidon.tracing.Span.Kind.SERVER)
78+
.tag("attribute", "value")
79+
.start();
80+
mixedSpan.end();
81+
82+
return new GreetingMessage("Mixed Span Injected");
83+
}
84+
}

examples/microprofile/telemetry/greeting/src/main/resources/META-INF/microprofile-config.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ metrics.rest-request.enabled=true
2525
otel.sdk.disabled=false
2626
otel.traces.exporter=jaeger
2727
otel.service.name=greeting-service
28+
29+
#telemetry.span.full.url=true

microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonTelemetryContainerFilter.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ class HelidonTelemetryContainerFilter implements ContainerRequestFilter, Contain
5151
private static final String SPAN_SCOPE = "otel.span.server.scope";
5252
private static final String HTTP_TARGET = "http.target";
5353

54+
private static final String SPAN_NAME_FULL_URL = "telemetry.span.full.url";
55+
56+
private static boolean spanNameFullUrl = false;
57+
5458
// Extract OpenTelemetry Parent Context from Request headers.
5559
private static final TextMapGetter<ContainerRequestContext> CONTEXT_HEADER_INJECTOR;
5660

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

8084
@Inject
81-
HelidonTelemetryContainerFilter(Tracer tracer, OpenTelemetry openTelemetry) {
85+
HelidonTelemetryContainerFilter(Tracer tracer, OpenTelemetry openTelemetry, org.eclipse.microprofile.config.Config mpConfig) {
8286
this.tracer = tracer;
8387
this.openTelemetry = openTelemetry;
88+
89+
mpConfig.getOptionalValue(SPAN_NAME_FULL_URL, Boolean.class).ifPresent(e -> spanNameFullUrl = e);
8490
}
8591

8692
@Override
@@ -100,10 +106,15 @@ public void filter(ContainerRequestContext requestContext) {
100106
parentContext = extractedContext;
101107
}
102108

103-
String annotatedPath = requestContext.getUriInfo().getPath();
104-
Path pathAnnotation = resourceInfo.getResourceMethod().getAnnotation(Path.class);
105-
if (pathAnnotation != null) {
106-
annotatedPath = pathAnnotation.value();
109+
String annotatedPath;
110+
if (spanNameFullUrl) {
111+
annotatedPath = requestContext.getUriInfo().getAbsolutePath().toString();
112+
} else {
113+
annotatedPath = requestContext.getUriInfo().getPath();
114+
Path pathAnnotation = resourceInfo.getResourceMethod().getAnnotation(Path.class);
115+
if (pathAnnotation != null) {
116+
annotatedPath = pathAnnotation.value();
117+
}
107118
}
108119

109120
//Start new span for container request.

microprofile/telemetry/src/main/java/io/helidon/microprofile/telemetry/HelidonWithSpan.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.helidon.microprofile.telemetry;
1717

18+
import java.io.Serial;
1819
import java.lang.annotation.ElementType;
1920
import java.lang.annotation.Retention;
2021
import java.lang.annotation.RetentionPolicy;
@@ -37,6 +38,8 @@
3738
// Literal to create HelidonWithSpan annotation.
3839
class Literal extends AnnotationLiteral<HelidonWithSpan> implements HelidonWithSpan {
3940
static final Literal INSTANCE = new Literal();
41+
@Serial
42+
private static final long serialVersionUID = 5910339603347723544L;
4043

4144
private Literal() {
4245
}

0 commit comments

Comments
 (0)