From 773cb02ad57ed24106d2633cc96d40ffa8a436f8 Mon Sep 17 00:00:00 2001 From: brunobat Date: Thu, 6 Oct 2022 19:20:50 +0100 Subject: [PATCH] workflows documentation about configurations Jaeger extension plus documentation skeleton based on extension from the main quarkus repository project structure --- .github/dependabot.yml | 13 ++ .github/project.yml | 5 + .github/workflows/build.yml | 48 ++++++++ .github/workflows/pre-release.yml | 25 ++++ .github/workflows/quarkus-snapshot.yaml | 52 ++++++++ .github/workflows/release.yml | 86 +++++++++++++ .gitignore | 3 + CONTRIBUTING.md | 26 ++++ README.md | 15 ++- docs/antora.yml | 5 + docs/modules/ROOT/assets/images/.keepme | 0 docs/modules/ROOT/examples/.keepme | 0 docs/modules/ROOT/nav.adoc | 2 + .../ROOT/pages/includes/attributes.adoc | 3 + docs/modules/ROOT/pages/index.adoc | 13 ++ ...quarkus-opentelemetry-exporter-jaeger.adoc | 31 +++++ docs/pom.xml | 106 ++++++++++++++++ docs/templates/includes/attributes.adoc | 3 + pom.xml | 92 ++++++++++++++ .../deployment/pom.xml | 71 +++++++++++ .../JaegerExporterAlwaysEnabledProcessor.java | 15 +++ .../deployment/JaegerExporterProcessor.java | 40 ++++++ .../JaegerExporterBadEndpointTest.java | 27 ++++ .../JaegerExporterDisabledTest.java | 32 +++++ .../integration-tests/pom.xml | 116 ++++++++++++++++++ .../exporter/it/SimpleResource.java | 88 +++++++++++++ .../opentelemetry/exporter/it/TraceData.java | 5 + .../exporter/it/TracedService.java | 13 ++ .../it/output/SpanDataModuleSerializer.java | 19 +++ .../it/output/SpanDataSerializer.java | 54 ++++++++ .../src/main/resources/application.properties | 9 ++ .../exporter/it/JaegerExportIT.java | 7 ++ .../exporter/it/JaegerExporterTest.java | 70 +++++++++++ .../exporter/it/JaegerTestResource.java | 42 +++++++ quarkus-opentelemetry-exporter-jaeger/pom.xml | 23 ++++ .../runtime/pom.xml | 102 +++++++++++++++ .../jaeger/runtime/JaegerExporterConfig.java | 42 +++++++ .../runtime/JaegerExporterProvider.java | 16 +++ .../jaeger/runtime/JaegerRecorder.java | 40 ++++++ .../jaeger/runtime/JaegerSubstitutions.java | 49 ++++++++ .../runtime/LateBoundBatchSpanProcessor.java | 112 +++++++++++++++++ .../resources/META-INF/quarkus-extension.yaml | 15 +++ 42 files changed, 1532 insertions(+), 3 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/project.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/pre-release.yml create mode 100644 .github/workflows/quarkus-snapshot.yaml create mode 100644 .github/workflows/release.yml create mode 100644 CONTRIBUTING.md create mode 100644 docs/antora.yml create mode 100644 docs/modules/ROOT/assets/images/.keepme create mode 100644 docs/modules/ROOT/examples/.keepme create mode 100644 docs/modules/ROOT/nav.adoc create mode 100644 docs/modules/ROOT/pages/includes/attributes.adoc create mode 100644 docs/modules/ROOT/pages/index.adoc create mode 100644 docs/modules/ROOT/pages/quarkus-opentelemetry-exporter-jaeger.adoc create mode 100644 docs/pom.xml create mode 100644 docs/templates/includes/attributes.adoc create mode 100644 pom.xml create mode 100644 quarkus-opentelemetry-exporter-jaeger/deployment/pom.xml create mode 100644 quarkus-opentelemetry-exporter-jaeger/deployment/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterAlwaysEnabledProcessor.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/deployment/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterProcessor.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/deployment/src/test/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterBadEndpointTest.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/deployment/src/test/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterDisabledTest.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/integration-tests/pom.xml create mode 100644 quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/SimpleResource.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/TraceData.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/TracedService.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/output/SpanDataModuleSerializer.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/output/SpanDataSerializer.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/resources/application.properties create mode 100644 quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerExportIT.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerExporterTest.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerTestResource.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/pom.xml create mode 100644 quarkus-opentelemetry-exporter-jaeger/runtime/pom.xml create mode 100644 quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerExporterConfig.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerExporterProvider.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerRecorder.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerSubstitutions.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/LateBoundBatchSpanProcessor.java create mode 100644 quarkus-opentelemetry-exporter-jaeger/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4b11d34 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" + ignore: + - dependency-name: "org.apache.maven.plugins:maven-compiler-plugin" diff --git a/.github/project.yml b/.github/project.yml new file mode 100644 index 0000000..7c3ce46 --- /dev/null +++ b/.github/project.yml @@ -0,0 +1,5 @@ +name: Quarkus OpenTelemetry Exporters +release: + current-version: "0" + next-version: "999-SNAPSHOT" + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c7aa10d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,48 @@ +name: Build + +on: + push: + branches: + - "main" + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + pull_request: + paths-ignore: + - '.gitignore' + - 'CODEOWNERS' + - 'LICENSE' + - '*.md' + - '*.adoc' + - '*.txt' + - '.all-contributorsrc' + +jobs: + build: + name: Build on ${{ matrix.os }} + strategy: + fail-fast: false + matrix: +# os: [windows-latest, macos-latest, ubuntu-latest] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Prepare git + run: git config --global core.autocrlf false + if: startsWith(matrix.os, 'windows') + + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + distribution: temurin + java-version: 11 + cache: 'maven' + + - name: Build with Maven + run: mvn -B formatter:validate clean install --file pom.xml diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 0000000..63d9d0f --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -0,0 +1,25 @@ +name: Quarkiverse Pre Release + +on: + pull_request: + paths: + - '.github/project.yml' + +jobs: + release: + runs-on: ubuntu-latest + name: pre release + + steps: + - uses: radcortez/project-metadata-action@master + name: retrieve project metadata + id: metadata + with: + github-token: ${{secrets.GITHUB_TOKEN}} + metadata-file-path: '.github/project.yml' + + - name: Validate version + if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT') + run: | + echo '::error::Cannot release a SNAPSHOT version.' + exit 1 \ No newline at end of file diff --git a/.github/workflows/quarkus-snapshot.yaml b/.github/workflows/quarkus-snapshot.yaml new file mode 100644 index 0000000..311001f --- /dev/null +++ b/.github/workflows/quarkus-snapshot.yaml @@ -0,0 +1,52 @@ +name: "Quarkus ecosystem CI" +on: + workflow_dispatch: + watch: + types: [started] + + # For this CI to work, ECOSYSTEM_CI_TOKEN needs to contain a GitHub with rights to close the Quarkus issue that the user/bot has opened, + # while 'ECOSYSTEM_CI_REPO_PATH' needs to be set to the corresponding path in the 'quarkusio/quarkus-ecosystem-ci' repository + +env: + ECOSYSTEM_CI_REPO: quarkusio/quarkus-ecosystem-ci + ECOSYSTEM_CI_REPO_FILE: context.yaml + JAVA_VERSION: 11 + + ######################### + # Repo specific setting # + ######################### + + ECOSYSTEM_CI_REPO_PATH: quarkiverse-opentelemetry-exporter + +jobs: + build: + name: "Build against latest Quarkus snapshot" + runs-on: ubuntu-latest + # Allow to manually launch the ecosystem CI in addition to the bots + if: github.actor == 'quarkusbot' || github.actor == 'quarkiversebot' || github.actor == '' + + steps: + - name: Install yq + run: sudo add-apt-repository ppa:rmescandon/yq && sudo apt update && sudo apt install yq -y + + - name: Set up Java + uses: actions/setup-java@v2 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} + + - name: Checkout repo + uses: actions/checkout@v2 + with: + path: current-repo + + - name: Checkout Ecosystem + uses: actions/checkout@v2 + with: + repository: ${{ env.ECOSYSTEM_CI_REPO }} + path: ecosystem-ci + + - name: Setup and Run Tests + run: ./ecosystem-ci/setup-and-test + env: + ECOSYSTEM_CI_TOKEN: ${{ secrets.ECOSYSTEM_CI_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..00c011e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,86 @@ +name: Quarkiverse Release + +on: + pull_request: + types: [closed] + paths: + - '.github/project.yml' + +jobs: + release: + runs-on: ubuntu-latest + name: release + if: ${{github.event.pull_request.merged == true}} + + steps: + - uses: radcortez/project-metadata-action@main + name: Retrieve project metadata + id: metadata + with: + github-token: ${{secrets.GITHUB_TOKEN}} + metadata-file-path: '.github/project.yml' + + - uses: actions/checkout@v3 + + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v3 + with: + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + cache: 'maven' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + + - name: Configure Git author + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Maven release ${{steps.metadata.outputs.current-version}} + run: | + git checkout -b release + mvn -B release:prepare -Prelease -DpreparationGoals="clean install" -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}} + if ! git diff --quiet docs/modules/ROOT/pages/includes/attributes.adoc; then + git add docs/modules/ROOT/pages/includes/attributes.adoc + git commit -m "Update stable version for documentation" + fi + git checkout ${{github.base_ref}} + git rebase release + mvn -B release:perform -Darguments=-DperformRelease -DperformRelease -Prelease + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + + - name: Adjust tag for documentation changes + run: | + git checkout ${{steps.metadata.outputs.current-version}} + mvn -B clean install -DskipTests -DskipITs + if ! git diff --quiet docs/modules/ROOT/pages/includes/attributes.adoc; then + git add docs/modules/ROOT/pages/includes/attributes.adoc + git commit -m "Update stable version for documentation" + # Move the tag after inclusion of documentation adjustments + git tag -f ${{steps.metadata.outputs.current-version}} + fi + # Go back to main + git checkout main + + - name: Push changes to ${{github.base_ref}} + uses: ad-m/github-push-action@v0.6.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{github.base_ref}} + + - name: Push tags + uses: ad-m/github-push-action@v0.6.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + tags: true + branch: ${{github.base_ref}} diff --git a/.gitignore b/.gitignore index 0e13eeb..a9264b5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ buildNumber.properties .mvn/timing.properties # https://github.com/takari/maven-wrapper#usage-without-binary-jar .mvn/wrapper/maven-wrapper.jar +/.idea/.gitignore +/.idea/ +/docs/modules/ROOT/pages/includes/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c3c88f4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Contributing + +Pile on! The help is welcome. + +## Contributing an additional registry + +It should be easy enough to follow the model of other registry implementations and sort out what else needs to happen to make your new registry work well. + +### Docs + +* The `docs` folder contains the root of the tree for building documentation. +* Configuration docs are automatically generated. They will show up in the `target` directory of root project after you build. +* The `docs` module build will copy those files into the `docs/modules/ROOT/pages/includes` directory +* Create a document, `micrometer-registry-` for the new module in the `docs/modules/ROOT/pages/` directory. + It should minimally contain the following: + ```asciidoc + = // (1) + + include::./includes/attributes.adoc[] + + == Configuration + include::./includes/quarkus-micrometer-export-.adoc[] // (2) + ``` + 1. Each registry has a nice name for titles. ;) + 2. If you look in the `target` directory of the root project after a build, you should see three files generated for each registry: one for build configuration, one for runtime configuration, and one for both. Use the one that contains both, which should be named similarly. +* Add this document (alphabetically) to `docs/modules/ROOT/nav.adoc` diff --git a/README.md b/README.md index 823234b..a7900a3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ -# quarkus-opentelemetry-exporter -Quarkus extensions related with additional OpenTelemetry exporters +# Quarkus OpenTelemetry Exporters +## About Exporters + +Exporters are OpenTelemetry SDK Plugins which implement the Exporter interface, and emit telemetry to consumers, usually observability vendors. +Currently, we support this implementation: +* Jaeger + +## Documentation + +The documentation for this extension is stored in the `docs/` directory. + +Please ask questions on stackoverflow (using quarkus and opentelemetry tags, or join us in the Quarkus Zulip chatroom, -Work in progress diff --git a/docs/antora.yml b/docs/antora.yml new file mode 100644 index 0000000..de3a4be --- /dev/null +++ b/docs/antora.yml @@ -0,0 +1,5 @@ +name: quarkus-opentelemetry-exporter +title: Quarkus Opentelemetry Exporter +version: dev +nav: + - modules/ROOT/nav.adoc diff --git a/docs/modules/ROOT/assets/images/.keepme b/docs/modules/ROOT/assets/images/.keepme new file mode 100644 index 0000000..e69de29 diff --git a/docs/modules/ROOT/examples/.keepme b/docs/modules/ROOT/examples/.keepme new file mode 100644 index 0000000..e69de29 diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc new file mode 100644 index 0000000..904d917 --- /dev/null +++ b/docs/modules/ROOT/nav.adoc @@ -0,0 +1,2 @@ +* xref:index.adoc[Introduction] +* xref:quarkus-opentelemetry-exporter-jaeger.adoc[Quarkus Opentelemetry Exporter Jaeger] diff --git a/docs/modules/ROOT/pages/includes/attributes.adoc b/docs/modules/ROOT/pages/includes/attributes.adoc new file mode 100644 index 0000000..19a687e --- /dev/null +++ b/docs/modules/ROOT/pages/includes/attributes.adoc @@ -0,0 +1,3 @@ +:project-version: 0 + +:examples-dir: ./../examples/ \ No newline at end of file diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 0000000..c15abbd --- /dev/null +++ b/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,13 @@ += Quarkus OpenTelemetry Exporter + +include::./includes/attributes.adoc[] + +TIP: Exporters are OpenTelemetry (OTel) SDK Plugins which implement the Exporter interface, and emit telemetry to consumers, usually observability vendors. + +Quarkus already includes the default OTLP exporter. This project will host additional ones. + +== Additional resources +* https://quarkus.io/guides/opentelemetry[Quarkus OpenTelemetry guide] +* https://opentelemetry.io/docs/concepts/data-collection/[OTel Data collection] + +Please ask questions on stackoverflow (using `quarkus` and `opentelemetry` tags, or join us in the https://quarkusio.zulipchat.com/[Quarkus Zulip] chatroom, \ No newline at end of file diff --git a/docs/modules/ROOT/pages/quarkus-opentelemetry-exporter-jaeger.adoc b/docs/modules/ROOT/pages/quarkus-opentelemetry-exporter-jaeger.adoc new file mode 100644 index 0000000..3514b96 --- /dev/null +++ b/docs/modules/ROOT/pages/quarkus-opentelemetry-exporter-jaeger.adoc @@ -0,0 +1,31 @@ += Quarkus Opentelemetry Exporter Jaeger + +include::./includes/attributes.adoc[] + +Exporters are OpenTelemetry SDK Plugins which implement the Exporter interface, and emit telemetry to consumers, usually observability vendors. + +This exporter send data to Jaeger using native protocol, previous to OTLP. + +== Installation + +If you want to use this extension, you need to add the `io.quarkiverse.opentelemetry.exporter:quarkus-opentelemetry-exporter` extension first to your build file. + +For instance, with Maven, add the following dependency to your POM file: + +[source,xml,subs=attributes+] +---- + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-jaeger + {project-version} + +---- + +Please mind the quarkus-opentelemetry extension already includes the default OTLP exporter. To deactivate that default exporter and only have this Jaeger extension exporting data, please set the following property: + +`quarkus.opentelemetry.tracer.exporter.otlp.enabled=false` + +[[extension-configuration-reference]] +== Extension Configuration Reference + +include::includes/quarkus-opentelemetry-tracer-exporter-jaeger.adoc[leveloffset=+1, opts=optional] diff --git a/docs/pom.xml b/docs/pom.xml new file mode 100644 index 0000000..c722a99 --- /dev/null +++ b/docs/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-opentelemetry-exporter-docs + Quarkus Opentelemetry Exporter - Documentation + + + + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-jaeger + ${project.version} + + + + + modules/ROOT/examples + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + it.ozimov + yaml-properties-maven-plugin + + + initialize + + read-project-properties + + + + ${project.basedir}/../.github/project.yml + + + + + + + maven-resources-plugin + + + copy-resources + prepare-package + + copy-resources + + + modules/ROOT/pages/includes + + + ${project.basedir}/../target/asciidoc/generated/config/ + quarkus-opentelemetry-tracer-exporter-*.adoc + false + + + ${project.basedir}/templates/includes + attributes.adoc + true + + + + + + copy-images + prepare-package + + copy-resources + + + target/generated-docs/_images + + + ${project.basedir}/modules/ROOT/assets/images/ + false + + + + + + + + org.asciidoctor + asciidoctor-maven-plugin + + + + + diff --git a/docs/templates/includes/attributes.adoc b/docs/templates/includes/attributes.adoc new file mode 100644 index 0000000..e1a2881 --- /dev/null +++ b/docs/templates/includes/attributes.adoc @@ -0,0 +1,3 @@ +:project-version: ${release.current-version} + +:examples-dir: ./../examples/ \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..fe9f842 --- /dev/null +++ b/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + io.quarkiverse + quarkiverse-parent + 10 + + + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-parent + 999-SNAPSHOT + pom + Quarkus Opentelemetry Exporter - Parent + + + quarkus-opentelemetry-exporter-jaeger + docs + + + + 3.8.1 + 11 + + UTF-8 + UTF-8 + + 2.13.1.Final + 1.18.0 + 1.18.0-alpha + + 3.23.1 + 1.17.5 + + + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + io.opentelemetry + opentelemetry-bom + ${opentelemetry.version} + pom + import + + + io.opentelemetry + opentelemetry-bom-alpha + ${opentelemetry-alpha.version} + pom + import + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + ${opentelemetry-alpha.version} + pom + import + + + + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + + + diff --git a/quarkus-opentelemetry-exporter-jaeger/deployment/pom.xml b/quarkus-opentelemetry-exporter-jaeger/deployment/pom.xml new file mode 100644 index 0000000..14fff9b --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/deployment/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-jaeger-parent + 999-SNAPSHOT + + + quarkus-opentelemetry-exporter-jaeger-deployment + Quarkus Opentelemetry Exporter Jaeger- Deployment + + + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-jaeger + ${project.version} + + + + io.quarkus + quarkus-opentelemetry-deployment + + + io.quarkus + quarkus-grpc-common-deployment + + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + org.assertj + assertj-core + ${assertj-core.version} + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/quarkus-opentelemetry-exporter-jaeger/deployment/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterAlwaysEnabledProcessor.java b/quarkus-opentelemetry-exporter-jaeger/deployment/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterAlwaysEnabledProcessor.java new file mode 100644 index 0000000..cacd1df --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/deployment/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterAlwaysEnabledProcessor.java @@ -0,0 +1,15 @@ +package io.quarkiverse.opentelemetry.exporter.jaeger.deployment; + +import io.quarkus.deployment.Feature; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +// Executed even if the extension is disabled, see https://github.com/quarkusio/quarkus/pull/26966/ +public class JaegerExporterAlwaysEnabledProcessor { + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(Feature.OPENTELEMETRY_JAEGER_EXPORTER); + } + +} diff --git a/quarkus-opentelemetry-exporter-jaeger/deployment/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterProcessor.java b/quarkus-opentelemetry-exporter-jaeger/deployment/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterProcessor.java new file mode 100644 index 0000000..7ad285c --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/deployment/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterProcessor.java @@ -0,0 +1,40 @@ +package io.quarkiverse.opentelemetry.exporter.jaeger.deployment; + +import java.util.function.BooleanSupplier; + +import io.quarkiverse.opentelemetry.exporter.jaeger.runtime.JaegerExporterConfig; +import io.quarkiverse.opentelemetry.exporter.jaeger.runtime.JaegerExporterProvider; +import io.quarkiverse.opentelemetry.exporter.jaeger.runtime.JaegerRecorder; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; + +@BuildSteps(onlyIf = JaegerExporterProcessor.JaegerExporterEnabled.class) +public class JaegerExporterProcessor { + + static class JaegerExporterEnabled implements BooleanSupplier { + JaegerExporterConfig.JaegerExporterBuildConfig jaegerExporterConfig; + + public boolean getAsBoolean() { + return jaegerExporterConfig.enabled; + } + } + + @BuildStep + AdditionalBeanBuildItem createBatchSpanProcessor() { + return AdditionalBeanBuildItem.builder() + .addBeanClass(JaegerExporterProvider.class) + .setUnremovable().build(); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + void installBatchSpanProcessorForJaeger(JaegerRecorder recorder, + LaunchModeBuildItem launchModeBuildItem, + JaegerExporterConfig.JaegerExporterRuntimeConfig runtimeConfig) { + recorder.installBatchSpanProcessorForJaeger(runtimeConfig, launchModeBuildItem.getLaunchMode()); + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/deployment/src/test/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterBadEndpointTest.java b/quarkus-opentelemetry-exporter-jaeger/deployment/src/test/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterBadEndpointTest.java new file mode 100644 index 0000000..4ce34c7 --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/deployment/src/test/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterBadEndpointTest.java @@ -0,0 +1,27 @@ +package io.quarkiverse.opentelemetry.exporter.jaeger.deployment; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.api.OpenTelemetry; +import io.quarkus.test.QuarkusUnitTest; + +public class JaegerExporterBadEndpointTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withEmptyApplication() + .overrideConfigKey("quarkus.opentelemetry.tracer.exporter.jaeger.endpoint", "httz://nada:zero") + .setExpectedException(IllegalStateException.class); + + @Inject + OpenTelemetry openTelemetry; + + @Test + void failStart() { + Assertions.fail("Test should not be run as deployment should fail"); + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/deployment/src/test/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterDisabledTest.java b/quarkus-opentelemetry-exporter-jaeger/deployment/src/test/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterDisabledTest.java new file mode 100644 index 0000000..5b21ee7 --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/deployment/src/test/java/io/quarkiverse/opentelemetry/exporter/jaeger/deployment/JaegerExporterDisabledTest.java @@ -0,0 +1,32 @@ +package io.quarkiverse.opentelemetry.exporter.jaeger.deployment; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.opentelemetry.api.OpenTelemetry; +import io.quarkiverse.opentelemetry.exporter.jaeger.runtime.LateBoundBatchSpanProcessor; +import io.quarkus.test.QuarkusUnitTest; + +public class JaegerExporterDisabledTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withEmptyApplication() + .overrideConfigKey("quarkus.opentelemetry.tracer.exporter.jaeger.enabled", "false"); + + @Inject + OpenTelemetry openTelemetry; + + @Inject + Instance lateBoundBatchSpanProcessorInstance; + + @Test + void testOpenTelemetryButNoBatchSpanProcessor() { + Assertions.assertNotNull(openTelemetry); + Assertions.assertFalse(lateBoundBatchSpanProcessorInstance.isResolvable()); + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/integration-tests/pom.xml b/quarkus-opentelemetry-exporter-jaeger/integration-tests/pom.xml new file mode 100644 index 0000000..2d308a9 --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/integration-tests/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-jaeger-parent + 999-SNAPSHOT + + + quarkus-opentelemetry-exporter-jaeger-integration-tests + Quarkus Opentelemetry Exporter Jaeger - Integration Tests + + + true + + + + + io.quarkus + quarkus-resteasy-jackson + + + io.quarkus + quarkus-rest-client + + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-jaeger + ${project.version} + + + + io.quarkus + quarkus-junit5 + test + + + org.testcontainers + junit-jupiter + ${junit-jupiter.version} + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + + native-image + + + native + + + + + + maven-surefire-plugin + + ${native.surefire.skip} + + + + + + false + native + + + + diff --git a/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/SimpleResource.java b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/SimpleResource.java new file mode 100644 index 0000000..ddc1dfc --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/SimpleResource.java @@ -0,0 +1,88 @@ +package io.quarkiverse.opentelemetry.exporter.it; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Path("") +@Produces(MediaType.APPLICATION_JSON) +public class SimpleResource { + @RegisterRestClient(configKey = "simple") + public interface SimpleClient { + @Path("") + @GET + TraceData noPath(); + + @Path("/") + @GET + TraceData slashPath(); + } + + @Inject + TracedService tracedService; + + @Inject + @RestClient + SimpleClient simpleClient; + + @GET + public TraceData noPath() { + TraceData data = new TraceData(); + data.message = "No path trace"; + return data; + } + + @GET + @Path("/nopath") + public TraceData noPathClient() { + return simpleClient.noPath(); + } + + @GET + @Path("/slashpath") + public TraceData slashPathClient() { + return simpleClient.slashPath(); + } + + @GET + @Path("/direct") + public TraceData directTrace() { + TraceData data = new TraceData(); + data.message = "Direct trace"; + + return data; + } + + @GET + @Path("/chained") + public TraceData chainedTrace() { + TraceData data = new TraceData(); + data.message = tracedService.call(); + + return data; + } + + @GET + @Path("/deep/path") + public TraceData deepUrlPathTrace() { + TraceData data = new TraceData(); + data.message = "Deep url path"; + + return data; + } + + @GET + @Path("/param/{paramId}") + public TraceData pathParameters(@PathParam("paramId") String paramId) { + TraceData data = new TraceData(); + data.message = "ParameterId: " + paramId; + + return data; + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/TraceData.java b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/TraceData.java new file mode 100644 index 0000000..b59319c --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/TraceData.java @@ -0,0 +1,5 @@ +package io.quarkiverse.opentelemetry.exporter.it; + +public class TraceData { + public String message; +} diff --git a/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/TracedService.java b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/TracedService.java new file mode 100644 index 0000000..3ed2c8c --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/TracedService.java @@ -0,0 +1,13 @@ +package io.quarkiverse.opentelemetry.exporter.it; + +import javax.enterprise.context.ApplicationScoped; + +import io.opentelemetry.instrumentation.annotations.WithSpan; + +@ApplicationScoped +public class TracedService { + @WithSpan + public String call() { + return "Chained trace"; + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/output/SpanDataModuleSerializer.java b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/output/SpanDataModuleSerializer.java new file mode 100644 index 0000000..41daa6e --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/output/SpanDataModuleSerializer.java @@ -0,0 +1,19 @@ +package io.quarkiverse.opentelemetry.exporter.it.output; + +import javax.inject.Singleton; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.jackson.ObjectMapperCustomizer; + +@Singleton +public class SpanDataModuleSerializer implements ObjectMapperCustomizer { + @Override + public void customize(ObjectMapper objectMapper) { + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(SpanData.class, new SpanDataSerializer()); + objectMapper.registerModule(simpleModule); + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/output/SpanDataSerializer.java b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/output/SpanDataSerializer.java new file mode 100644 index 0000000..29fe780 --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/java/io/quarkiverse/opentelemetry/exporter/it/output/SpanDataSerializer.java @@ -0,0 +1,54 @@ +package io.quarkiverse.opentelemetry.exporter.it.output; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import io.opentelemetry.sdk.trace.data.SpanData; + +public class SpanDataSerializer extends StdSerializer { + public SpanDataSerializer() { + this(null); + } + + public SpanDataSerializer(Class type) { + super(type); + } + + @Override + public void serialize(SpanData spanData, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) + throws IOException { + jsonGenerator.writeStartObject(); + + jsonGenerator.writeStringField("spanId", spanData.getSpanId()); + jsonGenerator.writeStringField("traceId", spanData.getTraceId()); + jsonGenerator.writeStringField("name", spanData.getName()); + jsonGenerator.writeStringField("kind", spanData.getKind().name()); + jsonGenerator.writeBooleanField("ended", spanData.hasEnded()); + + jsonGenerator.writeStringField("parent_spanId", spanData.getParentSpanContext().getSpanId()); + jsonGenerator.writeStringField("parent_traceId", spanData.getParentSpanContext().getTraceId()); + jsonGenerator.writeBooleanField("parent_remote", spanData.getParentSpanContext().isRemote()); + jsonGenerator.writeBooleanField("parent_valid", spanData.getParentSpanContext().isValid()); + + spanData.getAttributes().forEach((k, v) -> { + try { + jsonGenerator.writeStringField("attr_" + k.getKey(), v.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + spanData.getResource().getAttributes().forEach((k, v) -> { + try { + jsonGenerator.writeStringField("resource_" + k.getKey(), v.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + jsonGenerator.writeEndObject(); + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/resources/application.properties b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/resources/application.properties new file mode 100644 index 0000000..34f939e --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/main/resources/application.properties @@ -0,0 +1,9 @@ +# Setting these for tests explicitly. Not required in normal application +quarkus.application.name=opentelemetry-exporter-jaeger-integration-test +quarkus.application.version=999-SNAPSHOT + +pingpong/mp-rest/url=${test.url} +simple/mp-rest/url=${test.url} + +quarkus.opentelemetry.tracer.exporter.jaeger.enabled=true +quarkus.opentelemetry.tracer.exporter.jaeger.endpoint=http://localhost:14250 \ No newline at end of file diff --git a/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerExportIT.java b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerExportIT.java new file mode 100644 index 0000000..7f1d6c0 --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerExportIT.java @@ -0,0 +1,7 @@ +package io.quarkiverse.opentelemetry.exporter.it; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class JaegerExportIT extends JaegerExporterTest { +} diff --git a/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerExporterTest.java b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerExporterTest.java new file mode 100644 index 0000000..87fa3b3 --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerExporterTest.java @@ -0,0 +1,70 @@ +package io.quarkiverse.opentelemetry.exporter.it; + +import static io.restassured.RestAssured.get; +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Duration; +import java.util.List; +import java.util.Map; + +import org.awaitility.Awaitility; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; + +@QuarkusTest +@QuarkusTestResource(JaegerTestResource.class) +public class JaegerExporterTest { + + public static final String SERVICE_NAME = "opentelemetry-exporter-jaeger-integration-test"; + + @ConfigProperty(name = "quarkus.jaeger.port") + String jaegerPort; + + /** + * Response format example: + * result = {LinkedHashMap@11959} size = 5 + * "data" -> {ArrayList@11969} size = 0 + * "total" -> {Integer@11971} 0 + * "limit" -> {Integer@11971} 0 + * "offset" -> {Integer@11971} 0 + * "errors" -> null + */ + private static Map getJaegerTrace() { + Map as = get("/api/traces?service=" + SERVICE_NAME) + .body().as(new TypeRef<>() { + }); + return as; + } + + @Test + public void jaegerExtensionTest() { + given() + .contentType("application/json") + .when().get("/direct") + .then() + .statusCode(200) + .body("message", equalTo("Direct trace")); + + RestAssured.port = Integer.parseInt(jaegerPort); + + Awaitility.await() + .atMost(Duration.ofSeconds(30)) + .until(() -> ((List) getJaegerTrace().get("data")).size() > 0); + + final List data = (List) getJaegerTrace().get("data"); + final Map actual = (Map) data.get(0); + final List spans = (List) actual.get("spans"); + + assertEquals(1, data.size()); + assertEquals(1, spans.size()); + assertEquals("/direct", ((Map) spans.get(0)).get("operationName")); + } + +} diff --git a/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerTestResource.java b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerTestResource.java new file mode 100644 index 0000000..adf6346 --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/integration-tests/src/test/java/io/quarkiverse/opentelemetry/exporter/it/JaegerTestResource.java @@ -0,0 +1,42 @@ +package io.quarkiverse.opentelemetry.exporter.it; + +import java.util.Map; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.PullPolicy; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class JaegerTestResource implements QuarkusTestResourceLifecycleManager { + + private static final int QUERY_PORT = 16686; + private static final int COLLECTOR_PORT = 14250; + private static final int HEALTH_PORT = 14269; + + private GenericContainer jaegerContainer; + + @Override + public void init(Map initArgs) { + QuarkusTestResourceLifecycleManager.super.init(initArgs); + jaegerContainer = new GenericContainer<>("ghcr.io/open-telemetry/opentelemetry-java/jaeger:1.32") + .withImagePullPolicy(PullPolicy.alwaysPull()) + .withExposedPorts(COLLECTOR_PORT, QUERY_PORT, HEALTH_PORT) + .waitingFor(Wait.forHttp("/").forPort(HEALTH_PORT)); + } + + @Override + public Map start() { + jaegerContainer.start(); + final Map properties = Map.of( + "quarkus.jaeger.port", "" + jaegerContainer.getMappedPort(QUERY_PORT), + "quarkus.opentelemetry.tracer.exporter.jaeger.endpoint", "http://localhost:" + + jaegerContainer.getMappedPort(COLLECTOR_PORT)); + return properties; + } + + @Override + public void stop() { + jaegerContainer.stop(); + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/pom.xml b/quarkus-opentelemetry-exporter-jaeger/pom.xml new file mode 100644 index 0000000..72e0e3c --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-parent + 999-SNAPSHOT + + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-jaeger-parent + pom + Quarkus Opentelemetry Exporter - Jaeger + + + runtime + deployment + integration-tests + + + diff --git a/quarkus-opentelemetry-exporter-jaeger/runtime/pom.xml b/quarkus-opentelemetry-exporter-jaeger/runtime/pom.xml new file mode 100644 index 0000000..bef6d55 --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/runtime/pom.xml @@ -0,0 +1,102 @@ + + + 4.0.0 + + io.quarkiverse.opentelemetry.exporter + quarkus-opentelemetry-exporter-jaeger-parent + 999-SNAPSHOT + + + quarkus-opentelemetry-exporter-jaeger + Quarkus Opentelemetry Exporter Jaeger - Runtime + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-arc + + + + io.quarkus + quarkus-opentelemetry + + + io.quarkus + quarkus-grpc-common + + + + io.opentelemetry + opentelemetry-exporter-otlp-common + + + org.codehaus.mojo + animal-sniffer-annotations + + + org.checkerframework + checker-qual + + + + + + io.opentelemetry + opentelemetry-exporter-jaeger + + + org.codehaus.mojo + animal-sniffer-annotations + + + org.checkerframework + checker-qual + + + + + + org.graalvm.nativeimage + svm + provided + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${quarkus.version} + + + compile + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerExporterConfig.java b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerExporterConfig.java new file mode 100644 index 0000000..b78c1cc --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerExporterConfig.java @@ -0,0 +1,42 @@ +package io.quarkiverse.opentelemetry.exporter.jaeger.runtime; + +import java.time.Duration; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.runtime.annotations.ConvertWith; +import io.quarkus.runtime.configuration.TrimmedStringConverter; + +public class JaegerExporterConfig { + @ConfigRoot(name = "opentelemetry.tracer.exporter.jaeger", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) + public static class JaegerExporterBuildConfig { + /** + * Jaeger SpanExporter support. + *

+ * Jaeger SpanExporter support is enabled by default. + */ + @ConfigItem(defaultValue = "true") + public Boolean enabled; + } + + @ConfigRoot(name = "opentelemetry.tracer.exporter.jaeger", phase = ConfigPhase.RUN_TIME) + public static class JaegerExporterRuntimeConfig { + /** + * The Jaeger endpoint to connect to. The endpoint must start with either http:// or https://. + */ + @ConfigItem + @ConvertWith(TrimmedStringConverter.class) + public Optional endpoint; + + /** + * The maximum amount of time to wait for the collector to process exported spans before an exception is thrown. + * A value of `0` will disable the timeout: the exporter will continue waiting until either exported spans are + * processed, + * or the connection fails, or is closed for some other reason. + */ + @ConfigItem(defaultValue = "10S") + public Duration exportTimeout; + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerExporterProvider.java b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerExporterProvider.java new file mode 100644 index 0000000..6b573d4 --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerExporterProvider.java @@ -0,0 +1,16 @@ +package io.quarkiverse.opentelemetry.exporter.jaeger.runtime; + +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; + +import io.quarkus.arc.DefaultBean; + +@Singleton +public class JaegerExporterProvider { + @Produces + @Singleton + @DefaultBean + public LateBoundBatchSpanProcessor batchSpanProcessorForJaeger() { + return new LateBoundBatchSpanProcessor(); + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerRecorder.java b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerRecorder.java new file mode 100644 index 0000000..80c2b5a --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerRecorder.java @@ -0,0 +1,40 @@ +package io.quarkiverse.opentelemetry.exporter.jaeger.runtime; + +import java.util.Optional; + +import javax.enterprise.inject.Any; +import javax.enterprise.inject.spi.CDI; + +import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.quarkus.runtime.LaunchMode; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class JaegerRecorder { + public void installBatchSpanProcessorForJaeger(JaegerExporterConfig.JaegerExporterRuntimeConfig runtimeConfig, + LaunchMode launchMode) { + + if (launchMode == LaunchMode.DEVELOPMENT && !runtimeConfig.endpoint.isPresent()) { + // Default the endpoint for development only + runtimeConfig.endpoint = Optional.of("http://localhost:14250"); + } + + // Only create the JaegerGrpcSpanExporter if an endpoint was set in runtime config + if (runtimeConfig.endpoint.isPresent() && runtimeConfig.endpoint.get().trim().length() > 0) { + try { + JaegerGrpcSpanExporter jaegerSpanExporter = JaegerGrpcSpanExporter.builder() + .setEndpoint(runtimeConfig.endpoint.get()) + .setTimeout(runtimeConfig.exportTimeout) + .build(); + + // Create BatchSpanProcessor for Jaeger and install into LateBoundBatchSpanProcessor + LateBoundBatchSpanProcessor delayedProcessor = CDI.current() + .select(LateBoundBatchSpanProcessor.class, Any.Literal.INSTANCE).get(); + delayedProcessor.setBatchSpanProcessorDelegate(BatchSpanProcessor.builder(jaegerSpanExporter).build()); + } catch (IllegalArgumentException iae) { + throw new IllegalStateException("Unable to install Jaeger Exporter", iae); + } + } + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerSubstitutions.java b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerSubstitutions.java new file mode 100644 index 0000000..742194d --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/JaegerSubstitutions.java @@ -0,0 +1,49 @@ +package io.quarkiverse.opentelemetry.exporter.jaeger.runtime; + +import static java.util.Objects.requireNonNull; + +import javax.net.ssl.SSLException; +import javax.net.ssl.X509TrustManager; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import io.grpc.ManagedChannelBuilder; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.NettyChannelBuilder; +import io.opentelemetry.exporter.internal.grpc.ManagedChannelUtil; + +/** + * Replace the {@link ManagedChannelUtil#setClientKeysAndTrustedCertificatesPem(ManagedChannelBuilder, byte[], byte[], byte[])} + * method in native + * because the method implementation tries to look for grpc-netty-shaded dependencies, which we don't support. + * + * Check: + * https://github.com/open-telemetry/opentelemetry-java/blob/v1.13.0/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/grpc/ManagedChannelUtil.java#L47-L91 + */ +final class JaegerSubstitutions { + @TargetClass(ManagedChannelUtil.class) + static final class Target_ManagedChannelUtil { + @Substitute + public static void setClientKeysAndTrustedCertificatesPem( + ManagedChannelBuilder managedChannelBuilder, byte[] privateKeyPem, byte[] certificatePem, + byte[] trustedCertificatesPem) + throws SSLException { + requireNonNull(managedChannelBuilder, "managedChannelBuilder"); + requireNonNull(trustedCertificatesPem, "trustedCertificatesPem"); + + X509TrustManager tm = io.opentelemetry.exporter.internal.TlsUtil.trustManager(trustedCertificatesPem); + + // gRPC does not abstract TLS configuration so we need to check the implementation and act + // accordingly. + if (managedChannelBuilder.getClass().getName().equals("io.grpc.netty.NettyChannelBuilder")) { + NettyChannelBuilder nettyBuilder = (NettyChannelBuilder) managedChannelBuilder; + nettyBuilder.sslContext(GrpcSslContexts.forClient().trustManager(tm).build()); + } else { + throw new SSLException( + "TLS certificate configuration not supported for unrecognized ManagedChannelBuilder " + + managedChannelBuilder.getClass().getName()); + } + } + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/LateBoundBatchSpanProcessor.java b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/LateBoundBatchSpanProcessor.java new file mode 100644 index 0000000..0d3c39a --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/java/io/quarkiverse/opentelemetry/exporter/jaeger/runtime/LateBoundBatchSpanProcessor.java @@ -0,0 +1,112 @@ +package io.quarkiverse.opentelemetry.exporter.jaeger.runtime; + +import org.jboss.logging.Logger; + +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; + +/** + * Class to facilitate a delay in when the worker thread inside {@link BatchSpanProcessor} + * is started, enabling Quarkus to instantiate a {@link io.opentelemetry.api.trace.TracerProvider} + * during static initialization and set a {@link BatchSpanProcessor} delegate during runtime initialization. + */ +public class LateBoundBatchSpanProcessor implements SpanProcessor { + private static final Logger log = Logger.getLogger(LateBoundBatchSpanProcessor.class); + + private boolean warningLogged = false; + private BatchSpanProcessor delegate; + + /** + * Set the actual {@link BatchSpanProcessor} to use as the delegate. + * + * @param delegate Properly constructed {@link BatchSpanProcessor} for processing spans. + */ + public void setBatchSpanProcessorDelegate(BatchSpanProcessor delegate) { + this.delegate = delegate; + } + + @Override + public void onStart(Context parentContext, ReadWriteSpan span) { + if (delegate == null) { + logDelegateNotFound(); + return; + } + delegate.onStart(parentContext, span); + } + + @Override + public boolean isStartRequired() { + if (delegate == null) { + logDelegateNotFound(); + return false; + } + return delegate.isStartRequired(); + } + + @Override + public void onEnd(ReadableSpan span) { + if (delegate == null) { + logDelegateNotFound(); + return; + } + delegate.onEnd(span); + } + + @Override + public boolean isEndRequired() { + if (delegate == null) { + logDelegateNotFound(); + return true; + } + return delegate.isEndRequired(); + } + + @Override + public CompletableResultCode shutdown() { + if (delegate == null) { + logDelegateNotFound(); + return CompletableResultCode.ofSuccess(); + } + return delegate.shutdown(); + } + + @Override + public CompletableResultCode forceFlush() { + if (delegate == null) { + logDelegateNotFound(); + return CompletableResultCode.ofSuccess(); + } + return delegate.forceFlush(); + } + + @Override + public void close() { + if (delegate != null) { + delegate.close(); + } + resetDelegate(); + } + + /** + * Clear the {@code delegate} and reset {@code warningLogged}. + */ + private void resetDelegate() { + delegate = null; + warningLogged = false; + } + + /** + * If we haven't previously logged an error, + * log an error about a missing {@code delegate} and set {@code warningLogged=true} + */ + private void logDelegateNotFound() { + if (!warningLogged) { + log.warn("No BatchSpanProcessor delegate specified, no action taken."); + warningLogged = true; + } + } +} diff --git a/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000..722c0eb --- /dev/null +++ b/quarkus-opentelemetry-exporter-jaeger/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,15 @@ +name: "Quarkus OpenTelemetry exporter: Jaeger" +artifact: ${project.groupId}:${project.artifactId}:${project.version} +metadata: + keywords: + - "opentelemetry" + - "tracing" + - "distributed-tracing" + - "opentelemetry-jaeger-exporter" + - "monitoring" + guide: "https://quarkus.io/guides/opentelemetry" + categories: + - "observability" + status: "experimental" + config: + - "quarkus.opentelemetry."