Skip to content

Commit

Permalink
Use Helidon tracer, span builder, span types instead of OTel ones so …
Browse files Browse the repository at this point in the history
…we can trigger span listeners (#8778)
  • Loading branch information
tjquinno committed May 24, 2024
1 parent c06dc1f commit 19edc8b
Show file tree
Hide file tree
Showing 13 changed files with 392 additions and 126 deletions.
4 changes: 4 additions & 0 deletions microprofile/telemetry/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
</properties>

<dependencies>
<dependency>
<groupId>io.helidon.tracing</groupId>
<artifactId>helidon-tracing</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.tracing.providers</groupId>
<artifactId>helidon-tracing-providers-opentelemetry</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
* Copyright (c) 2023, 2024 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.
Expand All @@ -15,18 +15,20 @@
*/
package io.helidon.microprofile.telemetry;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.TextMapSetter;
import java.util.List;
import java.util.Optional;

import io.helidon.tracing.HeaderConsumer;
import io.helidon.tracing.HeaderProvider;
import io.helidon.tracing.Scope;
import io.helidon.tracing.Span;

import jakarta.inject.Inject;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.client.ClientResponseContext;
import jakarta.ws.rs.client.ClientResponseFilter;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.Provider;

import static io.helidon.microprofile.telemetry.HelidonTelemetryConstants.HTTP_METHOD;
Expand All @@ -42,22 +44,14 @@
class HelidonTelemetryClientFilter implements ClientRequestFilter, ClientResponseFilter {
private static final System.Logger LOGGER = System.getLogger(HelidonTelemetryContainerFilter.class.getName());
private static final String HTTP_URL = "http.url";
private static final String OTEL_CLIENT_SCOPE = "otel.span.client.scope";
private static final String OTEL_CLIENT_SPAN = "otel.span.client.span";
private static final String OTEL_CLIENT_CONTEXT = "otel.span.client.context";

// Extract the current OpenTelemetry Context. Required for a parent/child relationship
// to be correctly rebuilt in the next filter.
private static final TextMapSetter<ClientRequestContext> CONTEXT_HEADER_EXTRACTOR =
(carrier, key, value) -> carrier.getHeaders().add(key, value);
private static final String SPAN_SCOPE = Scope.class.getName();
private static final String SPAN = Span.class.getName();

private final Tracer tracer;
private final OpenTelemetry openTelemetry;
private final io.helidon.tracing.Tracer helidonTracer;

@Inject
HelidonTelemetryClientFilter(Tracer tracer, OpenTelemetry openTelemetry) {
this.tracer = tracer;
this.openTelemetry = openTelemetry;
HelidonTelemetryClientFilter(io.helidon.tracing.Tracer helidonTracer) {
this.helidonTracer = helidonTracer;
}


Expand All @@ -69,22 +63,25 @@ public void filter(ClientRequestContext clientRequestContext) {
}

//Start new span for Client request.
Span span = tracer.spanBuilder("HTTP " + clientRequestContext.getMethod())
.setParent(Context.current())
.setSpanKind(SpanKind.CLIENT)
.setAttribute(HTTP_METHOD, clientRequestContext.getMethod())
.setAttribute(HTTP_SCHEME, clientRequestContext.getUri().getScheme())
.setAttribute(HTTP_URL, clientRequestContext.getUri().toString())
.startSpan();

Scope scope = span.makeCurrent();
clientRequestContext.setProperty(OTEL_CLIENT_SCOPE, scope);
clientRequestContext.setProperty(OTEL_CLIENT_SPAN, span);
clientRequestContext.setProperty(OTEL_CLIENT_CONTEXT, Context.current());

// Propagate OpenTelemetry context to next filter
openTelemetry.getPropagators().getTextMapPropagator().inject(Context.current(), clientRequestContext,
CONTEXT_HEADER_EXTRACTOR);
// Use the Helidon wrappers so registered span listeners are notified.
Span helidonSpan = helidonTracer.spanBuilder("HTTP " + clientRequestContext.getMethod())
.kind(Span.Kind.CLIENT)
.tag(HTTP_METHOD, clientRequestContext.getMethod())
.tag(HTTP_SCHEME, clientRequestContext.getUri().getScheme())
.tag(HTTP_URL, clientRequestContext.getUri().toString())
.update(builder -> Span.current()
.map(Span::context)
.ifPresent(builder::parent))
.start();

Scope helidonScope = helidonSpan.activate();

clientRequestContext.setProperty(SPAN_SCOPE, helidonScope);
clientRequestContext.setProperty(SPAN, helidonSpan);

helidonTracer.inject(helidonSpan.context(),
HeaderProvider.empty(),
new RequestContextHeaderInjector(clientRequestContext.getHeaders()));
}


Expand All @@ -95,22 +92,57 @@ public void filter(ClientRequestContext clientRequestContext, ClientResponseCont
LOGGER.log(System.Logger.Level.TRACE, "Closing Span in a Client Response");
}

// End span for Client request.
Context context = (Context) clientRequestContext.getProperty(OTEL_CLIENT_CONTEXT);
if (context == null) {
Span span = (Span) clientRequestContext.getProperty(SPAN);
if (span == null) {
return;
}
Scope scope = (Scope) clientRequestContext.getProperty(SPAN_SCOPE);
scope.close();

Span span = (Span) clientRequestContext.getProperty(OTEL_CLIENT_SPAN);
span.setAttribute(HTTP_STATUS_CODE, clientResponseContext.getStatus());
span.tag(HTTP_STATUS_CODE, clientResponseContext.getStatus());
span.end();

Scope scope = (Scope) clientRequestContext.getProperty(OTEL_CLIENT_SCOPE);
scope.close();
clientRequestContext.removeProperty(SPAN);
clientRequestContext.removeProperty(SPAN_SCOPE);
}

clientRequestContext.removeProperty(OTEL_CLIENT_SPAN);
clientRequestContext.removeProperty(OTEL_CLIENT_SCOPE);
clientRequestContext.removeProperty(OTEL_CLIENT_CONTEXT);
private static class RequestContextHeaderInjector implements HeaderConsumer {

private final MultivaluedMap<String, Object> requestHeaders;

private RequestContextHeaderInjector(MultivaluedMap<String, Object> headers) {
requestHeaders = headers;
}

@Override
public void setIfAbsent(String key, String... values) {
requestHeaders.computeIfAbsent(key, k -> List.of(values));
}

@Override
public void set(String key, String... values) {
requestHeaders.put(key, List.of(values));
}

@Override
public Iterable<String> keys() {
return requestHeaders.keySet();
}

@Override
public Optional<String> get(String key) {
return Optional.ofNullable((String) requestHeaders.getFirst(key));
}

@Override
public Iterable<String> getAll(String key) {
return requestHeaders.get(key).stream().map(o -> (String) o).toList();
}

@Override
public boolean contains(String key) {
return requestHeaders.containsKey(key);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,15 @@
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

import io.helidon.common.context.Contexts;
import io.helidon.config.mp.MpConfig;
import io.helidon.tracing.Scope;
import io.helidon.tracing.Span;
import io.helidon.tracing.providers.opentelemetry.HelidonOpenTelemetry;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.TextMapGetter;
import jakarta.inject.Inject;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.container.ContainerRequestContext;
Expand All @@ -55,46 +50,25 @@
@Provider
class HelidonTelemetryContainerFilter implements ContainerRequestFilter, ContainerResponseFilter {
private static final System.Logger LOGGER = System.getLogger(HelidonTelemetryContainerFilter.class.getName());
private static final String SPAN = "otel.span.server.span";
private static final String SPAN_CONTEXT = "otel.span.server.context";
private static final String SPAN_SCOPE = "otel.span.server.scope";
private static final String SPAN = Span.class.getName();
private static final String SPAN_SCOPE = Scope.class.getName();
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;

static {
CONTEXT_HEADER_INJECTOR =
new TextMapGetter<>() {
@Override
public String get(ContainerRequestContext containerRequestContext, String s) {
Objects.requireNonNull(containerRequestContext);
return containerRequestContext.getHeaderString(s);
}

@Override
public Iterable<String> keys(ContainerRequestContext containerRequestContext) {
return List.copyOf(containerRequestContext.getHeaders().keySet());
}
};
}

private final Tracer tracer;
private final OpenTelemetry openTelemetry;
private final io.helidon.tracing.Tracer helidonTracer;
private final boolean isAgentPresent;


@jakarta.ws.rs.core.Context
private ResourceInfo resourceInfo;

@Inject
HelidonTelemetryContainerFilter(Tracer tracer, OpenTelemetry openTelemetry, org.eclipse.microprofile.config.Config mpConfig) {
this.tracer = tracer;
this.openTelemetry = openTelemetry;
HelidonTelemetryContainerFilter(io.helidon.tracing.Tracer helidonTracer,
org.eclipse.microprofile.config.Config mpConfig) {
this.helidonTracer = helidonTracer;
isAgentPresent = HelidonOpenTelemetry.AgentDetector.isAgentPresent(MpConfig.toHelidonConfig(mpConfig));

mpConfig.getOptionalValue(SPAN_NAME_FULL_URL, Boolean.class).ifPresent(e -> spanNameFullUrl = e);
Expand All @@ -111,26 +85,20 @@ public void filter(ContainerRequestContext requestContext) {
LOGGER.log(System.Logger.Level.TRACE, "Starting Span in a Container Request");
}

// Extract Parent Context from request headers to be used from previous filters
Context extractedContext = openTelemetry.getPropagators().getTextMapPropagator()
.extract(Context.current(), requestContext, CONTEXT_HEADER_INJECTOR);

Context parentContext = Objects.requireNonNullElseGet(extractedContext, Context::current);

//Start new span for container request.
Span span = tracer.spanBuilder(spanName(requestContext))
.setParent(parentContext)
.setSpanKind(SpanKind.SERVER)
.setAttribute(HTTP_METHOD, requestContext.getMethod())
.setAttribute(HTTP_SCHEME, requestContext.getUriInfo().getRequestUri().getScheme())
.setAttribute(HTTP_TARGET, resolveTarget(requestContext))
.startSpan();
Span helidonSpan = helidonTracer.spanBuilder(spanName(requestContext))
.kind(Span.Kind.SERVER)
.tag(HTTP_METHOD, requestContext.getMethod())
.tag(HTTP_SCHEME, requestContext.getUriInfo().getRequestUri().getScheme())
.tag(HTTP_TARGET, resolveTarget(requestContext))
.update(builder -> helidonTracer.extract(new RequestContextHeaderProvider(requestContext.getHeaders()))
.ifPresent(builder::parent))
.start();

Scope scope = span.makeCurrent();
requestContext.setProperty(SPAN, span);
requestContext.setProperty(SPAN_CONTEXT, Context.current());
requestContext.setProperty(SPAN_SCOPE, scope);
Scope helidonScope = helidonSpan.activate();

requestContext.setProperty(SPAN, helidonSpan);
requestContext.setProperty(SPAN_SCOPE, helidonScope);

// Handle OpenTelemetry Baggage from the current OpenTelemetry Context.
handleBaggage(requestContext, Context.current());
Expand All @@ -148,19 +116,18 @@ public void filter(final ContainerRequestContext request, final ContainerRespons
LOGGER.log(System.Logger.Level.TRACE, "Closing Span in a Container Request");
}

// Close span for container request.
Context context = (Context) request.getProperty(SPAN_CONTEXT);
if (context == null) {
return;
}

try {
Span span = (Span) request.getProperty(SPAN);
span.setAttribute(HTTP_STATUS_CODE, response.getStatus());
span.end();

if (span == null) {
return;
}
Scope scope = (Scope) request.getProperty(SPAN_SCOPE);
scope.close();

span.tag(HTTP_STATUS_CODE, response.getStatus());
span.end();


} finally {
request.removeProperty(SPAN);
request.removeProperty(SPAN_SCOPE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024 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.microprofile.telemetry;

import java.util.Optional;

import io.helidon.tracing.HeaderProvider;

import jakarta.ws.rs.core.MultivaluedMap;

record RequestContextHeaderProvider(MultivaluedMap<String, String> headers) implements HeaderProvider {

@Override
public Iterable<String> keys() {
return headers.keySet();
}

@Override
public Optional<String> get(String key) {
return Optional.ofNullable(headers.getFirst(key));
}

@Override
public Iterable<String> getAll(String key) {
return headers.get(key);
}

@Override
public boolean contains(String key) {
return headers.containsKey(key);
}
}
Loading

0 comments on commit 19edc8b

Please sign in to comment.