From 244dc58638dee88a2fca784ebccdec18cd5d1392 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 6 Feb 2024 20:41:44 +0700 Subject: [PATCH] Adds Micrometer Tracing Example Signed-off-by: Adrian Cole --- .dockerignore | 1 + .../workflows/deploy-webflux6-micrometer.yml | 46 ++++++++++ .../workflows/test-webflux6-micrometer.yml | 37 ++++++++ README.md | 4 + build-bin/docker/docker_args | 8 ++ build-bin/docker_args | 8 +- docker/Dockerfile | 5 +- webflux6-micrometer/README.md | 15 +++ webflux6-micrometer/pom.xml | 91 +++++++++++++++++++ .../brave/example/AppAutoConfiguration.java | 14 +++ .../src/main/java/brave/example/Backend.java | 30 ++++++ .../src/main/java/brave/example/Frontend.java | 32 +++++++ .../main/resources/META-INF/spring.factories | 2 + .../src/main/resources/application.yaml | 42 +++++++++ 14 files changed, 332 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/deploy-webflux6-micrometer.yml create mode 100644 .github/workflows/test-webflux6-micrometer.yml create mode 100644 webflux6-micrometer/README.md create mode 100644 webflux6-micrometer/pom.xml create mode 100644 webflux6-micrometer/src/main/java/brave/example/AppAutoConfiguration.java create mode 100644 webflux6-micrometer/src/main/java/brave/example/Backend.java create mode 100644 webflux6-micrometer/src/main/java/brave/example/Frontend.java create mode 100644 webflux6-micrometer/src/main/resources/META-INF/spring.factories create mode 100644 webflux6-micrometer/src/main/resources/application.yaml diff --git a/.dockerignore b/.dockerignore index ed4fa17..0078e3e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,6 +14,7 @@ !jersey2-cassandra3/src/main/** !netty4-grpc/src/main/** !webflux5-sleuth/src/main/** +!webflux6-micrometer/src/main/** !webmvc3-jetty/src/main/** !webmvc4-boot/src/main/** !webmvc4-jetty/src/main/** diff --git a/.github/workflows/deploy-webflux6-micrometer.yml b/.github/workflows/deploy-webflux6-micrometer.yml new file mode 100644 index 0000000..95ebcdd --- /dev/null +++ b/.github/workflows/deploy-webflux6-micrometer.yml @@ -0,0 +1,46 @@ +# yamllint --format github .github/workflows/deploy.yml +--- +name: deploy webflux6-micrometer + +on: + # We deploy non-tagged pushes to master relevant for this project. We can't opt out of + # documentation-only commits because GH actions does not permit paths and paths-ignore. + push: + tags: '' + branches: master + paths: + - "build-bin/**" + - "docker/**" + - "webflux6-micrometer/**" + - ".github/workflows/deploy-webflux6-micrometer.yaml" + - "parent-pom.xml" + +jobs: + deploy: + runs-on: ubuntu-22.04 # newest available distribution, aka jellyfish + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Cache local Maven repository + uses: actions/cache@v3 + with: + path: ./m2repository # Shared with the Docker build context via .dockerignore + key: ${{ runner.os }}-webflux6-micrometer-maven-${{ hashFiles('parent-pom.xml', 'webflux6-micrometer/pom.xml') }} + restore-keys: ${{ runner.os }}-webflux6-micrometer-maven- + # Don't attempt to cache Docker. Sensitive information can be stolen + # via forks, and login session ends up in ~/.docker. This is ok because + # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. + - name: Deploy webflux6-micrometer + env: + # GH_USER= + GH_USER: ${{ secrets.GH_USER }} + # GH_TOKEN= + # - pushes Docker images to ghcr.io + # - create via https://github.com/settings/tokens + # - needs repo:status, public_repo, write:packages, delete:packages + GH_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + build-bin/configure_deploy webflux6-micrometer && + build-bin/deploy webflux6-micrometer diff --git a/.github/workflows/test-webflux6-micrometer.yml b/.github/workflows/test-webflux6-micrometer.yml new file mode 100644 index 0000000..8fcb919 --- /dev/null +++ b/.github/workflows/test-webflux6-micrometer.yml @@ -0,0 +1,37 @@ +# yamllint --format github .github/workflows/test.yml +--- +name: test webflux6-micrometer + +on: + # We deploy non-tagged pushes to master relevant for this project. We can't opt out of + # documentation-only commits because GH actions does not permit paths and paths-ignore. + pull_request: + branches: master + paths: + - "build-bin/**" + - "docker/**" + - "webflux6-micrometer/**" + - ".github/workflows/test-webflux6-micrometer.yaml" + - "parent-pom.xml" + +jobs: + test: + runs-on: ubuntu-22.04 # newest available distribution, aka jellyfish + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Cache local Maven repository + uses: actions/cache@v3 + with: + path: ./m2repository # Shared with the Docker build context via .dockerignore + key: ${{ runner.os }}-webflux6-micrometer-maven-${{ hashFiles('parent-pom.xml', 'webflux6-micrometer/pom.xml') }} + restore-keys: ${{ runner.os }}-webflux6-micrometer-maven- + # Don't attempt to cache Docker. Sensitive information can be stolen + # via forks, and login session ends up in ~/.docker. This is ok because + # we publish DOCKER_PARENT_IMAGE to ghcr.io, hence local to the runner. + - name: Test webflux6-micrometer + run: | + build-bin/configure_test webflux6-micrometer && + build-bin/test webflux6-micrometer diff --git a/README.md b/README.md index b48ab32..c48074c 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,10 @@ Here are the example projects you can try: * Trace Instrumentation: [WebFlux Server](https://github.com/spring-cloud/spring-cloud-sleuth/blob/2.2.x/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java), [WebFlux Client](https://github.com/spring-cloud/spring-cloud-sleuth/blob/2.2.x/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java), [Reactor Context](https://github.com/spring-cloud/spring-cloud-sleuth/blob/2.2.x/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ScopePassingSpanSubscriber.java), [SLF4J](https://github.com/openzipkin/brave/tree/master/context/slf4j) * Trace Configuration: [Spring Cloud Sleuth](https://github.com/spring-cloud/spring-cloud-sleuth/tree/2.2.x/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig) [Properties](webflux5-sleuth/src/main/resources/application.properties) +* [webflux6-micrometer](webflux6-micrometer) `BRAVE_EXAMPLE=webflux6-micrometer docker-compose up` + * Runtime: Spring 6, Reactor Netty, Spring Boot 3, Micrometer, Log4J 2, JRE 21 + * Trace Configuration: [Spring Boot Actuator](https://github.com/spring-projects/spring-boot/blob/3.2.x/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java) + * [webmvc25-jetty](webmvc25-jetty) `BRAVE_EXAMPLE=webmvc25-jetty docker-compose up` * Runtime: Spring 2.5, Apache HttpClient 4.3, Servlet 2.5, Jetty 7.6, Log4J 1.2, JRE 6 * Trace Instrumentation: [Servlet](https://github.com/openzipkin/brave/tree/master/instrumentation/servlet), [Spring MVC](https://github.com/openzipkin/brave/tree/master/instrumentation/spring-webmvc), [Apache HttpClient](https://github.com/openzipkin/brave/tree/master/instrumentation/httpclient), [Log4J 1.2](https://github.com/openzipkin/brave/tree/master/context/log4j12) diff --git a/build-bin/docker/docker_args b/build-bin/docker/docker_args index fe711a0..8736196 100755 --- a/build-bin/docker/docker_args +++ b/build-bin/docker/docker_args @@ -45,6 +45,14 @@ if [ -n "${DOCKER_TARGET}" ]; then docker_args="${docker_args} --target ${DOCKER_TARGET}" fi +# When non-empty, becomes the layer that builds the maven projects. +# e.g. ghcr.io/openzipkin/java:11.0.22_p7 +# +# This must include maven and a full JDK. +if [ -n "${DOCKER_BUILD_IMAGE}" ]; then + docker_args="${docker_args} --build-arg docker_build_image=${DOCKER_BUILD_IMAGE}" +fi + # When non-empty, becomes the base layer including tag appropriate for the image being built. # e.g. ghcr.io/openzipkin/java:21.0.2_p13-jre # diff --git a/build-bin/docker_args b/build-bin/docker_args index d4fb434..70cfdf6 100755 --- a/build-bin/docker_args +++ b/build-bin/docker_args @@ -14,25 +14,31 @@ DOCKER_ARCHS="amd64 arm64" case "${JRE_VERSION}" in 6 ) + DOCKER_BUILD_IMAGE=ghcr.io/openzipkin/java:11.0.22_p7 DOCKER_PARENT_IMAGE=ghcr.io/openzipkin/java:1.6.0-119 # single arch image DOCKER_ARCHS=amd64 ;; 7 ) + DOCKER_BUILD_IMAGE=ghcr.io/openzipkin/java:11.0.22_p7 DOCKER_PARENT_IMAGE=ghcr.io/openzipkin/java:1.7.0_285 # single arch image DOCKER_ARCHS=amd64 ;; 8 ) + DOCKER_BUILD_IMAGE=ghcr.io/openzipkin/java:11.0.22_p7 DOCKER_PARENT_IMAGE=ghcr.io/openzipkin/java:8.392.08-jre ;; 11 ) + DOCKER_BUILD_IMAGE=ghcr.io/openzipkin/java:11.0.22_p7 DOCKER_PARENT_IMAGE=ghcr.io/openzipkin/java:11.0.22_p7-jre ;; 17 ) + DOCKER_BUILD_IMAGE=ghcr.io/openzipkin/java:21.0.2_p13 DOCKER_PARENT_IMAGE=ghcr.io/openzipkin/java:17.0.10_p7-jre ;; 21 ) + DOCKER_BUILD_IMAGE=ghcr.io/openzipkin/java:21.0.2_p13 DOCKER_PARENT_IMAGE=ghcr.io/openzipkin/java:21.0.2_p13-jre ;; * ) @@ -40,4 +46,4 @@ case "${JRE_VERSION}" in exit 1 esac -export DOCKER_PARENT_IMAGE DOCKER_ARCHS +export DOCKER_BUILD_IMAGE DOCKER_PARENT_IMAGE DOCKER_ARCHS diff --git a/docker/Dockerfile b/docker/Dockerfile index 93fae4c..9fb2705 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,6 +3,8 @@ # The image binaries this example builds are installed over ARG docker_parent_image=ghcr.io/openzipkin/java:21.0.2_p13 +## Use JDK 11 to build projects, as that can still compile Java 6 +ARG docker_build_image=ghcr.io/openzipkin/java:11.0.22_p7 # We copy files from the context into a scratch container first to avoid a problem where docker and # docker-compose don't share layer hashes https://github.com/docker/compose/issues/883 normally. @@ -11,8 +13,7 @@ FROM scratch as scratch COPY . /code/ -## Use JDK 11 to build projects, as that can still compile Java 6 -FROM ghcr.io/openzipkin/java:11.0.22_p7 as install +FROM $docker_build_image as install WORKDIR /code COPY --from=scratch /code . diff --git a/webflux6-micrometer/README.md b/webflux6-micrometer/README.md new file mode 100644 index 0000000..c564dfc --- /dev/null +++ b/webflux6-micrometer/README.md @@ -0,0 +1,15 @@ +## Tracing Example: Spring 6/Reactor Netty/Spring Boot 3/Micrometer/Log4J 2 + +Instead of servlet, this uses Spring Boot 3 to create a self-contained +application that runs Spring WebFlux 6 controllers. + +* brave.example.Frontend and Backend: Rest controllers +* brave.example.AppAutoConfiguration: Sets up the WebClient used in the Frontend. + +Application code doesn't show any tracing configuration because that's handled +by [Micrometer Tracing](https://docs.micrometer.io/tracing/reference/index.html), +configured by [Spring Boot](https://docs.spring.io/spring-boot/docs/3.2.2/reference/htmlsingle/#actuator.micrometer-tracing.tracer-implementations.brave-zipkin). + +Micrometer Tracing has its own Tracer API that can bridge to Brave's with a +plugin. This allows users to mix Micrometer with native Brave instrumentation. +However, this example does not use any native Brave instrumentation. diff --git a/webflux6-micrometer/pom.xml b/webflux6-micrometer/pom.xml new file mode 100644 index 0000000..5e34053 --- /dev/null +++ b/webflux6-micrometer/pom.xml @@ -0,0 +1,91 @@ + + 4.0.0 + + io.zipkin.brave.example + brave-example-parent + 1.0-SNAPSHOT + ../parent-pom.xml + + + brave-example-webflux6-micrometer + jar + + brave-example-webflux6-micrometer + Tracing Example: Spring 6, Reactor Netty, Spring Boot 3, Micrometer, Log4J 2, JRE 21 + + + 21 + 17 + + 3.2.2 + 1.2.2 + + + 5.17.1 + 2.17.2 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-actuator + ${spring-boot.version} + + + + + io.micrometer + micrometer-tracing-bridge-brave + ${micrometer.version} + + + io.zipkin.reporter2 + zipkin-reporter-brave + ${zipkin-reporter.version} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + brave.example.Backend + exec + true + + + + + diff --git a/webflux6-micrometer/src/main/java/brave/example/AppAutoConfiguration.java b/webflux6-micrometer/src/main/java/brave/example/AppAutoConfiguration.java new file mode 100644 index 0000000..ffded96 --- /dev/null +++ b/webflux6-micrometer/src/main/java/brave/example/AppAutoConfiguration.java @@ -0,0 +1,14 @@ +package brave.example; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +/** The application is simple, it only uses WebFlux. */ +// This type makes support easier as forks can make a diff off a working AutoConfiguration type */ +@Configuration +public class AppAutoConfiguration { + @Bean WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } +} diff --git a/webflux6-micrometer/src/main/java/brave/example/Backend.java b/webflux6-micrometer/src/main/java/brave/example/Backend.java new file mode 100644 index 0000000..ce3efc8 --- /dev/null +++ b/webflux6-micrometer/src/main/java/brave/example/Backend.java @@ -0,0 +1,30 @@ +package brave.example; + +import java.time.LocalDate; +import java.util.Optional; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@EnableAutoConfiguration +@RestController +public class Backend { + + @GetMapping("/api") + public Mono printDate(@RequestHeader("user_name") Optional username) { + return Mono.fromSupplier(() -> { + String date = LocalDate.now().toString(); + return username.map(u -> date + " " + u).orElse(date); + }); + } + + public static void main(String[] args) { + SpringApplication.run(Backend.class, + "--spring.application.name=backend", + "--server.port=9000" + ); + } +} diff --git a/webflux6-micrometer/src/main/java/brave/example/Frontend.java b/webflux6-micrometer/src/main/java/brave/example/Frontend.java new file mode 100644 index 0000000..e303102 --- /dev/null +++ b/webflux6-micrometer/src/main/java/brave/example/Frontend.java @@ -0,0 +1,32 @@ +package brave.example; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +@EnableAutoConfiguration +@RestController +public class Frontend { + final WebClient webClient; + + @Autowired Frontend(WebClient.Builder webClientBuilder, + @Value("${backend.endpoint:http://127.0.0.1:9000/api}") String backendEndpoint) { + this.webClient = webClientBuilder.baseUrl(backendEndpoint).build(); + } + + @GetMapping("/") public Mono callBackend() { + return webClient.get().uri("").retrieve().bodyToMono(String.class); + } + + public static void main(String[] args) { + SpringApplication.run(Frontend.class, + "--spring.application.name=frontend", + "--server.port=8081" + ); + } +} diff --git a/webflux6-micrometer/src/main/resources/META-INF/spring.factories b/webflux6-micrometer/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..f41e69f --- /dev/null +++ b/webflux6-micrometer/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +brave.example.AppAutoConfiguration diff --git a/webflux6-micrometer/src/main/resources/application.yaml b/webflux6-micrometer/src/main/resources/application.yaml new file mode 100644 index 0000000..107629a --- /dev/null +++ b/webflux6-micrometer/src/main/resources/application.yaml @@ -0,0 +1,42 @@ +management: + # only expose /health from actuator + endpoints: + enabled-by-default: false + web: + base-path: '' + path-mapping: + health: /health + endpoint: + health: + enabled: true + + tracing: + brave: + # Note: There is no property to bind ${brave.traceId128Bit:false} + span-joining-supported: ${brave.supportsJoin:true} + sampling: + # There doesn't seem to be a way to skip tracing /health by property + probability: 1.0 + # Propagates a field named 'user_name' downstream + # Note: In Spring Boot 3 it is not yet possible to map 'user_name' to the + # correlation field 'userName'. + baggage: + remote-fields: + - user_name + correlation: + fields: + - user_name + propagation: + # Use b3 to mix with other examples without adding brave-propagation-w3c + type: b3_multi + zipkin: + tracing: + # Note: There is no property to bind ${brave.localServiceName:${spring.application.name}} + endpoint: ${zipkin.baseUrl:http://127.0.0.1:9411}/api/v2/spans + +logging: + level: + root: INFO + pattern: + # Note: Logs don't appear to have any correlation fields assigned. + level: "[%X{user_name}] [%X{traceId}/%X{spanId}] %-5p [%t] %C{2} - %m%n"