diff --git a/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java b/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java index 44ad7f099..1da4e71c6 100644 --- a/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java +++ b/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java @@ -31,7 +31,7 @@ public class InvokeClient { /** * Identifier in Dapr for the service this client will invoke. */ - private static final String SERVICE_APP_ID = "tracingdemo"; + private static final String SERVICE_APP_ID = "tracingdemoproxy"; /** * Starts the invoke client. @@ -45,7 +45,7 @@ public class InvokeClient { try (DaprClient client = (new DaprClientBuilder()).build()) { for (String message : args) { try (Scope scope = span.makeCurrent()) { - InvokeServiceRequestBuilder builder = new InvokeServiceRequestBuilder(SERVICE_APP_ID, "echo"); + InvokeServiceRequestBuilder builder = new InvokeServiceRequestBuilder(SERVICE_APP_ID, "proxy_echo"); InvokeServiceRequest request = builder.withBody(message).withHttpExtension(HttpExtension.POST).withContext(Context.current()).build(); client.invokeMethod(request, TypeRef.get(byte[].class)) @@ -54,7 +54,7 @@ public class InvokeClient { return r; }) .flatMap(r -> { - InvokeServiceRequest sleepRequest = new InvokeServiceRequestBuilder(SERVICE_APP_ID, "sleep") + InvokeServiceRequest sleepRequest = new InvokeServiceRequestBuilder(SERVICE_APP_ID, "proxy_sleep") .withHttpExtension(HttpExtension.POST) .withContext(r.getContext()).build(); return client.invokeMethod(sleepRequest, TypeRef.get(Void.class)); diff --git a/examples/src/main/java/io/dapr/examples/tracing/README.md b/examples/src/main/java/io/dapr/examples/tracing/README.md index 551840c7b..d14a5731d 100644 --- a/examples/src/main/java/io/dapr/examples/tracing/README.md +++ b/examples/src/main/java/io/dapr/examples/tracing/README.md @@ -3,7 +3,9 @@ In this sample, we'll create two java applications: a service application, which exposes two methods, and a client application which will invoke the methods from the service using Dapr. This sample includes: -* TracingDemoService (Exposes the method to be remotely accessed) +* TracingDemoService (Exposes the methods to be remotely accessed) +* TracingDemoServiceController (Implements two methods: `echo` and `sleep`) +* TracingDemoMiddleServiceController (Implements two methods: `proxy_echo` and `proxy_sleep`) * InvokeClient (Invokes the exposed methods from TracingDemoService) Also consider [getting started with observability in Dapr](https://github.com/dapr/quickstarts/tree/master/observability). @@ -54,7 +56,7 @@ CONTAINER ID IMAGE COMMAND CREATED If Zipkin is not working, [install the newest version of Dapr Cli and initialize it](https://github.com/dapr/cli#install-dapr-on-your-local-machine-self-hosted). -### Running the Demo service sample +### Running the Demo service app The Demo service application exposes two methods that can be remotely invoked. In this example, the service code has two parts: @@ -82,11 +84,11 @@ This Rest Controller exposes the `echo` and `sleep` methods. The `echo` method r public class TracingDemoServiceController { ///... @PostMapping(path = "/echo") - public Mono handleMethod(@RequestBody(required = false) byte[] body, + public Mono handleMethod(@RequestBody(required = false) String body, @RequestHeader Map headers) { return Mono.fromSupplier(() -> { try { - String message = body == null ? "" : new String(body, StandardCharsets.UTF_8); + String message = body == null ? "" : body; Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT")); String utcNowAsString = DATE_FORMAT.format(utcNow.getTime()); @@ -95,7 +97,7 @@ public class TracingDemoServiceController { // Handles the request by printing message. System.out.println( - "Server: " + message + " @ " + utcNowAsString + " and metadata: " + metadataString); + "Server: " + message + " @ " + utcNowAsString + " and metadata: " + metadataString); return utcNowAsString; } catch (Exception e) { @@ -130,15 +132,67 @@ dapr run --app-id tracingdemo --app-port 3000 -- java -jar target/dapr-java-sdk- Once running, the TracingDemoService is now ready to be invoked by Dapr. +### Running the Demo middle service app -### Running the InvokeClient sample +This service will handle the `proxy_echo` and `proxy_sleep` methods and invoke the corresponding methods in the service above. +In the code below, the `opentelemetry-context` attribute is used to propagate the tracing context among service invocations in multiple layers. -This sample code uses the Dapr SDK for invoking two remote methods (`echo` and `sleep`). It is also instrumented with OpenTelemetry. See the code snippet below: +```java +@RestController +public class TracingDemoMiddleServiceController { + // ... + @PostMapping(path = "/proxy_echo") + public Mono echo( + @RequestAttribute(name = "opentelemetry-context") Context context, + @RequestBody(required = false) String body) { + InvokeServiceRequestBuilder builder = new InvokeServiceRequestBuilder(INVOKE_APP_ID, "echo"); + InvokeServiceRequest request + = builder.withBody(body).withHttpExtension(HttpExtension.POST).withContext(context).build(); + return client.invokeMethod(request, TypeRef.get(byte[].class)).map(r -> r.getObject()); + } + // ... + @PostMapping(path = "/proxy_sleep") + public Mono sleep(@RequestAttribute(name = "opentelemetry-context") Context context) { + InvokeServiceRequestBuilder builder = new InvokeServiceRequestBuilder(INVOKE_APP_ID, "sleep"); + InvokeServiceRequest request = builder.withHttpExtension(HttpExtension.POST).withContext(context).build(); + return client.invokeMethod(request, TypeRef.get(byte[].class)).then(); + } +} +``` + +The request attribute `opentelemetry-context` in created by parsing the tracing headers in the [OpenTelemetryInterceptor](../../springboot/OpenTelemetryInterceptor.java) class. See the code below: + +```java +@Component +public class OpenTelemetryInterceptor implements HandlerInterceptor { + // ... + @Override + public boolean preHandle( + HttpServletRequest request, HttpServletResponse response, Object handler) { + final TextMapPropagator textFormat = OpenTelemetry.getGlobalPropagators().getTextMapPropagator(); + // ... + Context context = textFormat.extract(Context.current(), request, HTTP_SERVLET_REQUEST_GETTER); + request.setAttribute("opentelemetry-context", context); + return true; + } + // ... +} +``` + +Use the follow command to execute the service: + +```sh +dapr run --app-id tracingdemoproxy --app-port 3001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.tracing.TracingDemoService -p 3001 +``` + +### Running the InvokeClient app + +This sample code uses the Dapr SDK for invoking two remote methods (`proxy_echo` and `proxy_sleep`). It is also instrumented with OpenTelemetry. See the code snippet below: ```java public class InvokeClient { -private static final String SERVICE_APP_ID = "invokedemo"; +private static final String SERVICE_APP_ID = "tracingdemoproxy"; ///... public static void main(String[] args) throws Exception { Tracer tracer = OpenTelemetryConfig.createTracer(InvokeClient.class.getCanonicalName()); @@ -147,7 +201,7 @@ private static final String SERVICE_APP_ID = "invokedemo"; try (DaprClient client = (new DaprClientBuilder()).build()) { for (String message : args) { try (Scope scope = tracer.withSpan(span)) { - InvokeServiceRequestBuilder builder = new InvokeServiceRequestBuilder(SERVICE_APP_ID, "echo"); + InvokeServiceRequestBuilder builder = new InvokeServiceRequestBuilder(SERVICE_APP_ID, "proxy_echo"); InvokeServiceRequest request = builder.withBody(message).withHttpExtension(HttpExtension.POST).withContext(Context.current()).build(); client.invokeService(request, TypeRef.get(byte[].class)) @@ -156,7 +210,7 @@ private static final String SERVICE_APP_ID = "invokedemo"; return r; }) .flatMap(r -> { - InvokeServiceRequest sleepRequest = new InvokeServiceRequestBuilder(SERVICE_APP_ID, "sleep") + InvokeServiceRequest sleepRequest = new InvokeServiceRequestBuilder(SERVICE_APP_ID, "proxy_sleep") .withHttpExtension(HttpExtension.POST) .withContext(r.getContext()).build(); return client.invokeMethod(sleepRequest, TypeRef.get(Void.class)); diff --git a/examples/src/main/java/io/dapr/examples/tracing/TracingDemoMiddleServiceController.java b/examples/src/main/java/io/dapr/examples/tracing/TracingDemoMiddleServiceController.java new file mode 100644 index 000000000..6e99fda5f --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/tracing/TracingDemoMiddleServiceController.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.tracing; + +import io.dapr.client.DaprClient; +import io.dapr.client.domain.HttpExtension; +import io.dapr.client.domain.InvokeServiceRequest; +import io.dapr.client.domain.InvokeServiceRequestBuilder; +import io.dapr.utils.TypeRef; +import io.opentelemetry.context.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +import java.util.Map; + +/** + * SpringBoot Controller to handle service invocation. + * + *

Instrumentation is handled in {@link io.dapr.springboot.OpenTelemetryInterceptor}. + */ +@RestController +public class TracingDemoMiddleServiceController { + + private static final String INVOKE_APP_ID = "tracingdemo"; + + /** + * Dapr client. + */ + @Autowired + private DaprClient client; + + /** + * Handles the 'echo' method invocation, by proxying a call into another service. + * + * @param context The tracing context for the request. + * @param body The body of the http message. + * @return A message containing the time. + */ + @PostMapping(path = "/proxy_echo") + public Mono echo( + @RequestAttribute(name = "opentelemetry-context") Context context, + @RequestBody(required = false) String body) { + InvokeServiceRequestBuilder builder = new InvokeServiceRequestBuilder(INVOKE_APP_ID, "echo"); + InvokeServiceRequest request + = builder.withBody(body).withHttpExtension(HttpExtension.POST).withContext(context).build(); + return client.invokeMethod(request, TypeRef.get(byte[].class)).map(r -> r.getObject()); + } + + /** + * Handles the 'sleep' method invocation, by proxying a call into another service. + * + * @param context The tracing context for the request. + * @return A message containing the time. + */ + @PostMapping(path = "/proxy_sleep") + public Mono sleep(@RequestAttribute(name = "opentelemetry-context") Context context) { + InvokeServiceRequestBuilder builder = new InvokeServiceRequestBuilder(INVOKE_APP_ID, "sleep"); + InvokeServiceRequest request = builder.withHttpExtension(HttpExtension.POST).withContext(context).build(); + return client.invokeMethod(request, TypeRef.get(byte[].class)).then(); + } + +} diff --git a/examples/src/main/java/io/dapr/examples/tracing/TracingDemoService.java b/examples/src/main/java/io/dapr/examples/tracing/TracingDemoService.java index f7a5a25c0..2f9790391 100644 --- a/examples/src/main/java/io/dapr/examples/tracing/TracingDemoService.java +++ b/examples/src/main/java/io/dapr/examples/tracing/TracingDemoService.java @@ -22,6 +22,9 @@ import org.apache.commons.cli.Options; * 3. Run in server mode: * dapr run --app-id tracingdemo --app-port 3000 \ * -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.tracing.TracingDemoService -p 3000 + * 4. Run middle server: + * dapr run --app-id tracingdemoproxy --app-port 3001 \ + * -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.tracing.TracingDemoService -p 3001 */ public class TracingDemoService { diff --git a/examples/src/main/java/io/dapr/examples/tracing/TracingDemoServiceController.java b/examples/src/main/java/io/dapr/examples/tracing/TracingDemoServiceController.java index 46aee0666..83566d578 100644 --- a/examples/src/main/java/io/dapr/examples/tracing/TracingDemoServiceController.java +++ b/examples/src/main/java/io/dapr/examples/tracing/TracingDemoServiceController.java @@ -53,11 +53,11 @@ public class TracingDemoServiceController { * @return A message containing the time. */ @PostMapping(path = "/echo") - public Mono handleMethod(@RequestBody(required = false) byte[] body, + public Mono handleMethod(@RequestBody(required = false) String body, @RequestHeader Map headers) { return Mono.fromSupplier(() -> { try { - String message = body == null ? "" : new String(body, StandardCharsets.UTF_8); + String message = body == null ? "" : body; Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT")); String utcNowAsString = DATE_FORMAT.format(utcNow.getTime()); diff --git a/examples/src/main/java/io/dapr/springboot/DaprConfig.java b/examples/src/main/java/io/dapr/springboot/DaprConfig.java new file mode 100644 index 000000000..9de490382 --- /dev/null +++ b/examples/src/main/java/io/dapr/springboot/DaprConfig.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.springboot; + +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DaprConfig { + + private static final DaprClientBuilder BUILDER = new DaprClientBuilder(); + + @Bean + public DaprClient buildDaprClient() { + return BUILDER.build(); + } +} \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/springboot/OpenTelemetryInterceptor.java b/examples/src/main/java/io/dapr/springboot/OpenTelemetryInterceptor.java index 7a344240e..002e035e9 100644 --- a/examples/src/main/java/io/dapr/springboot/OpenTelemetryInterceptor.java +++ b/examples/src/main/java/io/dapr/springboot/OpenTelemetryInterceptor.java @@ -6,12 +6,9 @@ package io.dapr.springboot; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapPropagator; import org.jetbrains.annotations.Nullable; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; @@ -36,8 +33,6 @@ public class OpenTelemetryInterceptor implements HandlerInterceptor { return carrier.getHeader(key); } }; - @Autowired - Tracer tracer; @Override public boolean preHandle( @@ -49,43 +44,15 @@ public class OpenTelemetryInterceptor implements HandlerInterceptor { return true; } - Span span; - try { - Context context = textFormat.extract(Context.current(), request, HTTP_SERVLET_REQUEST_GETTER); - request.setAttribute("opentelemetry-context", context); - span = tracer.spanBuilder(request.getRequestURI()).setParent(context).startSpan(); - span.setAttribute("handler", "pre"); - } catch (Exception e) { - span = tracer.spanBuilder(request.getRequestURI()).startSpan(); - span.setAttribute("handler", "pre"); - - span.addEvent(e.toString()); - span.setAttribute("error", true); - } - request.setAttribute("opentelemetry-span", span); + Context context = textFormat.extract(Context.current(), request, HTTP_SERVLET_REQUEST_GETTER); + request.setAttribute("opentelemetry-context", context); return true; } @Override public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, - ModelAndView modelAndView) throws Exception { - } - - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, - Object handler, Exception exception) { - Object contextObject = request.getAttribute("opentelemetry-context"); - Object spanObject = request.getAttribute("opentelemetry-span"); - if ((contextObject == null) || (spanObject == null)) { - return; - } - Context context = (Context) contextObject; - Span span = (Span) spanObject; - span.setAttribute("handler", "afterCompletion"); - final TextMapPropagator textFormat = OpenTelemetry.getGlobalPropagators().getTextMapPropagator(); - textFormat.inject(context, response, HttpServletResponse::addHeader); - span.end(); + ModelAndView modelAndView) { } } diff --git a/examples/src/main/resources/img/zipkin-details.png b/examples/src/main/resources/img/zipkin-details.png index 2fe2dd911..1637bc9d3 100644 Binary files a/examples/src/main/resources/img/zipkin-details.png and b/examples/src/main/resources/img/zipkin-details.png differ