diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 3f68af1890..a43714c12e 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -112,6 +112,10 @@ jobs:
working-directory: ./examples
run: |
mm.py ./src/main/java/io/dapr/examples/invoke/grpc/README.md
+ - name: Validate tracing example
+ working-directory: ./examples
+ run: |
+ mm.py ./src/main/java/io/dapr/examples/tracing/README.md
- name: Validate expection handling example
working-directory: ./examples
run: |
diff --git a/examples/pom.xml b/examples/pom.xml
index dc6b8562de..db569840c6 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -74,6 +74,11 @@
spring-boot-autoconfigure
2.3.5.RELEASE
+
+ com.jayway.jsonpath
+ json-path
+ 2.4.0
+
io.opentelemetry
opentelemetry-sdk
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 b918019efe..5f638199cc 100644
--- a/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java
+++ b/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java
@@ -66,18 +66,15 @@ public static void main(String[] args) throws Exception {
}).subscriberContext(getReactorContext()).block();
}
}
-
- // This is an example, so for simplicity we are just exiting here.
- // Normally a dapr app would be a web service and not exit main.
- System.out.println("Done");
}
span.end();
shutdown();
+ System.out.println("Done");
}
- private static void shutdown() {
+ private static void shutdown() throws Exception {
OpenTelemetrySdk.getGlobalTracerManagement().shutdown();
+ Validation.validate();
}
-
}
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 f449c28c2f..99a1c2c6a4 100644
--- a/examples/src/main/java/io/dapr/examples/tracing/README.md
+++ b/examples/src/main/java/io/dapr/examples/tracing/README.md
@@ -126,10 +126,19 @@ The instrumentation for the service happens via the `OpenTelemetryIterceptor` cl
Use the follow command to execute the service:
-```sh
+
+
+```bash
dapr run --app-id tracingdemo --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.tracing.TracingDemoService -p 3000
```
+
+
Once running, the TracingDemoService is now ready to be invoked by Dapr.
### Running the Demo middle service app
@@ -208,10 +217,19 @@ public class OpenTelemetryConfig {
Use the follow command to execute the service:
-```sh
+
+
+```bash
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:
@@ -247,13 +265,10 @@ private static final String SERVICE_APP_ID = "tracingdemoproxy";
}).subscriberContext(getReactorContext()).block();
}
}
-
- // This is an example, so for simplicity we are just exiting here.
- // Normally a dapr app would be a web service and not exit main.
- System.out.println("Done");
}
span.end();
shutdown();
+ System.out.println("Done");
}
///...
}
@@ -262,9 +277,21 @@ private static final String SERVICE_APP_ID = "tracingdemoproxy";
The class knows the app id for the remote application. It uses `invokeMethod` method to invoke API calls on the service endpoint. The request object includes an instance of `io.opentelemetry.context.Context` for the proper tracing headers to be propagated.
Execute the follow script in order to run the InvokeClient example, passing two messages for the remote method:
-```sh
+
+
+
+```bash
dapr run -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.tracing.InvokeClient "message one" "message two"
```
+
+
+
Open the `tracingdemo` console and check the output as follows:
```txt
diff --git a/examples/src/main/java/io/dapr/examples/tracing/Validation.java b/examples/src/main/java/io/dapr/examples/tracing/Validation.java
new file mode 100644
index 0000000000..dea1e848cd
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/tracing/Validation.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) Microsoft Corporation and Dapr Contributors.
+ * Licensed under the MIT License.
+ */
+
+package io.dapr.examples.tracing;
+
+import com.jayway.jsonpath.DocumentContext;
+import com.jayway.jsonpath.JsonPath;
+import net.minidev.json.JSONArray;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+/**
+ * Class used to verify that traces are present as expected.
+ */
+final class Validation {
+
+ private static final OkHttpClient HTTP_CLIENT = new OkHttpClient();
+
+ public static final String JSONPATH_PROXY_ECHO_SPAN_ID =
+ "$..[?(@.parentId=='%s' && @.name=='calllocal/tracingdemoproxy/proxy_echo')]['id']";
+
+ public static final String JSONPATH_ECHO_SPAN_ID =
+ "$..[?(@.parentId=='%s' && @.name=='calllocal/tracingdemo/echo')]['id']";
+
+ public static final String JSONPATH_PROXY_SLEEP_SPAN_ID =
+ "$..[?(@.parentId=='%s' && @.duration > 1000000 && @.name=='calllocal/tracingdemoproxy/proxy_sleep')]['id']";
+
+ public static final String JSONPATH_SLEEP_SPAN_ID =
+ "$..[?(@.parentId=='%s' && @.duration > 1000000 && @.name=='calllocal/tracingdemo/sleep')]['id']";
+
+ static void validate() throws Exception {
+ // Must wait for some time to make sure Zipkin receives all spans.
+ Thread.sleep(5000);
+ HttpUrl.Builder urlBuilder = new HttpUrl.Builder();
+ urlBuilder.scheme("http")
+ .host("localhost")
+ .port(9411);
+ urlBuilder.addPathSegments("api/v2/traces");
+ Request.Builder requestBuilder = new Request.Builder()
+ .url(urlBuilder.build());
+ requestBuilder.method("GET", null);
+
+ Request request = requestBuilder.build();
+
+ Response response = HTTP_CLIENT.newCall(request).execute();
+ DocumentContext documentContext = JsonPath.parse(response.body().string());
+ String mainSpanId = readOne(documentContext, "$..[?(@.name == \"example's main\")]['id']").toString();
+
+ // Validate echo
+ assertCount(documentContext,
+ String.format(JSONPATH_PROXY_ECHO_SPAN_ID, mainSpanId),
+ 2);
+ String proxyEchoSpanId = readOne(documentContext,
+ String.format(JSONPATH_PROXY_ECHO_SPAN_ID, mainSpanId))
+ .toString();
+ String proxyEchoSpanId2 = readOne(documentContext,
+ String.format(JSONPATH_PROXY_ECHO_SPAN_ID, proxyEchoSpanId))
+ .toString();
+ readOne(documentContext,
+ String.format(JSONPATH_ECHO_SPAN_ID, proxyEchoSpanId2));
+
+ // Validate sleep
+ assertCount(documentContext,
+ String.format(JSONPATH_PROXY_SLEEP_SPAN_ID, mainSpanId),
+ 2);
+ String proxySleepSpanId = readOne(documentContext,
+ String.format(JSONPATH_PROXY_SLEEP_SPAN_ID, mainSpanId))
+ .toString();
+ String proxySleepSpanId2 = readOne(documentContext,
+ String.format(JSONPATH_PROXY_SLEEP_SPAN_ID, proxySleepSpanId))
+ .toString();
+ readOne(documentContext,
+ String.format(JSONPATH_SLEEP_SPAN_ID, proxySleepSpanId2));
+ }
+
+ private static Object readOne(DocumentContext documentContext, String path) {
+ JSONArray arr = documentContext.read(path);
+ if (arr.size() == 0) {
+ throw new RuntimeException("No record found for " + path);
+ }
+
+ return arr.get(0);
+ }
+
+ private static void assertCount(DocumentContext documentContext, String path, int expectedCount) {
+ JSONArray arr = documentContext.read(path);
+ if (arr.size() != expectedCount) {
+ throw new RuntimeException(
+ String.format("Unexpected count %d vs expected %d for %s", arr.size(), expectedCount, path));
+ }
+ }
+
+}