Skip to content

Commit

Permalink
Drop the tracing context, provide access to parent span if needed, up…
Browse files Browse the repository at this point in the history
…date docs
  • Loading branch information
johanandren authored and octonato committed Nov 20, 2024
1 parent c085112 commit 1aa467a
Show file tree
Hide file tree
Showing 14 changed files with 72 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ object TestKitTracing extends Tracing {

override def startSpan(name: String): Optional[Span] = Optional.empty()

override def parentSpan(): Optional[Span] = Optional.empty()
}

This file was deleted.

This file was deleted.

7 changes: 0 additions & 7 deletions akka-javasdk/src/main/java/akka/javasdk/Metadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,6 @@ public interface Metadata extends Iterable<Metadata.MetadataEntry> {
*/
CloudEvent asCloudEvent(String id, URI source, String type);


/**
* Get the trace context associated with this request metadata.
* @return The trace context.
*/
TraceContext traceContext();

/**
* Merge the given Metadata entries with this Metadata. If the same key is present in both, both values will be kept.
*
Expand Down
48 changes: 0 additions & 48 deletions akka-javasdk/src/main/java/akka/javasdk/TraceContext.java

This file was deleted.

8 changes: 7 additions & 1 deletion akka-javasdk/src/main/java/akka/javasdk/Tracing.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* Factory for manually creating open telemetry spans in addition to those automatically provided by
* the runtime and SDK.
*
* <p>Not for user extension. Is injected into constructors of supported components by the SDK
* <p>Not for user extension. Injectable into endpoint constructors or available through component command contexts.
*/
@DoNotInherit
public interface Tracing {
Expand All @@ -25,4 +25,10 @@ public interface Tracing {
* @return Optional of the span if tracing is enabled, empty option if tracing is not enabled.
*/
Optional<Span> startSpan(String name);

/**
* If tracing is enabled, this returns the current parent span, to use for propagating trace parent
* through third party integrations.
*/
Optional<Span> parentSpan();
}
19 changes: 6 additions & 13 deletions akka-javasdk/src/main/scala/akka/javasdk/impl/MetadataImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import scala.jdk.OptionConverters._
import akka.annotation.InternalApi
import akka.javasdk.CloudEvent
import akka.javasdk.Metadata
import akka.javasdk.TraceContext
import akka.javasdk.impl.telemetry.Telemetry
import akka.javasdk.impl.telemetry.Telemetry.metadataGetter
import com.google.protobuf.ByteString
Expand Down Expand Up @@ -207,22 +206,16 @@ private[javasdk] class MetadataImpl private (val entries: Seq[MetadataEntry]) ex

override def asMetadata(): Metadata = this

override lazy val traceContext: TraceContext = new TraceContext {
override def asOpenTelemetryContext(): OtelContext = W3CTraceContextPropagator
lazy val traceId: Option[String] = {
val otelContext = W3CTraceContextPropagator
.getInstance()
.extract(OtelContext.current(), asMetadata(), metadataGetter)

override def traceId(): Optional[String] = {
Span.fromContext(asOpenTelemetryContext()).getSpanContext.getTraceId match {
case "00000000000000000000000000000000" =>
Optional.empty() // when no traceId returns io.opentelemetry.api.trace.TraceId.INVALID
case traceId => Some(traceId).toJava
}
Span.fromContext(otelContext).getSpanContext.getTraceId match {
case "00000000000000000000000000000000" =>
None // when no traceId returns io.opentelemetry.api.trace.TraceId.INVALID
case traceId => Some(traceId)
}

override def traceParent(): Optional[String] = getScala(Telemetry.TRACE_PARENT_KEY).toJava

override def traceState(): Optional[String] = getScala(Telemetry.TRACE_STATE_KEY).toJava
}

override def merge(other: Metadata): Metadata = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ final class SpanTracingImpl(span: Option[Span], tracerFactory: () => Tracer) ext
.setParent(parent)
.startSpan()
}.toJava

override def parentSpan(): Optional[Span] = span.toJava
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import com.google.protobuf.any.{ Any => ScalaPbAny }
import org.slf4j.LoggerFactory
import org.slf4j.MDC

import scala.jdk.OptionConverters._

/**
* INTERNAL API
*/
Expand Down Expand Up @@ -97,7 +95,7 @@ final class ViewsImpl(_services: Map[String, ViewService[_]], sdkDispatcherName:
val commandName = receiveEvent.commandName
val msg = service.messageCodec.decodeMessage(receiveEvent.payload.get)
val metadata = MetadataImpl.of(receiveEvent.metadata.map(_.entries.toVector).getOrElse(Nil))
val addedToMDC = metadata.traceContext.traceId().toScala match {
val addedToMDC = metadata.traceId match {
case Some(traceId) =>
MDC.put(Telemetry.TRACE_ID, traceId)
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import akka.javasdk.consumer.Consumer;
import akka.javasdk.eventsourcedentity.TestESEvent;
import akka.javasdk.eventsourcedentity.TestEventSourcedEntity;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapSetter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import akka.javasdk.annotations.ComponentId;
Expand All @@ -20,7 +23,20 @@ public class TestTracing extends Consumer {

public Effect consume(TestESEvent.Event2 event) {
logger.info("registering a logging event");
return effects().produce(
messageContext().metadata().traceContext().traceParent().orElse("not-found"));

// test expects a w3c encoded trace parent so here are some hoops to get that
// FIXME if this turns out to be a common need we could provide the w3c encoded traceparent from Tracing
// but for now leaving otel hoops to users is fine enough
String[] w3cEncodedTraceParent = {"not-enabled"};
messageContext().tracing().parentSpan().ifPresent(span -> {
var contextWithSpan = Context.current().with(span);
W3CTraceContextPropagator.getInstance().inject(contextWithSpan, null,
(carrier, key, value) -> {
if (key.equals("traceparent")) {
w3cEncodedTraceParent[0] = value;
}
});
});
return effects().produce(w3cEncodedTraceParent[0]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Furthermore, the following component specific types can also be injected:
| Injectable classes

| Endpoint
|`io.opentelemetry.api.trace.Span` for creating custom traces
|`akka.javasdk.Tracing` for creating custom traces
|Workflow
|`akka.javasdk.workflow.WorkflowContext` for access to the workflow id
|Event Sourced Entity
Expand Down
2 changes: 1 addition & 1 deletion samples/tracing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ curl -i -XPOST localhost:9000/tracing/custom/5
Schedule a timed action which reports a custom span when executing an async call to an external service:

```shell
curl -i -XPOST -H "Content-Type: application/json" localhost:9000/tracing -d '{"id":"2454cb46-1b16-408a-b7f8-bd2d5c376969"}'
curl -i -XPOST -H "Content-Type: application/json" localhost:9000/tracing -d '{"id":"2454cb46-1b16-408a-b7f8-bd2d5c376969"}'
```

Now you can see the trace in Jaeger UI at http://localhost:16686
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.example.tracing.application;


import akka.javasdk.Tracing;
import akka.javasdk.annotations.ComponentId;
import akka.javasdk.timedaction.TimedAction;
import com.example.tracing.domain.Typicode;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import org.slf4j.Logger;
Expand All @@ -28,15 +25,15 @@ public Effect callAnotherService(String postID) {
maybeSpan.ifPresent(span -> span.setAttribute("post", postID));

// FIXME this should demonstrate how to propagate the trace parent with a third party client to another service as well
CompletionStage<HttpResponse<Typicode.TypicodePost>> asyncResult = typicode.callAsyncService(postID);
CompletionStage<HttpResponse<Typicode.TypicodePost>> asyncResult = typicode.callAsyncService(postID, maybeSpan);

maybeSpan.ifPresent(span ->
asyncResult.whenComplete((response, ex) -> {

if (ex != null) {
span.setStatus(StatusCode.ERROR, ex.getMessage()).end();
} else {
span.setAttribute("result", response.body().title()).end();
span.setAttribute("response-status", response.statusCode()).end();
}
})
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,59 @@
package com.example.tracing.domain;
package com.example.tracing.application;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapSetter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Optional;
import java.util.concurrent.CompletionStage;

public class Typicode {

private static final Logger log = LoggerFactory.getLogger(Typicode.class);

public static final String url = "https://jsonplaceholder.typicode.com/posts";

// using a third party HTTP client here rather than the built in `akka.javasdk.http.HttpClient`
// in order to showcase external/manual tracing
// in order to showcase external/manual tracing and propagating context manually
private final HttpClient httpClient = HttpClient.newHttpClient();

private final TextMapSetter<HttpRequest.Builder> setter =
(carrier, key, value) -> carrier.setHeader(key, value);

public record TypicodePost(String userId, String id, String title, String body) {}

public CompletionStage<HttpResponse<TypicodePost>> callAsyncService(String postID) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(url + "/" + postID))
.build();
public CompletionStage<HttpResponse<TypicodePost>> callAsyncService(String postID, Optional<Span> parentSpan) {
var requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(url + "/" + postID));

parentSpan.ifPresent(span -> {
// propagate trace parent to third party service
var contextWithSpan = Context.current().with(span);
W3CTraceContextPropagator.getInstance().inject(contextWithSpan, requestBuilder, setter);
});

HttpRequest httpRequest = requestBuilder.build();

parentSpan.ifPresent(__ -> {
log.info("Request headers propagating open telemetry trace parent: {}", httpRequest.headers().toString());
});

//Async call to external service
return httpClient.sendAsync(httpRequest,
new JsonResponseHandler<>(TypicodePost.class));
}

public static class JsonResponseHandler<T> implements HttpResponse.BodyHandler<T> {
private static class JsonResponseHandler<T> implements HttpResponse.BodyHandler<T> {
private final Class<T> responseType;

public JsonResponseHandler(Class<T> responseType){
Expand Down

0 comments on commit 1aa467a

Please sign in to comment.