diff --git a/.github/workflows/benchmark-tags.yml b/.github/workflows/benchmark-tags.yml index f43fda3bef3..124651823fb 100644 --- a/.github/workflows/benchmark-tags.yml +++ b/.github/workflows/benchmark-tags.yml @@ -13,7 +13,7 @@ jobs: name: Benchmark SDK runs-on: oracle-bare-metal-64cpu-1024gb-x86-64-ubuntu-24 container: - image: ubuntu:26.04@sha256:3131b4cc82a783df6c9df078f86e01819a13594b865c2cad47bd1bca2b7063bb + image: ubuntu:26.04@sha256:678c6550cc43645e08669028bc177f50be4e7c5b8cca677067b1914d4afc7a03 timeout-minutes: 20 # since there is only a single bare metal runner across all repos strategy: fail-fast: false @@ -28,13 +28,13 @@ jobs: - id: setup-java name: Set up Java for build - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin java-version: 21 - name: Set up gradle - uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Run jmh run: ./gradlew jmhJar env: diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 258a22c2f9d..89ff69f63d9 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -16,7 +16,7 @@ jobs: name: Benchmark SDK runs-on: oracle-bare-metal-64cpu-1024gb-x86-64-ubuntu-24 container: - image: ubuntu:26.04@sha256:3131b4cc82a783df6c9df078f86e01819a13594b865c2cad47bd1bca2b7063bb + image: ubuntu:26.04@sha256:678c6550cc43645e08669028bc177f50be4e7c5b8cca677067b1914d4afc7a03 timeout-minutes: 120 # since there is only a single bare metal runner across all repos steps: - name: Install Git @@ -31,13 +31,13 @@ jobs: - id: setup-java name: Set up Java for build - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin java-version: 21 - name: Set up gradle - uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Run jmh run: ./gradlew jmhJar env: diff --git a/.github/workflows/build-daily.yml b/.github/workflows/build-daily.yml index 15f3f8c03f1..0b0b97c9d55 100644 --- a/.github/workflows/build-daily.yml +++ b/.github/workflows/build-daily.yml @@ -22,13 +22,13 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Java - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin java-version: 21 - name: Set up gradle - uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Publish to Sonatype run: ./gradlew assemble publishToSonatype diff --git a/.github/workflows/build-tracecontext-testsuite.yml b/.github/workflows/build-tracecontext-testsuite.yml index de7586806a6..aefa81a86bd 100644 --- a/.github/workflows/build-tracecontext-testsuite.yml +++ b/.github/workflows/build-tracecontext-testsuite.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Login to GitHub package registry - uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aef1339eef9..25acff015e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,11 +31,12 @@ jobs: - 11 - 17 - 21 + - 25 - 26 # renovate(java-version) # Collect coverage on latest LTS include: - os: ubuntu-latest - test-java-version: 21 + test-java-version: 25 coverage: true jmh-based-tests: true steps: @@ -43,7 +44,7 @@ jobs: - id: setup-java-test name: Set up Java ${{ matrix.test-java-version }} for tests - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: # using zulu for 26 because new releases get published quickly distribution: zulu @@ -51,13 +52,13 @@ jobs: - id: setup-java name: Set up Java for build - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin java-version: 21 - name: Set up gradle - uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Build run: > ./gradlew build diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c59bd531bef..4f74db1b12b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -35,17 +35,17 @@ jobs: - name: Set up Java 21 if: matrix.language == 'java' - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin java-version: 21 - name: Set up gradle if: matrix.language == 'java' - uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Initialize CodeQL - uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 + uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: languages: ${{ matrix.language }} # using "linked" helps to keep up with the latest Kotlin support @@ -62,7 +62,7 @@ jobs: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 + uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 5eb702bacbf..c1ec5b7ea7e 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -14,4 +14,4 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # this needs to be in its own workflow in order to make OSSF scorecard happy - - uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + - uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 diff --git a/.github/workflows/javadoc-crawler.yml b/.github/workflows/javadoc-crawler.yml index 886cc7cee3f..a4ef63579e7 100644 --- a/.github/workflows/javadoc-crawler.yml +++ b/.github/workflows/javadoc-crawler.yml @@ -15,13 +15,13 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin java-version: 21 - name: Set up gradle - uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Run crawler run: ./gradlew :javadoc-crawler:crawl diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index c36df2a374b..f38713eb78d 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -44,7 +44,7 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 + uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: sarif_file: results.sarif diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f1de71af56..4ae73d35c52 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,13 +23,13 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin java-version: 21 - name: Set up gradle - uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Build and publish artifacts run: ./gradlew assemble publishToSonatype closeAndReleaseSonatypeStagingRepository @@ -182,13 +182,13 @@ jobs: done - name: Set up JDK for running Gradle - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin java-version: 21 - name: Set up Gradle - uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Update apidiff baseline env: diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 09f9b4bd61d..61e297a7343 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -14,7 +14,7 @@ jobs: with: fetch-depth: 0 # needed for merge-base used in modified-files mode - - uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3 + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 - name: Link check for pull requests if: github.event_name == 'pull_request' diff --git a/.github/workflows/sonatype-guide-dependency-audit-daily.yml b/.github/workflows/sonatype-guide-dependency-audit-daily.yml index 2f0334bff80..0c5c2448c5f 100644 --- a/.github/workflows/sonatype-guide-dependency-audit-daily.yml +++ b/.github/workflows/sonatype-guide-dependency-audit-daily.yml @@ -18,13 +18,13 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin java-version: 21 - name: Set up gradle - uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 # --no-parallel is needed to avoid OverlappingFileLockException on the shared OSS Index cache - run: ./gradlew ossIndexAudit --no-configuration-cache --no-parallel --info diff --git a/CHANGELOG.md b/CHANGELOG.md index cf04a42f4d9..6252a43ec29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,13 @@ * Logging: Output event name in `SystemOutLogRecordExporter` ([#8609](https://github.com/open-telemetry/opentelemetry-java/pull/8609)) +### Shims + +* OpenTracing: Return the extracted `SpanContext` when it is sampled but invalid + ([#8634](https://github.com/open-telemetry/opentelemetry-java/pull/8634)) +* Add OpenCensus links to the OpenTelemetry span instead of logging a warning and dropping them + ([#8635](https://github.com/open-telemetry/opentelemetry-java/pull/8635)) + ## Version 1.64.0 (2026-07-10) ### API diff --git a/README.md b/README.md index 311bfd7a294..372a1d90d8a 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,6 @@ SDK exporters for shipping traces, metrics, and logs out of process. | [OTLP Logging Exporters](./exporters/logging-otlp) | Logging exporters in OTLP JSON encoding, including traces, metrics, and logs | `opentelemetry-exporter-logging-otlp` | 1.64.0 | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-exporter-logging-otlp.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-exporter-logging-otlp) | | [OTLP Common](./exporters/otlp/common) | Shared OTLP components (internal) | `opentelemetry-exporter-otlp-common` | 1.64.0 | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-exporter-otlp-common.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-exporter-otlp-common) | | [Logging Exporter](./exporters/logging) | Logging exporters, including metrics, traces, and logs | `opentelemetry-exporter-logging` | 1.64.0 | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-exporter-logging.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-exporter-logging) | -| [Zipkin Exporter](./exporters/zipkin) | **DEPRECATED** Zipkin trace exporter | `opentelemetry-exporter-zipkin` | 1.64.0 | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-exporter-zipkin.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-exporter-zipkin) | | [Prometheus Exporter](./exporters/prometheus) | Prometheus metric exporter | `opentelemetry-exporter-prometheus` | 1.64.0-alpha | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-exporter-prometheus.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-exporter-prometheus) | | [Exporter Common](./exporters/common) | Shared exporter components (internal) | `opentelemetry-exporter-common` | 1.64.0 | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-exporter-common.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-exporter-common) | | [OkHttp Sender](./exporters/sender/okhttp) | OkHttp implementation of HttpSender (internal) | `opentelemetry-exporter-sender-okhttp` | 1.64.0 | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-exporter-sender-okhttp.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-exporter-sender-okhttp) | @@ -142,7 +141,7 @@ Shims for bridging data from one observability library to another. | Component | Description | Artifact ID | Version | Javadoc | |----------------------------------------|--------------------------------------------------------------|----------------------------------|-------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [OpenCensus Shim](./opencensus-shim) | Bridge opencensus metrics into the OpenTelemetry metrics SDK | `opentelemetry-opencensus-shim` | 1.64.0-alpha | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-opencensus-shim.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-opencensus-shim) | +| [OpenCensus Shim](./opencensus-shim) | **DEPRECATED** Bridge opencensus metrics into the OpenTelemetry metrics SDK | `opentelemetry-opencensus-shim` | 1.64.0-alpha | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-opencensus-shim.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-opencensus-shim) | | [OpenTracing Shim](./opentracing-shim) | **DEPRECATED** Bridge opentracing spans into the OpenTelemetry trace API | `opentelemetry-opentracing-shim` | 1.64.0 | [![Javadocs](https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-opentracing-shim.svg)](https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-opentracing-shim) | diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 092909c5743..bf414902e86 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -31,11 +31,9 @@ val DEPENDENCY_BOMS = listOf( "com.google.guava:guava-bom:33.6.0-jre", "com.google.protobuf:protobuf-bom:4.35.1", "com.squareup.okhttp3:okhttp-bom:$okhttpVersion", - "com.squareup.okio:okio-bom:3.18.0", // applies to transitive dependencies of okhttp - "io.grpc:grpc-bom:1.83.0", + "com.squareup.okio:okio-bom:3.18.1", // applies to transitive dependencies of okhttp + "io.grpc:grpc-bom:1.83.1", "io.netty:netty-bom:4.2.16.Final", - "io.zipkin.brave:brave-bom:6.3.1", - "io.zipkin.reporter2:zipkin-reporter-bom:3.5.3", "org.assertj:assertj-bom:3.27.7", "org.osgi:org.osgi.test.bom:1.3.0", "org.testcontainers:testcontainers-bom:2.0.5", @@ -74,12 +72,12 @@ val DEPENDENCIES = listOf( "io.prometheus:prometheus-metrics-exposition-formats-no-protobuf:${prometheusServerVersion}", "javax.annotation:javax.annotation-api:1.3.2", "com.github.stefanbirkner:system-rules:1.19.0", - "com.google.api.grpc:proto-google-common-protos:2.73.0", + "com.google.api.grpc:proto-google-common-protos:2.74.0", "com.google.code.findbugs:jsr305:3.0.2", "com.google.guava:guava-beta-checker:1.0", "com.sun.net.httpserver:http:20070405", "com.squareup.okhttp3:okhttp:$okhttpVersion", - "com.tngtech.archunit:archunit-junit5:1.4.2", + "com.tngtech.archunit:archunit-junit5:1.5.0", "com.uber.nullaway:nullaway:0.13.8", "edu.berkeley.cs.jqf:jqf-fuzz:1.7", // jqf-fuzz version 1.8+ requires Java 11+ "eu.rekawek.toxiproxy:toxiproxy-java:2.1.11", @@ -92,10 +90,7 @@ val DEPENDENCIES = listOf( "io.opentelemetry.proto:opentelemetry-proto:1.11.0-alpha", "io.opentracing:opentracing-api:0.33.0", "io.opentracing:opentracing-noop:0.33.0", - // zipkin-reporter-bom depends on zipkin v2.x for java 6 compatibility - // we only need java 8 compatibility. upgrade to 3.x to avoid problematic shaded gson dependency version - // See: https://github.com/open-telemetry/opentelemetry-java/issues/8427 - "io.zipkin.zipkin2:zipkin:3.6.1", + "io.zipkin.brave:brave:6.3.1", "junit:junit:4.13.2", "nl.jqno.equalsverifier:equalsverifier:3.19.4", "org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle:1.3.7", diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt deleted file mode 100644 index 11551cefc22..00000000000 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt +++ /dev/null @@ -1,2 +0,0 @@ -Comparing source compatibility of opentelemetry-exporter-zipkin-1.65.0-SNAPSHOT.jar against opentelemetry-exporter-zipkin-1.64.0.jar -No changes. \ No newline at end of file diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/StringEncoderHolder.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/StringEncoderHolder.java index 3f0155b20fe..ba64ba4ebda 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/StringEncoderHolder.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/StringEncoderHolder.java @@ -39,6 +39,12 @@ public static StringEncoder createFallbackEncoder() { */ @Nullable public static StringEncoder createUnsafeEncoder() { + // Android exposes sun.misc.Unsafe, but String does not have the OpenJDK internal fields used by + // UnsafeStringEncoder. ART logs an error when these missing fields are queried, even though the + // exception is caught. + if ("Dalvik".equals(System.getProperty("java.vm.name"))) { + return null; + } return UnsafeStringEncoder.createIfAvailable(); } diff --git a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/marshal/StringEncoderTest.java b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/marshal/StringEncoderTest.java index 6a0f3e8a813..a0b117292cf 100644 --- a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/marshal/StringEncoderTest.java +++ b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/marshal/StringEncoderTest.java @@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.JRE; +import org.junitpioneer.jupiter.SetSystemProperty; class StringEncoderTest { @@ -57,6 +58,12 @@ void testUtf8SizeLatin1_VarHandle() { testUtf8SizeLatin1(varHandleStringEncoder); } + @Test + @SetSystemProperty(key = "java.vm.name", value = "Dalvik") + void unsafeStringEncoderNotAvailableOnAndroid() { + assertThat(StringEncoderHolder.createUnsafeEncoder()).isNull(); + } + @SuppressWarnings("AvoidEscapedUnicodeCharacters") private static void testUtf8Encoding(StringEncoder stringEncoder) { assertThat(stringEncoder).isNotNull(); diff --git a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingLogRecordExporter.java b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingLogRecordExporter.java index 0c00cca908e..f45ccc0f62c 100644 --- a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingLogRecordExporter.java +++ b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingLogRecordExporter.java @@ -17,6 +17,9 @@ * A {@link LogRecordExporter} which writes {@linkplain LogRecordData logs} to a {@link Logger} in * OTLP JSON format. Each log line will include a single {@code ResourceLogs}. * + *

Note: This class is superseded by {@link OtlpStdoutLogRecordExporter}, which allows + * configuring a custom logger or output stream. + * * @since 1.19.0 */ public final class OtlpJsonLoggingLogRecordExporter implements LogRecordExporter { diff --git a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingMetricExporter.java b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingMetricExporter.java index 520b047f5f8..23a5579d0c5 100644 --- a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingMetricExporter.java +++ b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingMetricExporter.java @@ -18,6 +18,9 @@ /** * A {@link MetricExporter} which writes {@linkplain MetricData metrics} to a {@link Logger} in OTLP * JSON format. Each log line will include a single {@code ResourceMetrics}. + * + *

Note: This class is superseded by {@link OtlpStdoutMetricExporter}, which allows configuring a + * custom logger or output stream. */ public final class OtlpJsonLoggingMetricExporter implements MetricExporter { diff --git a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingSpanExporter.java b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingSpanExporter.java index 63901351326..a2500935cc2 100644 --- a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingSpanExporter.java +++ b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/OtlpJsonLoggingSpanExporter.java @@ -16,6 +16,9 @@ /** * A {@link SpanExporter} which writes {@linkplain SpanData spans} to a {@link Logger} in OTLP JSON * format. Each log line will include a single {@code ResourceSpans}. + * + *

Note: This class is superseded by {@link OtlpStdoutSpanExporter}, which allows configuring a + * custom logger or output stream. */ public final class OtlpJsonLoggingSpanExporter implements SpanExporter { diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java index 27872dbf03b..5948edb3b3e 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/OtlpConfigUtil.java @@ -165,7 +165,7 @@ static void configureOtlpHeaders( // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables addHeader.accept(key, URLDecoder.decode(value, StandardCharsets.UTF_8.name())); } catch (Exception e) { - throw new ConfigurationException("Cannot decode header value: " + value, e); + throw new ConfigurationException("Cannot decode header value for header: " + key, e); } } } diff --git a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProviderTest.java b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProviderTest.java index 1454d209da4..827fdc009ba 100644 --- a/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProviderTest.java +++ b/exporters/otlp/all/src/test/java/io/opentelemetry/exporter/otlp/internal/OtlpSpanExporterProviderTest.java @@ -303,9 +303,10 @@ void createExporter_decodingError() { () -> { provider.createExporter( DefaultConfigProperties.createFromMap( - Collections.singletonMap("otel.exporter.otlp.headers", "header-key=%-1"))); + Collections.singletonMap( + "otel.exporter.otlp.headers", "header-key=Bearer%20s3cr3t%-1"))); }) .isInstanceOf(ConfigurationException.class) - .hasMessage("Cannot decode header value: %-1"); + .hasMessage("Cannot decode header value for header: header-key"); } } diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java index 7b85bb027ac..156c721e449 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusHttpServerBuilder.java @@ -97,7 +97,7 @@ public PrometheusHttpServerBuilder setOtelScopeLabelsEnabled(boolean otelScopeLa return this; } - /** Set if the {@code otel_target_info} metric is generated. Default is {@code true}. */ + /** Set if the {@code target_info} metric is generated. Default is {@code true}. */ public PrometheusHttpServerBuilder setTargetInfoMetricEnabled(boolean targetInfoMetricEnabled) { metricReaderBuilder.setTargetInfoMetricEnabled(targetInfoMetricEnabled); return this; diff --git a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java index d9c1e490051..878cca34781 100644 --- a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java +++ b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java @@ -188,7 +188,7 @@ private void handleResponse(Response response, Consumer onResponse compressed = body.source().readByte() != 0; body.source().skip(4); // message length — we bound reads by EOF instead } catch (IOException e) { - logger.log(Level.FINE, "Invalid gRPC response frame"); + logger.log(Level.FINE, "Invalid gRPC response frame", e); onResponse.accept( ImmutableGrpcResponse.create(grpcStatus(response), grpcMessage(response), new byte[0])); return; diff --git a/exporters/zipkin/README.md b/exporters/zipkin/README.md deleted file mode 100644 index 9fb38662a12..00000000000 --- a/exporters/zipkin/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# OpenTelemetry - Zipkin Span Exporter - -## Attribution - -The code in this module is based on the [OpenCensus Zipkin exporter][oc-origin] code. - -[oc-origin]: https://github.com/census-instrumentation/opencensus-java/ diff --git a/exporters/zipkin/build.gradle.kts b/exporters/zipkin/build.gradle.kts deleted file mode 100644 index c46c0d15dfd..00000000000 --- a/exporters/zipkin/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -plugins { - id("otel.java-conventions") - id("otel.publish-conventions") - - id("otel.animalsniffer-conventions") -} - -description = "OpenTelemetry - Zipkin Exporter" -otelJava.moduleName.set("io.opentelemetry.exporter.zipkin") -otelJava.osgiOptionalPackages.set(listOf("io.opentelemetry.sdk.autoconfigure.spi")) -otelJava.osgiServiceLoaderProvides.set(listOf( - "io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider", -)) - -dependencies { - api(project(":sdk:all")) - - api("io.zipkin.reporter2:zipkin-reporter") - - compileOnly(project(":sdk-extensions:autoconfigure-spi")) - compileOnly(project(":api:incubator")) - - implementation("io.zipkin.reporter2:zipkin-sender-okhttp3") - - testImplementation(project(":sdk:testing")) - testImplementation(project(":sdk-extensions:autoconfigure-spi")) - - testImplementation("com.linecorp.armeria:armeria") - testImplementation("org.testcontainers:testcontainers-junit-jupiter") -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/BytesEncoderAdapter.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/BytesEncoderAdapter.java deleted file mode 100644 index 8605eacd936..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/BytesEncoderAdapter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import zipkin2.Span; -import zipkin2.reporter.BytesEncoder; -import zipkin2.reporter.Encoding; - -/** - * This supports the deprecated method {@link - * ZipkinSpanExporterBuilder#setEncoder(zipkin2.codec.BytesEncoder)}. - */ -final class BytesEncoderAdapter implements BytesEncoder { - private final zipkin2.codec.BytesEncoder delegate; - private final Encoding encoding; - - @SuppressWarnings("deprecation") // we have to use the deprecated thrift encoding to return it - BytesEncoderAdapter(zipkin2.codec.BytesEncoder delegate) { - this.delegate = delegate; - switch (delegate.encoding()) { - case JSON: - this.encoding = Encoding.JSON; - break; - case PROTO3: - this.encoding = Encoding.PROTO3; - break; - case THRIFT: - this.encoding = Encoding.THRIFT; - break; - default: - // Only possible if zipkin2 adds an encoding besides above, which is very unlikely. - throw new UnsupportedOperationException("unsupported encoding " + delegate.encoding()); - } - } - - @Override - public Encoding encoding() { - return encoding; - } - - @Override - public int sizeInBytes(Span span) { - return delegate.sizeInBytes(span); - } - - @Override - public byte[] encode(Span span) { - return delegate.encode(span); - } - - @Override - public String toString() { - return delegate.toString(); - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java deleted file mode 100644 index 855488a8b52..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotation.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import static java.util.stream.Collectors.joining; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.Value; -import io.opentelemetry.api.common.ValueType; -import io.opentelemetry.sdk.trace.data.EventData; -import java.util.List; - -/** - * Converts an EventData instance to a String representation of that data, with attributes converted - * to JSON. - * - *

See the - * zipkin exporter spec for details. - */ -final class EventDataToAnnotation { - - private EventDataToAnnotation() {} - - static String apply(EventData eventData) { - String name = eventData.getName(); - String value = toJson(eventData.getAttributes()); - return "\"" + name + "\":" + value; - } - - private static String toJson(Attributes attributes) { - return attributes.asMap().entrySet().stream() - .map(entry -> "\"" + entry.getKey() + "\":" + toValue(entry.getValue())) - .collect(joining(",", "{", "}")); - } - - private static String toValue(Object o) { - if (o instanceof String) { - return "\"" + o + "\""; - } - if (o instanceof List) { - return ((List) o) - .stream().map(EventDataToAnnotation::toValue).collect(joining(",", "[", "]")); - } - if (o instanceof Value) { - return toJsonValue((Value) o); - } - return String.valueOf(o); - } - - // note: simple types (STRING, BOOLEAN, LONG, DOUBLE) won't actually come here - // but handling here for completeness - private static String toJsonValue(Value value) { - ValueType type = value.getType(); - switch (type) { - case STRING: - case BYTES: - // For JSON encoding, strings and bytes need to be quoted - return "\"" + value.asString() + "\""; - case EMPTY: - // For JSON encoding, empty values should be null - return "null"; - case ARRAY: - case KEY_VALUE_LIST: - case BOOLEAN: - case LONG: - case DOUBLE: - // Arrays, maps, and primitives are already valid JSON from asString() - return value.asString(); - } - throw new IllegalStateException("Unknown value type: " + type); - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/LocalInetAddressSupplier.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/LocalInetAddressSupplier.java deleted file mode 100644 index a5376eb88a9..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/LocalInetAddressSupplier.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.util.Enumeration; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.annotation.Nullable; - -class LocalInetAddressSupplier implements Supplier { - - private static final Logger logger = Logger.getLogger(LocalInetAddressSupplier.class.getName()); - private static final LocalInetAddressSupplier INSTANCE = - new LocalInetAddressSupplier(findLocalIp()); - @Nullable private final InetAddress inetAddress; - - private LocalInetAddressSupplier(@Nullable InetAddress inetAddress) { - this.inetAddress = inetAddress; - } - - @Nullable - @Override - public InetAddress get() { - return inetAddress; - } - - /** Logic borrowed from brave.internal.Platform.produceLocalEndpoint. */ - @Nullable - private static InetAddress findLocalIp() { - try { - Enumeration nics = NetworkInterface.getNetworkInterfaces(); - while (nics.hasMoreElements()) { - NetworkInterface nic = nics.nextElement(); - Enumeration addresses = nic.getInetAddresses(); - while (addresses.hasMoreElements()) { - InetAddress address = addresses.nextElement(); - if (address.isSiteLocalAddress()) { - return address; - } - } - } - } catch (Exception e) { - // don't crash the caller if there was a problem reading nics. - logger.log(Level.FINE, "error reading nics", e); - } - return null; - } - - static LocalInetAddressSupplier getInstance() { - return INSTANCE; - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java deleted file mode 100644 index 57d12ec5864..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformer.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import static io.opentelemetry.api.common.AttributeKey.longKey; -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static java.util.concurrent.TimeUnit.NANOSECONDS; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.AttributeType; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.Value; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.data.EventData; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import java.net.InetAddress; -import java.util.List; -import java.util.function.Supplier; -import javax.annotation.Nullable; -import zipkin2.Endpoint; -import zipkin2.Span; - -/** - * This class is responsible for transforming an OpenTelemetry SpanData instance into an instance of - * a Zipkin Span. It is based, in part, on code from - * https://github.com/census-instrumentation/opencensus-java/tree/c960b19889de5e4a7b25f90919d28b066590d4f0/exporters/trace/zipkin - */ -final class OtelToZipkinSpanTransformer { - - private static final AttributeKey SERVICE_NAME = AttributeKey.stringKey("service.name"); - private static final AttributeKey PEER_SERVICE = stringKey("peer.service"); - private static final AttributeKey SERVER_SOCKET_ADDRESS = - stringKey("server.socket.address"); - private static final AttributeKey SERVER_SOCKET_PORT = longKey("server.socket.port"); - - static final String KEY_INSTRUMENTATION_SCOPE_NAME = "otel.scope.name"; - static final String KEY_INSTRUMENTATION_SCOPE_VERSION = "otel.scope.version"; - static final String KEY_INSTRUMENTATION_LIBRARY_NAME = "otel.library.name"; - static final String KEY_INSTRUMENTATION_LIBRARY_VERSION = "otel.library.version"; - static final String OTEL_DROPPED_ATTRIBUTES_COUNT = "otel.dropped_attributes_count"; - static final String OTEL_DROPPED_EVENTS_COUNT = "otel.dropped_events_count"; - static final String OTEL_STATUS_CODE = "otel.status_code"; - static final AttributeKey STATUS_ERROR = stringKey("error"); - private final Supplier ipAddressSupplier; - - /** - * Creates an instance of an OtelToZipkinSpanTransformer with the given Supplier that can produce - * an InetAddress, which may be null. This value from this Supplier will be used when creating the - * local zipkin Endpoint for each Span. The default implementation uses - * LocalInetAddressSupplier.getInstance(). - * - * @param ipAddressSupplier - A Supplier of an InetAddress. - */ - static OtelToZipkinSpanTransformer create(Supplier ipAddressSupplier) { - return new OtelToZipkinSpanTransformer(ipAddressSupplier); - } - - /** - * Creates an instance of an OtelToZipkinSpanTransformer with the given Supplier that can produce - * an InetAddress. Supplier may return null. This value from this Supplier will be used when - * creating the local zipkin Endpoint for each Span. - * - * @param ipAddressSupplier - A Supplier of an InetAddress, which can be null - */ - private OtelToZipkinSpanTransformer(Supplier ipAddressSupplier) { - this.ipAddressSupplier = ipAddressSupplier; - } - - /** - * Creates an instance of a Zipkin Span from an OpenTelemetry SpanData instance. - * - * @param spanData an OpenTelemetry spanData instance - * @return a new Zipkin Span - */ - Span generateSpan(SpanData spanData) { - long startTimestamp = toEpochMicros(spanData.getStartEpochNanos()); - long endTimestamp = toEpochMicros(spanData.getEndEpochNanos()); - - Span.Builder spanBuilder = - Span.newBuilder() - .traceId(spanData.getTraceId()) - .id(spanData.getSpanId()) - .kind(toSpanKind(spanData)) - .name(spanData.getName()) - .timestamp(toEpochMicros(spanData.getStartEpochNanos())) - .duration(Math.max(1, endTimestamp - startTimestamp)) - .localEndpoint(getLocalEndpoint(spanData)) - .remoteEndpoint(getRemoteEndpoint(spanData)); - - if (spanData.getParentSpanContext().isValid()) { - spanBuilder.parentId(spanData.getParentSpanId()); - } - - Attributes spanAttributes = spanData.getAttributes(); - spanAttributes.forEach( - (key, value) -> spanBuilder.putTag(key.getKey(), valueToString(key, value))); - int droppedAttributes = spanData.getTotalAttributeCount() - spanAttributes.size(); - if (droppedAttributes > 0) { - spanBuilder.putTag(OTEL_DROPPED_ATTRIBUTES_COUNT, String.valueOf(droppedAttributes)); - } - - StatusData status = spanData.getStatus(); - - // include status code & error. - if (status.getStatusCode() != StatusCode.UNSET) { - spanBuilder.putTag(OTEL_STATUS_CODE, status.getStatusCode().toString()); - - // add the error tag, if it isn't already in the source span. - if (status.getStatusCode() == StatusCode.ERROR && spanAttributes.get(STATUS_ERROR) == null) { - spanBuilder.putTag(STATUS_ERROR.getKey(), nullToEmpty(status.getDescription())); - } - } - - InstrumentationScopeInfo instrumentationScopeInfo = spanData.getInstrumentationScopeInfo(); - - if (!instrumentationScopeInfo.getName().isEmpty()) { - spanBuilder.putTag(KEY_INSTRUMENTATION_SCOPE_NAME, instrumentationScopeInfo.getName()); - // Include instrumentation library name for backwards compatibility - spanBuilder.putTag(KEY_INSTRUMENTATION_LIBRARY_NAME, instrumentationScopeInfo.getName()); - } - if (instrumentationScopeInfo.getVersion() != null) { - spanBuilder.putTag(KEY_INSTRUMENTATION_SCOPE_VERSION, instrumentationScopeInfo.getVersion()); - // Include instrumentation library name for backwards compatibility - spanBuilder.putTag( - KEY_INSTRUMENTATION_LIBRARY_VERSION, instrumentationScopeInfo.getVersion()); - } - - for (EventData eventData : spanData.getEvents()) { - String annotation = EventDataToAnnotation.apply(eventData); - spanBuilder.addAnnotation(toEpochMicros(eventData.getEpochNanos()), annotation); - } - int droppedEvents = spanData.getTotalRecordedEvents() - spanData.getEvents().size(); - if (droppedEvents > 0) { - spanBuilder.putTag(OTEL_DROPPED_EVENTS_COUNT, String.valueOf(droppedEvents)); - } - - return spanBuilder.build(); - } - - private static String nullToEmpty(@Nullable String value) { - return value != null ? value : ""; - } - - private Endpoint getLocalEndpoint(SpanData spanData) { - Attributes resourceAttributes = spanData.getResource().getAttributes(); - - Endpoint.Builder endpoint = Endpoint.newBuilder(); - endpoint.ip(ipAddressSupplier.get()); - - // use the service.name from the Resource, if it's been set. - String serviceNameValue = resourceAttributes.get(SERVICE_NAME); - if (serviceNameValue == null) { - serviceNameValue = Resource.getDefault().getAttribute(SERVICE_NAME); - } - // In practice should never be null unless the default Resource spec is changed. - if (serviceNameValue != null) { - endpoint.serviceName(serviceNameValue); - } - return endpoint.build(); - } - - @Nullable - private static Endpoint getRemoteEndpoint(SpanData spanData) { - if (spanData.getKind() == SpanKind.CLIENT || spanData.getKind() == SpanKind.PRODUCER) { - // TODO: Implement fallback mechanism: - // https://opentelemetry.io/docs/reference/specification/trace/sdk_exporters/zipkin/#otlp---zipkin - Attributes attributes = spanData.getAttributes(); - String serviceName = attributes.get(PEER_SERVICE); - - if (serviceName != null) { - Endpoint.Builder endpoint = Endpoint.newBuilder(); - endpoint.serviceName(serviceName); - endpoint.ip(attributes.get(SERVER_SOCKET_ADDRESS)); - Long port = attributes.get(SERVER_SOCKET_PORT); - if (port != null) { - endpoint.port(port.intValue()); - } - - return endpoint.build(); - } - } - - return null; - } - - @Nullable - private static Span.Kind toSpanKind(SpanData spanData) { - switch (spanData.getKind()) { - case SERVER: - return Span.Kind.SERVER; - case CLIENT: - return Span.Kind.CLIENT; - case PRODUCER: - return Span.Kind.PRODUCER; - case CONSUMER: - return Span.Kind.CONSUMER; - case INTERNAL: - return null; - } - return null; - } - - private static long toEpochMicros(long epochNanos) { - return NANOSECONDS.toMicros(epochNanos); - } - - private static String valueToString(AttributeKey key, Object attributeValue) { - AttributeType type = key.getType(); - switch (type) { - case STRING: - case BOOLEAN: - case LONG: - case DOUBLE: - return String.valueOf(attributeValue); - case STRING_ARRAY: - case BOOLEAN_ARRAY: - case LONG_ARRAY: - case DOUBLE_ARRAY: - return commaSeparated((List) attributeValue); - case VALUE: - return ((Value) attributeValue).asString(); - } - throw new IllegalStateException("Unknown attribute type: " + type); - } - - private static String commaSeparated(List values) { - StringBuilder builder = new StringBuilder(); - for (Object value : values) { - if (builder.length() != 0) { - builder.append(','); - } - builder.append(value); - } - return builder.toString(); - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporter.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporter.java deleted file mode 100644 index d726a5d5655..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporter.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import io.opentelemetry.api.impl.InstrumentationUtil; -import io.opentelemetry.api.metrics.MeterProvider; -import io.opentelemetry.exporter.zipkin.internal.ComponentId; -import io.opentelemetry.exporter.zipkin.internal.ExporterInstrumentation; -import io.opentelemetry.exporter.zipkin.internal.StandardComponentId; -import io.opentelemetry.exporter.zipkin.internal.ThrottlingLogger; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.common.InternalTelemetryVersion; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.io.IOException; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.Logger; -import zipkin2.Span; -import zipkin2.reporter.BytesEncoder; -import zipkin2.reporter.BytesMessageSender; -import zipkin2.reporter.Encoding; - -/** - * This class was based on the OpenCensus - * zipkin exporter code. - * - * @deprecated Zipkin exporter is deprecated in the OpenTelemetry specification (see #4715). - * Expect this artifact to no longer be published in approximately 6 months (mid 2026). - */ -@Deprecated -public final class ZipkinSpanExporter implements SpanExporter { - - public static final Logger baseLogger = Logger.getLogger(ZipkinSpanExporter.class.getName()); - - public static final String DEFAULT_ENDPOINT = "http://localhost:9411/api/v2/spans"; - - private final ThrottlingLogger logger = new ThrottlingLogger(baseLogger); - private final AtomicBoolean isShutdown = new AtomicBoolean(); - private final ZipkinSpanExporterBuilder builder; - private final BytesEncoder encoder; - private final BytesMessageSender sender; - private final ExporterInstrumentation exporterMetrics; - - private final OtelToZipkinSpanTransformer transformer; - - ZipkinSpanExporter( - ZipkinSpanExporterBuilder builder, - BytesEncoder encoder, - BytesMessageSender sender, - Supplier meterProviderSupplier, - InternalTelemetryVersion internalTelemetryVersion, - String endpoint, - OtelToZipkinSpanTransformer transformer) { - this.builder = builder; - this.encoder = encoder; - this.sender = sender; - this.transformer = transformer; - - StandardComponentId.ExporterType exporterType; - if (sender.encoding() == Encoding.JSON) { - exporterType = StandardComponentId.ExporterType.ZIPKIN_HTTP_JSON_SPAN_EXPORTER; - } else { - exporterType = StandardComponentId.ExporterType.ZIPKIN_HTTP_SPAN_EXPORTER; - } - this.exporterMetrics = - new ExporterInstrumentation( - internalTelemetryVersion, - meterProviderSupplier, - ComponentId.generateLazy(exporterType), - URI.create(endpoint)); - } - - @Override - public CompletableResultCode export(Collection spanDataList) { - if (isShutdown.get()) { - return CompletableResultCode.ofFailure(); - } - - int numItems = spanDataList.size(); - ExporterInstrumentation.Recording metricRecording = - exporterMetrics.startRecordingExport(numItems); - - List encodedSpans = new ArrayList<>(numItems); - for (SpanData spanData : spanDataList) { - Span zipkinSpan = transformer.generateSpan(spanData); - encodedSpans.add(encoder.encode(zipkinSpan)); - } - - CompletableResultCode resultCode = new CompletableResultCode(); - InstrumentationUtil.suppressInstrumentation( - () -> { - try { - sender.send(encodedSpans); - metricRecording.finishSuccessful(); - resultCode.succeed(); - } catch (IOException | RuntimeException e) { - metricRecording.finishFailed(e); - logger.log(Level.WARNING, "Failed to export spans", e); - resultCode.fail(); - } - }); - return resultCode; - } - - @Override - public CompletableResultCode flush() { - // nothing required here - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode shutdown() { - if (!isShutdown.compareAndSet(false, true)) { - logger.log(Level.INFO, "Calling shutdown() multiple times."); - return CompletableResultCode.ofSuccess(); - } - try { - sender.close(); - } catch (IOException e) { - logger.log(Level.WARNING, "Exception while closing the Zipkin Sender instance", e); - } - return CompletableResultCode.ofSuccess(); - } - - @Override - public String toString() { - return "ZipkinSpanExporter{" + builder.toString(false) + "}"; - } - - /** - * Returns a new Builder for {@link ZipkinSpanExporter}. - * - * @return a new {@link ZipkinSpanExporter}. - */ - public static ZipkinSpanExporterBuilder builder() { - return new ZipkinSpanExporterBuilder(); - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java deleted file mode 100644 index 98dc68bbc66..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import static java.util.Objects.requireNonNull; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.metrics.MeterProvider; -import io.opentelemetry.sdk.common.InternalTelemetryVersion; -import java.net.InetAddress; -import java.time.Duration; -import java.util.StringJoiner; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; -import javax.annotation.Nullable; -import zipkin2.Span; -import zipkin2.reporter.BytesEncoder; -import zipkin2.reporter.BytesMessageSender; -import zipkin2.reporter.Sender; -import zipkin2.reporter.SpanBytesEncoder; -import zipkin2.reporter.okhttp3.OkHttpSender; - -/** - * Builder class for {@link ZipkinSpanExporter}. - * - * @deprecated Zipkin exporter is deprecated in OpenTelemetry spec (see the PR). - * Expect this artifact to no longer be published in approximately 6 months (mid 2026). - */ -@Deprecated -public final class ZipkinSpanExporterBuilder { - private BytesEncoder encoder = SpanBytesEncoder.JSON_V2; - private Supplier localIpAddressSupplier = LocalInetAddressSupplier.getInstance(); - @Nullable private BytesMessageSender sender; - private String endpoint = ZipkinSpanExporter.DEFAULT_ENDPOINT; - // compression is enabled by default, because this is the default of OkHttpSender, - // which is created when no custom sender is set (see OkHttpSender.Builder) - private boolean compressionEnabled = true; - private int readTimeoutMillis = (int) TimeUnit.SECONDS.toMillis(10); - private Supplier meterProviderSupplier = - () -> GlobalOpenTelemetry.getOrNoop().getMeterProvider(); - private InternalTelemetryVersion internalTelemetryVersion = InternalTelemetryVersion.LEGACY; - - /** - * Sets the Zipkin sender. Implements the client side of the span transport. An {@link - * OkHttpSender} is a good default. - * - *

The {@link BytesMessageSender#close()} method will be called when the exporter is shut down. - * - * @param sender the Zipkin sender implementation. - * @return this. - * @deprecated Use {@link #setSender(BytesMessageSender)} insteead. - */ - @Deprecated - public ZipkinSpanExporterBuilder setSender(Sender sender) { - return setSender((BytesMessageSender) sender); - } - - /** - * Sets the Zipkin sender. Implements the client side of the span transport. An {@link - * OkHttpSender} is a good default. - * - *

The {@link BytesMessageSender#close()} method will be called when the exporter is shut down. - * - * @param sender the Zipkin sender implementation. - * @return this. - * @since 1.35.0 - */ - public ZipkinSpanExporterBuilder setSender(BytesMessageSender sender) { - requireNonNull(sender, "sender"); - this.sender = sender; - return this; - } - - /** - * Sets the {@link zipkin2.codec.BytesEncoder}, which controls the format used by the {@link - * BytesMessageSender}. Defaults to the {@link zipkin2.codec.SpanBytesEncoder#JSON_V2}. - * - * @param encoder the {@code BytesEncoder} to use. - * @return this. - * @see zipkin2.codec.SpanBytesEncoder - * @deprecated Use {@link #setEncoder(BytesEncoder)} instead. - */ - @Deprecated - public ZipkinSpanExporterBuilder setEncoder(zipkin2.codec.BytesEncoder encoder) { - requireNonNull(encoder, "encoder"); - return setEncoder(new BytesEncoderAdapter(encoder)); - } - - /** - * Sets the {@link BytesEncoder}, which controls the format used by the {@link - * BytesMessageSender}. Defaults to the {@link SpanBytesEncoder#JSON_V2}. - * - * @param encoder the {@code BytesEncoder} to use. - * @return this. - * @see SpanBytesEncoder - * @since 1.35.0 - */ - public ZipkinSpanExporterBuilder setEncoder(BytesEncoder encoder) { - requireNonNull(encoder, "encoder"); - this.encoder = encoder; - return this; - } - - /** - * Sets the Supplier of InetAddress. This Supplier will be used by the {@link - * OtelToZipkinSpanTransformer} when creating the Zipkin local endpoint. The default - * implementation uses a Supplier that returns a single unchanging IP address that is captured at - * creation time. - * - * @param localIpAddressSupplier - A supplier that returns an InetAddress that may be null. - * @return this - * @since 1.18.0 - */ - public ZipkinSpanExporterBuilder setLocalIpAddressSupplier( - Supplier localIpAddressSupplier) { - requireNonNull(localIpAddressSupplier, "localIpAddressSupplier"); - this.localIpAddressSupplier = localIpAddressSupplier; - return this; - } - - /** - * Sets the zipkin endpoint. This will use the endpoint to assign an {@link OkHttpSender} instance - * to this builder. - * - * @param endpoint The Zipkin endpoint URL, ex. "http://zipkinhost:9411/api/v2/spans". - * @return this. - * @see OkHttpSender - */ - public ZipkinSpanExporterBuilder setEndpoint(String endpoint) { - requireNonNull(endpoint, "endpoint"); - this.endpoint = endpoint; - return this; - } - - /** - * Sets the method used to compress payloads. If unset, gzip compression is enabled. Currently - * supported compression methods include "gzip" and "none". - * - *

The compression method is ignored when a custom Zipkin sender is set via {@link - * #setSender(BytesMessageSender)}. - * - * @param compressionMethod The compression method, ex. "gzip". - * @return this. - * @see OkHttpSender - * @since 1.20.0 - */ - public ZipkinSpanExporterBuilder setCompression(String compressionMethod) { - requireNonNull(compressionMethod, "compressionMethod"); - if (!compressionMethod.equals("gzip") && !compressionMethod.equals("none")) { - throw new IllegalArgumentException( - "Unsupported compression method. Supported compression methods include: gzip, none."); - } - this.compressionEnabled = compressionMethod.equals("gzip"); - return this; - } - - /** - * Sets the maximum time to wait for the export of a batch of spans. If unset, defaults to 10s. - * - * @return this. - * @since 1.2.0 - */ - public ZipkinSpanExporterBuilder setReadTimeout(long timeout, TimeUnit unit) { - requireNonNull(unit, "unit"); - if (timeout < 0) { - throw new IllegalArgumentException("timeout must be non-negative"); - } - long timeoutMillis = timeout == 0 ? Long.MAX_VALUE : unit.toMillis(timeout); - this.readTimeoutMillis = (int) Math.min(timeoutMillis, Integer.MAX_VALUE); - return this; - } - - /** - * Sets the maximum time to wait for the export of a batch of spans. If unset, defaults to 10s. - * - * @return this. - * @since 1.2.0 - */ - public ZipkinSpanExporterBuilder setReadTimeout(Duration timeout) { - requireNonNull(timeout, "timeout"); - setReadTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS); - return this; - } - - /** - * Sets the {@link MeterProvider} to use to collect metrics related to export. If not set, uses - * {@link GlobalOpenTelemetry#getMeterProvider()}. - * - * @return this. - * @since 1.17.0 - */ - public ZipkinSpanExporterBuilder setMeterProvider(MeterProvider meterProvider) { - requireNonNull(meterProvider, "meterProvider"); - this.meterProviderSupplier = () -> meterProvider; - return this; - } - - /** - * Sets the {@link InternalTelemetryVersion} defining which self-monitoring metrics this exporter - * collects. - * - * @since 1.51.0 - */ - public ZipkinSpanExporterBuilder setInternalTelemetryVersion(InternalTelemetryVersion level) { - requireNonNull(level, "level"); - this.internalTelemetryVersion = level; - return this; - } - - String toString(boolean includePrefixAndSuffix) { - StringJoiner joiner = - includePrefixAndSuffix - ? new StringJoiner(", ", "ZipkinSpanExporterBuilder{", "}") - : new StringJoiner(", "); - joiner.add("endpoint=" + endpoint); - joiner.add("compressionEnabled=" + compressionEnabled); - joiner.add("readTimeoutMillis=" + readTimeoutMillis); - joiner.add("internalTelemetrySchemaVersion=" + internalTelemetryVersion); - // Note: omit sender because we can't log the configuration in any readable way - // Note: omit encoder because we can't log the configuration in any readable way - // Note: omit localIpAddressSupplier because we can't log the configuration in any readable way - // Note: omit meterProviderSupplier because we can't log the configuration in any readable way - return joiner.toString(); - } - - /** - * Builds a {@link ZipkinSpanExporter}. - * - * @return a {@code ZipkinSpanExporter}. - */ - public ZipkinSpanExporter build() { - BytesMessageSender sender = this.sender; - if (sender == null) { - sender = - OkHttpSender.newBuilder() - .endpoint(endpoint) - .compressionEnabled(compressionEnabled) - .readTimeout(readTimeoutMillis) - .build(); - } - OtelToZipkinSpanTransformer transformer = - OtelToZipkinSpanTransformer.create(localIpAddressSupplier); - return new ZipkinSpanExporter( - this, - encoder, - sender, - meterProviderSupplier, - internalTelemetryVersion, - endpoint, - transformer); - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ComponentId.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ComponentId.java deleted file mode 100644 index 15d735df0cf..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ComponentId.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nullable; - -/** - * The component id used for SDK health metrics. This corresponds to the otel.component.name and - * otel.component.id semconv attributes. - * - *

Copied from {@code io.opentelemetry.sdk.common.internal.ComponentId} to avoid shared internal - * code. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public abstract class ComponentId { - - private ComponentId() {} - - public abstract String getTypeName(); - - public abstract String getComponentName(); - - static class Lazy extends ComponentId { - - private static final Map nextIdCounters = new ConcurrentHashMap<>(); - - private final String componentType; - @Nullable private volatile String componentName = null; - - Lazy(String componentType) { - this.componentType = componentType; - } - - @Override - public String getTypeName() { - return componentType; - } - - @Override - public String getComponentName() { - if (componentName == null) { - synchronized (this) { - if (componentName == null) { - int id = - nextIdCounters - .computeIfAbsent(componentType, k -> new AtomicInteger(0)) - .getAndIncrement(); - componentName = componentType + "/" + id; - } - } - } - return componentName; - } - } - - public static StandardComponentId generateLazy(StandardComponentId.ExporterType exporterType) { - return new StandardComponentId(exporterType); - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterInstrumentation.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterInstrumentation.java deleted file mode 100644 index fc2f2fb8919..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterInstrumentation.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.metrics.MeterProvider; -import io.opentelemetry.sdk.common.InternalTelemetryVersion; -import java.net.URI; -import java.util.function.Supplier; - -/** - * Copied from {@code io.opentelemetry.exporter.internal.ExporterInstrumentation} to avoid shared - * internal code. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public class ExporterInstrumentation { - - private final ExporterMetrics implementation; - - public ExporterInstrumentation( - InternalTelemetryVersion schema, - Supplier meterProviderSupplier, - StandardComponentId componentId, - URI endpoint) { - - Signal signal = componentId.getStandardType().signal(); - switch (schema) { - case LEGACY: - implementation = - LegacyExporterMetrics.isSupportedType() - ? new LegacyExporterMetrics(meterProviderSupplier, componentId.getStandardType()) - : NoopExporterMetrics.INSTANCE; - break; - case LATEST: - implementation = - new SemConvExporterMetrics( - meterProviderSupplier, signal, componentId, extractServerAttributes(endpoint)); - break; - default: - throw new IllegalStateException("Unhandled case: " + schema); - } - } - - // visible for testing - static Attributes extractServerAttributes(URI httpEndpoint) { - AttributesBuilder builder = Attributes.builder(); - String host = httpEndpoint.getHost(); - if (host != null) { - builder.put(SemConvAttributes.SERVER_ADDRESS, host); - } - int port = httpEndpoint.getPort(); - if (port == -1) { - String scheme = httpEndpoint.getScheme(); - if ("https".equals(scheme)) { - port = 443; - } else if ("http".equals(scheme)) { - port = 80; - } - } - if (port != -1) { - builder.put(SemConvAttributes.SERVER_PORT, port); - } - return builder.build(); - } - - public Recording startRecordingExport(int itemCount) { - return new Recording(implementation.startRecordingExport(itemCount)); - } - - /** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ - public static class Recording { - - private final ExporterMetrics.Recording delegate; - - private Recording(ExporterMetrics.Recording delegate) { - this.delegate = delegate; - } - - /** Callback to notify that the export was successful. */ - public void finishSuccessful() { - delegate.finishSuccessful(buildRequestAttributes()); - } - - /** - * Callback to notify that the export has failed with the given {@link Throwable} as failure - * cause. - * - * @param failureCause the cause of the failure - */ - public void finishFailed(Throwable failureCause) { - finishFailed(failureCause.getClass().getName()); - } - - /** - * Callback to notify that the export has failed. - * - * @param errorType a failure reason suitable for the error.type attribute - */ - public void finishFailed(String errorType) { - delegate.finishFailed(errorType, buildRequestAttributes()); - } - - private static Attributes buildRequestAttributes() { - return Attributes.empty(); - } - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterMetrics.java deleted file mode 100644 index 7ef471fe126..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ExporterMetrics.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import io.opentelemetry.api.common.Attributes; -import javax.annotation.Nullable; - -/** - * Copied from {@code io.opentelemetry.exporter.internal.ExporterMetrics} to avoid shared internal - * code. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public interface ExporterMetrics { - - Recording startRecordingExport(int itemCount); - - /** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ - abstract class Recording { - - private boolean alreadyEnded = false; - - protected Recording() {} - - public final void finishSuccessful(Attributes requestAttributes) { - ensureEndedOnce(); - doFinish(null, requestAttributes); - } - - public final void finishFailed(String errorType, Attributes requestAttributes) { - ensureEndedOnce(); - if (errorType == null || errorType.isEmpty()) { - throw new IllegalArgumentException("The export failed but no failure reason was provided"); - } - doFinish(errorType, requestAttributes); - } - - private void ensureEndedOnce() { - if (alreadyEnded) { - throw new IllegalStateException("Recording already ended"); - } - alreadyEnded = true; - } - - /** - * Invoked when the export has finished, either successfully or failed. - * - * @param errorType null if the export was successful, otherwise a failure reason suitable for - * the error.type attribute - * @param requestAttributes additional attributes to add to request metrics - */ - protected abstract void doFinish(@Nullable String errorType, Attributes requestAttributes); - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/LegacyExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/LegacyExporterMetrics.java deleted file mode 100644 index 9eae9e1f843..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/LegacyExporterMetrics.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import static io.opentelemetry.api.common.AttributeKey.booleanKey; -import static io.opentelemetry.api.common.AttributeKey.stringKey; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.metrics.LongCounter; -import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.api.metrics.MeterProvider; -import java.util.function.Supplier; -import javax.annotation.Nullable; - -/** - * Implements health metrics for exporters which were defined prior to the standardization in - * semantic conventions. - * - *

Copied from {@code io.opentelemetry.exporter.internal.LegacyExporterMetrics} to avoid shared - * internal code. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public class LegacyExporterMetrics implements ExporterMetrics { - - private static final AttributeKey ATTRIBUTE_KEY_TYPE = stringKey("type"); - private static final AttributeKey ATTRIBUTE_KEY_SUCCESS = booleanKey("success"); - - private final Supplier meterProviderSupplier; - private final String exporterName; - private final String transportName; - private final Attributes seenAttrs; - private final Attributes successAttrs; - private final Attributes failedAttrs; - - /** Access via {@link #seen()}. */ - @Nullable private volatile LongCounter seen; - - /** Access via {@link #exported()} . */ - @Nullable private volatile LongCounter exported; - - LegacyExporterMetrics( - Supplier meterProviderSupplier, - StandardComponentId.ExporterType exporterType) { - this.meterProviderSupplier = meterProviderSupplier; - this.exporterName = "zipkin"; - this.transportName = getTransportName(exporterType); - this.seenAttrs = Attributes.builder().put(ATTRIBUTE_KEY_TYPE, "span").build(); - this.successAttrs = this.seenAttrs.toBuilder().put(ATTRIBUTE_KEY_SUCCESS, true).build(); - this.failedAttrs = this.seenAttrs.toBuilder().put(ATTRIBUTE_KEY_SUCCESS, false).build(); - } - - public static boolean isSupportedType() { - return true; - } - - private static String getTransportName(StandardComponentId.ExporterType exporterType) { - switch (exporterType) { - case ZIPKIN_HTTP_SPAN_EXPORTER: - return "http"; - case ZIPKIN_HTTP_JSON_SPAN_EXPORTER: - return "http-json"; - } - throw new IllegalArgumentException("Not a supported exporter type: " + exporterType); - } - - /** Record number of records seen. */ - private void addSeen(long value) { - seen().add(value, seenAttrs); - } - - /** Record number of records which successfully exported. */ - private void addSuccess(long value) { - exported().add(value, successAttrs); - } - - /** Record number of records which failed to export. */ - private void addFailed(long value) { - exported().add(value, failedAttrs); - } - - private LongCounter seen() { - LongCounter seen = this.seen; - if (seen == null || SemConvExporterMetrics.isNoop(seen)) { - seen = meter().counterBuilder(exporterName + ".exporter.seen").build(); - this.seen = seen; - } - return seen; - } - - private LongCounter exported() { - LongCounter exported = this.exported; - if (exported == null || SemConvExporterMetrics.isNoop(exported)) { - exported = meter().counterBuilder(exporterName + ".exporter.exported").build(); - this.exported = exported; - } - return exported; - } - - private Meter meter() { - MeterProvider meterProvider = meterProviderSupplier.get(); - if (meterProvider == null) { - meterProvider = MeterProvider.noop(); - } - return meterProvider.get("io.opentelemetry.exporters." + exporterName + "-" + transportName); - } - - @Override - public ExporterMetrics.Recording startRecordingExport(int itemCount) { - return new Recording(itemCount); - } - - private class Recording extends ExporterMetrics.Recording { - - private final int itemCount; - - private Recording(int itemCount) { - this.itemCount = itemCount; - addSeen(itemCount); - } - - @Override - protected void doFinish(@Nullable String errorType, Attributes requestAttributes) { - if (errorType != null) { - addFailed(itemCount); - } else { - addSuccess(itemCount); - } - } - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/NoopExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/NoopExporterMetrics.java deleted file mode 100644 index abea8c9c45a..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/NoopExporterMetrics.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import io.opentelemetry.api.common.Attributes; -import javax.annotation.Nullable; - -/** - * Copied from {@code io.opentelemetry.exporter.internal.NoopExporterMetrics} to avoid shared - * internal code. - */ -class NoopExporterMetrics implements ExporterMetrics { - - static final NoopExporterMetrics INSTANCE = new NoopExporterMetrics(); - - @Override - public Recording startRecordingExport(int itemCount) { - return new NoopRecording(); - } - - private static class NoopRecording extends Recording { - - @Override - protected void doFinish(@Nullable String errorType, Attributes requestAttributes) {} - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/RateLimiter.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/RateLimiter.java deleted file mode 100644 index 03230f9ede2..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/RateLimiter.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import io.opentelemetry.sdk.common.Clock; -import java.util.concurrent.atomic.AtomicLong; - -/** - * This class was taken from Jaeger java client. - * https://github.com/jaegertracing/jaeger-client-java/blob/master/jaeger-core/src/main/java/io/jaegertracing/internal/samplers/RateLimitingSampler.java - * - *

Variables have been renamed for clarity. - * - *

Copied from {@code io.opentelemetry.sdk.common.internal.RateLimiter} to avoid shared internal - * code. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public class RateLimiter { - private final Clock clock; - private final double creditsPerNanosecond; - private final long maxBalance; // max balance in nano ticks - private final AtomicLong currentBalance; // last op nano time less remaining balance - - /** - * Create a new RateLimiter with the provided parameters. - * - * @param creditsPerSecond How many credits to accrue per second. - * @param maxBalance The maximum balance that the limiter can hold, which corresponds to the rate - * that is being limited to. - * @param clock An implementation of the {@link Clock} interface. - */ - public RateLimiter(double creditsPerSecond, double maxBalance, Clock clock) { - this.clock = clock; - this.creditsPerNanosecond = creditsPerSecond / 1.0e9; - this.maxBalance = (long) (maxBalance / creditsPerNanosecond); - this.currentBalance = new AtomicLong(clock.nanoTime() - this.maxBalance); - } - - /** - * Check to see if the provided cost can be spent within the current limits. Will deduct the cost - * from the current balance if it can be spent. - */ - public boolean trySpend(double itemCost) { - long cost = (long) (itemCost / creditsPerNanosecond); - long currentNanos; - long currentBalanceNanos; - long availableBalanceAfterWithdrawal; - do { - currentBalanceNanos = this.currentBalance.get(); - currentNanos = clock.nanoTime(); - long currentAvailableBalance = currentNanos - currentBalanceNanos; - if (currentAvailableBalance > maxBalance) { - currentAvailableBalance = maxBalance; - } - availableBalanceAfterWithdrawal = currentAvailableBalance - cost; - if (availableBalanceAfterWithdrawal < 0) { - return false; - } - } while (!this.currentBalance.compareAndSet( - currentBalanceNanos, currentNanos - availableBalanceAfterWithdrawal)); - return true; - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvAttributes.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvAttributes.java deleted file mode 100644 index 14a39c7c0a7..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvAttributes.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import io.opentelemetry.api.common.AttributeKey; - -/** - * Provides access to semantic convention attributes used within the SDK implementation. This avoids - * having to pull in semantic conventions as a dependency, which would easily collide and conflict - * with user-provided dependencies. - * - *

Copied from {@code io.opentelemetry.sdk.common.internal.SemConvAttributes} to avoid shared - * internal code. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public class SemConvAttributes { - - private SemConvAttributes() {} - - public static final AttributeKey OTEL_COMPONENT_TYPE = - AttributeKey.stringKey("otel.component.type"); - public static final AttributeKey OTEL_COMPONENT_NAME = - AttributeKey.stringKey("otel.component.name"); - public static final AttributeKey ERROR_TYPE = AttributeKey.stringKey("error.type"); - - public static final AttributeKey SERVER_ADDRESS = - AttributeKey.stringKey("server.address"); - public static final AttributeKey SERVER_PORT = AttributeKey.longKey("server.port"); -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvExporterMetrics.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvExporterMetrics.java deleted file mode 100644 index 18254fd21ec..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/SemConvExporterMetrics.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.metrics.DoubleHistogram; -import io.opentelemetry.api.metrics.LongCounter; -import io.opentelemetry.api.metrics.LongUpDownCounter; -import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.api.metrics.MeterProvider; -import io.opentelemetry.sdk.common.Clock; -import java.util.Collections; -import java.util.function.Supplier; -import javax.annotation.Nullable; - -/** - * Copied from {@code io.opentelemetry.exporter.internal.SemConvExporterMetrics} to avoid shared - * internal code. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public class SemConvExporterMetrics implements ExporterMetrics { - - private static final Clock CLOCK = Clock.getDefault(); - - private final Supplier meterProviderSupplier; - private final Signal signal; - private final ComponentId componentId; - private final Attributes additionalAttributes; - - @Nullable private volatile LongUpDownCounter inflight = null; - @Nullable private volatile LongCounter exported = null; - @Nullable private volatile DoubleHistogram duration = null; - @Nullable private volatile Attributes allAttributes = null; - - public SemConvExporterMetrics( - Supplier meterProviderSupplier, - Signal signal, - ComponentId componentId, - Attributes additionalAttributes) { - this.meterProviderSupplier = meterProviderSupplier; - this.componentId = componentId; - this.signal = signal; - this.additionalAttributes = additionalAttributes; - } - - @Override - public ExporterMetrics.Recording startRecordingExport(int itemCount) { - return new Recording(itemCount); - } - - private Meter meter() { - MeterProvider meterProvider = meterProviderSupplier.get(); - if (meterProvider == null) { - meterProvider = MeterProvider.noop(); - } - return meterProvider.get("io.opentelemetry.exporters." + componentId.getTypeName()); - } - - private Attributes allAttributes() { - // attributes are initialized lazily to trigger lazy initialization of the componentId - Attributes allAttributes = this.allAttributes; - if (allAttributes == null) { - AttributesBuilder builder = Attributes.builder(); - builder.put(SemConvAttributes.OTEL_COMPONENT_TYPE, componentId.getTypeName()); - builder.put(SemConvAttributes.OTEL_COMPONENT_NAME, componentId.getComponentName()); - builder.putAll(additionalAttributes); - allAttributes = builder.build(); - this.allAttributes = allAttributes; - } - return allAttributes; - } - - private LongUpDownCounter inflight() { - LongUpDownCounter inflight = this.inflight; - if (inflight == null || isNoop(inflight)) { - String unit = signal.getMetricUnit(); - inflight = - meter() - .upDownCounterBuilder(signal.getExporterMetricNamespace() + ".inflight") - .setUnit("{" + unit + "}") - .setDescription( - "The number of " - + unit - + "s which were passed to the exporter, but that have not been exported yet (neither successful, nor failed)") - .build(); - this.inflight = inflight; - } - return inflight; - } - - private LongCounter exported() { - LongCounter exported = this.exported; - if (exported == null || isNoop(exported)) { - String unit = signal.getMetricUnit(); - exported = - meter() - .counterBuilder(signal.getExporterMetricNamespace() + ".exported") - .setUnit("{" + unit + "}") - .setDescription( - "The number of " - + unit - + "s for which the export has finished, either successful or failed") - .build(); - this.exported = exported; - } - return exported; - } - - private DoubleHistogram duration() { - DoubleHistogram duration = this.duration; - if (duration == null || isNoop(duration)) { - duration = - meter() - .histogramBuilder("otel.sdk.exporter.operation.duration") - .setUnit("s") - .setDescription("The duration of exporting a batch of telemetry records") - .setExplicitBucketBoundariesAdvice(Collections.emptyList()) - .build(); - this.duration = duration; - } - return duration; - } - - private void incrementInflight(long count) { - inflight().add(count, allAttributes()); - } - - private void decrementInflight(long count) { - inflight().add(-count, allAttributes()); - } - - private void incrementExported(long count, @Nullable String errorType) { - exported().add(count, getAttributesWithPotentialError(errorType, Attributes.empty())); - } - - static boolean isNoop(Object instrument) { - // This is a poor way to identify a Noop implementation, but the API doesn't provide a better - // way. Perhaps we could add a common "Noop" interface to allow for an instanceof check? - return instrument.getClass().getSimpleName().startsWith("Noop"); - } - - private Attributes getAttributesWithPotentialError( - @Nullable String errorType, Attributes additionalAttributes) { - Attributes attributes = allAttributes(); - boolean errorPresent = errorType != null && !errorType.isEmpty(); - if (errorPresent || !additionalAttributes.isEmpty()) { - AttributesBuilder builder = attributes.toBuilder(); - if (errorPresent) { - builder.put(SemConvAttributes.ERROR_TYPE, errorType); - } - attributes = builder.putAll(additionalAttributes).build(); - } - return attributes; - } - - private void recordDuration( - double seconds, @Nullable String errorType, Attributes requestAttributes) { - duration().record(seconds, getAttributesWithPotentialError(errorType, requestAttributes)); - } - - private class Recording extends ExporterMetrics.Recording { - - private final int itemCount; - - private final long startNanoTime; - - private Recording(int itemCount) { - this.itemCount = itemCount; - startNanoTime = CLOCK.nanoTime(); - incrementInflight(itemCount); - } - - @Override - protected void doFinish(@Nullable String errorType, Attributes requestAttributes) { - decrementInflight(itemCount); - incrementExported(itemCount, errorType); - long durationNanos = CLOCK.nanoTime() - startNanoTime; - recordDuration(durationNanos / 1_000_000_000.0, errorType, requestAttributes); - } - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/Signal.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/Signal.java deleted file mode 100644 index 30736dead3d..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/Signal.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -/** - * Copied from {@code io.opentelemetry.sdk.common.internal.Signal} to avoid shared internal code. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public enum Signal { - SPAN("otel.sdk.exporter.span", "span"); - - private final String exporterMetricNamespace; - private final String metricUnit; - - Signal(String exporterMetricNamespace, String metricUnit) { - this.exporterMetricNamespace = exporterMetricNamespace; - this.metricUnit = metricUnit; - } - - public String getExporterMetricNamespace() { - return exporterMetricNamespace; - } - - public String getMetricUnit() { - return metricUnit; - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/StandardComponentId.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/StandardComponentId.java deleted file mode 100644 index 608a361e9b7..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/StandardComponentId.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -/** - * A {@link ComponentId} where the component type is one of {@link ExporterType}. - * - *

Copied from {@code io.opentelemetry.sdk.common.internal.StandardComponentId} to avoid shared - * internal code. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public class StandardComponentId extends ComponentId.Lazy { - - /** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ - public enum ExporterType { - ZIPKIN_HTTP_SPAN_EXPORTER("zipkin_http_span_exporter", Signal.SPAN), - /** - * Has the same semconv attribute value as ZIPKIN_HTTP_SPAN_EXPORTER, but we still use a - * different enum value for now because they produce separate legacy metrics. - */ - ZIPKIN_HTTP_JSON_SPAN_EXPORTER("zipkin_http_span_exporter", Signal.SPAN); - - final String value; - private final Signal signal; - - ExporterType(String value, Signal signal) { - this.value = value; - this.signal = signal; - } - - public Signal signal() { - return signal; - } - } - - private final ExporterType standardType; - - StandardComponentId(ExporterType standardType) { - super(standardType.value); - this.standardType = standardType; - } - - public ExporterType getStandardType() { - return standardType; - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ThrottlingLogger.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ThrottlingLogger.java deleted file mode 100644 index b5d8c808bc5..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ThrottlingLogger.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import static java.util.concurrent.TimeUnit.MINUTES; - -import io.opentelemetry.sdk.common.Clock; -import java.util.Locale; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.annotation.Nullable; - -/** - * Will limit the number of log messages emitted, so as not to spam when problems are happening. - * - *

Copied from {@code io.opentelemetry.sdk.common.internal.ThrottlingLogger} to avoid shared - * internal code. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public class ThrottlingLogger { - private static final double DEFAULT_RATE_LIMIT = 5; - private static final double DEFAULT_THROTTLED_RATE_LIMIT = 1; - private static final TimeUnit DEFAULT_RATE_TIME_UNIT = MINUTES; - - private final Logger delegate; - private final AtomicBoolean throttled = new AtomicBoolean(false); - private final RateLimiter fastRateLimiter; - private final RateLimiter throttledRateLimiter; - - private final double rateLimit; - private final double throttledRateLimit; - private final TimeUnit rateTimeUnit; - - /** Create a new logger which will enforce a max number of messages per minute. */ - public ThrottlingLogger(Logger delegate) { - Clock clock = Clock.getDefault(); - this.delegate = delegate; - this.rateLimit = DEFAULT_RATE_LIMIT; - this.throttledRateLimit = DEFAULT_THROTTLED_RATE_LIMIT; - this.rateTimeUnit = DEFAULT_RATE_TIME_UNIT; - this.fastRateLimiter = - new RateLimiter(this.rateLimit / this.rateTimeUnit.toSeconds(1), this.rateLimit, clock); - this.throttledRateLimiter = - new RateLimiter( - this.throttledRateLimit / this.rateTimeUnit.toSeconds(1), - this.throttledRateLimit, - clock); - } - - /** Log a message at the given level. */ - public void log(Level level, String message) { - log(level, message, null); - } - - /** Log a message at the given level with a throwable. */ - public void log(Level level, String message, @Nullable Throwable throwable) { - if (!isLoggable(level)) { - return; - } - if (throttled.get()) { - if (throttledRateLimiter.trySpend(1.0)) { - doLog(level, message, throwable); - } - return; - } - - if (fastRateLimiter.trySpend(1.0)) { - doLog(level, message, throwable); - return; - } - - if (throttled.compareAndSet(false, true)) { - // spend the balance in the throttled one, so that it starts at zero. - throttledRateLimiter.trySpend(throttledRateLimit); - String timeUnitString = rateTimeUnit.toString().toLowerCase(Locale.ROOT); - String throttleMessage = - String.format( - Locale.ROOT, - "Too many log messages detected. Will only log %.0f time(s) per %s from now on.", - throttledRateLimit, - timeUnitString.substring(0, timeUnitString.length() - 1)); - delegate.log(level, throttleMessage); - doLog(level, message, throwable); - } - } - - private void doLog(Level level, String message, @Nullable Throwable throwable) { - if (throwable != null) { - delegate.log(level, message, throwable); - } else { - delegate.log(level, message); - } - } - - /** - * Returns whether the current wrapped logger is set to log at the given level. - * - * @return true if the logger set to log at the requested level. - */ - public boolean isLoggable(Level level) { - return delegate.isLoggable(level); - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java deleted file mode 100644 index fc5e0ab64a2..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; -import io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.time.Duration; - -/** - * {@link SpanExporter} SPI implementation for {@link - * io.opentelemetry.exporter.zipkin.ZipkinSpanExporter}. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -@SuppressWarnings("deprecation") -public class ZipkinSpanExporterProvider implements ConfigurableSpanExporterProvider { - @Override - public String getName() { - return "zipkin"; - } - - @Override - public SpanExporter createExporter(ConfigProperties config) { - ZipkinSpanExporterBuilder builder = ZipkinSpanExporter.builder(); - - String endpoint = config.getString("otel.exporter.zipkin.endpoint"); - if (endpoint != null) { - builder.setEndpoint(endpoint); - } - - Duration timeout = config.getDuration("otel.exporter.zipkin.timeout"); - if (timeout != null) { - builder.setReadTimeout(timeout); - } - - return builder.build(); - } -} diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/package-info.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/package-info.java deleted file mode 100644 index 8e03649dd64..00000000000 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -@ParametersAreNonnullByDefault -package io.opentelemetry.exporter.zipkin; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/exporters/zipkin/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider b/exporters/zipkin/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider deleted file mode 100644 index 8a45b6fab18..00000000000 --- a/exporters/zipkin/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider +++ /dev/null @@ -1 +0,0 @@ -io.opentelemetry.exporter.zipkin.internal.ZipkinSpanExporterProvider diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/BytesEncoderAdapterTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/BytesEncoderAdapterTest.java deleted file mode 100644 index 24b16eb3a06..00000000000 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/BytesEncoderAdapterTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.PARENT_SPAN_ID; -import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.SPAN_ID; -import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.TRACE_ID; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; -import zipkin2.Endpoint; -import zipkin2.Span; -import zipkin2.reporter.Encoding; -import zipkin2.reporter.SpanBytesEncoder; - -class BytesEncoderAdapterTest { - - /** Contains {@link Span#localEndpoint()} to ensure would be encoded differently. */ - private final Span testSpan = - Span.newBuilder() - .traceId(TRACE_ID) - .parentId(PARENT_SPAN_ID) - .id(SPAN_ID) - .localEndpoint(Endpoint.newBuilder().serviceName("test").build()) - .build(); - - @Test - void testJsonV2() { - BytesEncoderAdapter adapter = new BytesEncoderAdapter(zipkin2.codec.SpanBytesEncoder.JSON_V2); - assertThat(adapter.encoding()).isEqualTo(Encoding.JSON); - assertThat(adapter.encode(testSpan)).isEqualTo(SpanBytesEncoder.JSON_V2.encode(testSpan)); - assertThat(adapter.sizeInBytes(testSpan)) - .isEqualTo(SpanBytesEncoder.JSON_V2.sizeInBytes(testSpan)); - assertThat(adapter).hasToString(SpanBytesEncoder.JSON_V2.toString()); - } - - @Test - void testProtobuf() { - BytesEncoderAdapter adapter = new BytesEncoderAdapter(zipkin2.codec.SpanBytesEncoder.PROTO3); - assertThat(adapter.encoding()).isEqualTo(Encoding.PROTO3); - assertThat(adapter.encode(testSpan)).isEqualTo(SpanBytesEncoder.PROTO3.encode(testSpan)); - assertThat(adapter.sizeInBytes(testSpan)) - .isEqualTo(SpanBytesEncoder.PROTO3.sizeInBytes(testSpan)); - assertThat(adapter).hasToString(SpanBytesEncoder.PROTO3.toString()); - } - - @Test - @SuppressWarnings("deprecation") // we have to use the deprecated thrift encoding to test it - void testThrift() { - BytesEncoderAdapter adapter = new BytesEncoderAdapter(zipkin2.codec.SpanBytesEncoder.THRIFT); - assertThat(adapter.encoding()).isEqualTo(Encoding.THRIFT); - assertThat(adapter.encode(testSpan)).isEqualTo(SpanBytesEncoder.THRIFT.encode(testSpan)); - assertThat(adapter.sizeInBytes(testSpan)) - .isEqualTo(SpanBytesEncoder.THRIFT.sizeInBytes(testSpan)); - assertThat(adapter).hasToString(SpanBytesEncoder.THRIFT.toString()); - } - - @Test - void testJsonV1() { - BytesEncoderAdapter adapter = new BytesEncoderAdapter(zipkin2.codec.SpanBytesEncoder.JSON_V1); - assertThat(adapter.encoding()).isEqualTo(Encoding.JSON); - assertThat(adapter.encode(testSpan)).isEqualTo(SpanBytesEncoder.JSON_V1.encode(testSpan)); - assertThat(adapter.sizeInBytes(testSpan)) - .isEqualTo(SpanBytesEncoder.JSON_V1.sizeInBytes(testSpan)); - assertThat(adapter).hasToString(SpanBytesEncoder.JSON_V1.toString()); - } -} diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java deleted file mode 100644 index 429ccd06bf1..00000000000 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/EventDataToAnnotationTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import static io.opentelemetry.api.common.AttributeKey.valueKey; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.KeyValue; -import io.opentelemetry.api.common.Value; -import io.opentelemetry.sdk.trace.data.EventData; -import org.junit.jupiter.api.Test; - -class EventDataToAnnotationTest { - - @Test - void basicConversion() { - - Attributes attrs = - Attributes.builder() - .put("v01", "v1") - .put("v02", 12L) - .put("v03", 123.45) - .put("v04", false) - .put("v05", "foo", "bar", "baz") - .put("v06", 1, 2, 3) - .put("v07", 1.23, 3.45) - .put("v08", true, false, true) - .put(valueKey("v09"), Value.of(new byte[] {1, 2, 3})) - .put(valueKey("v10"), Value.of(KeyValue.of("nested", Value.of("value")))) - .put(valueKey("v11"), Value.of(Value.of("string"), Value.of(123L))) - .put(valueKey("v12"), Value.empty()) - .build(); - String expected = - "\"cat\":{\"v01\":\"v1\",\"v02\":12,\"v03\":123.45,\"v04\":false,\"v05\":[\"foo\",\"bar\",\"baz\"],\"v06\":[1,2,3],\"v07\":[1.23,3.45],\"v08\":[true,false,true],\"v09\":\"AQID\",\"v10\":{\"nested\":\"value\"},\"v11\":[\"string\",123],\"v12\":null}"; - EventData eventData = EventData.create(0, "cat", attrs); - - String result = EventDataToAnnotation.apply(eventData); - - assertThat(result).isEqualTo(expected); - } - - @Test - void empty() { - Attributes attrs = Attributes.empty(); - String expected = "\"dog\":{}"; - EventData eventData = EventData.create(0, "dog", attrs); - - String result = EventDataToAnnotation.apply(eventData); - - assertThat(result).isEqualTo(expected); - } -} diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformerTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformerTest.java deleted file mode 100644 index e81e3a3c0e1..00000000000 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/OtelToZipkinSpanTransformerTest.java +++ /dev/null @@ -1,515 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey; -import static io.opentelemetry.api.common.AttributeKey.booleanKey; -import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey; -import static io.opentelemetry.api.common.AttributeKey.doubleKey; -import static io.opentelemetry.api.common.AttributeKey.longArrayKey; -import static io.opentelemetry.api.common.AttributeKey.longKey; -import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static io.opentelemetry.api.common.AttributeKey.valueKey; -import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.spanBuilder; -import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.zipkinSpan; -import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.zipkinSpanBuilder; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.KeyValue; -import io.opentelemetry.api.common.Value; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import java.net.InetAddress; -import java.util.Arrays; -import java.util.Collections; -import javax.annotation.Nullable; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; -import zipkin2.Endpoint; -import zipkin2.Span; - -class OtelToZipkinSpanTransformerTest { - - private OtelToZipkinSpanTransformer transformer; - private InetAddress localIp; - - @BeforeEach - void setup() { - localIp = mock(InetAddress.class); - transformer = OtelToZipkinSpanTransformer.create(() -> localIp); - } - - @Test - void generateSpan_remoteParent() { - SpanData data = spanBuilder().build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpanBuilder(Span.Kind.SERVER, localIp) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build()); - } - - @Test - void generateSpan_subMicroDurations() { - SpanData data = - spanBuilder() - .setStartEpochNanos(1505855794_194009601L) - .setEndEpochNanos(1505855794_194009999L) - .build(); - - Span expected = - zipkinSpanBuilder(Span.Kind.SERVER, localIp) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .duration(1) - .build(); - assertThat(transformer.generateSpan(data)).isEqualTo(expected); - } - - @Test - void generateSpan_ServerKind() { - SpanData data = spanBuilder().setKind(SpanKind.SERVER).build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpanBuilder(Span.Kind.SERVER, localIp) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build()); - } - - @Test - void generateSpan_ClientKind() { - SpanData data = spanBuilder().setKind(SpanKind.CLIENT).build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpanBuilder(Span.Kind.CLIENT, localIp) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build()); - } - - @Test - void generateSpan_InternalKind() { - SpanData data = spanBuilder().setKind(SpanKind.INTERNAL).build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpanBuilder(null, localIp) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build()); - } - - @Test - void generateSpan_ConsumeKind() { - SpanData data = spanBuilder().setKind(SpanKind.CONSUMER).build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpanBuilder(Span.Kind.CONSUMER, localIp) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build()); - } - - @Test - void generateSpan_ProducerKind() { - SpanData data = spanBuilder().setKind(SpanKind.PRODUCER).build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpanBuilder(Span.Kind.PRODUCER, localIp) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build()); - } - - @Test - void generateSpan_ResourceServiceNameMapping() { - Resource resource = - Resource.create(Attributes.of(stringKey("service.name"), "super-zipkin-service")); - SpanData data = spanBuilder().setResource(resource).build(); - - Endpoint expectedLocalEndpoint = - Endpoint.newBuilder().serviceName("super-zipkin-service").ip(localIp).build(); - Span expectedZipkinSpan = - zipkinSpan(Span.Kind.SERVER, localIp).toBuilder() - .localEndpoint(expectedLocalEndpoint) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - assertThat(transformer.generateSpan(data)).isEqualTo(expectedZipkinSpan); - } - - @Test - void generateSpan_defaultResourceServiceName() { - SpanData data = spanBuilder().setResource(Resource.empty()).build(); - - Endpoint expectedLocalEndpoint = - Endpoint.newBuilder() - .serviceName(Resource.getDefault().getAttribute(stringKey("service.name"))) - .ip(localIp) - .build(); - Span expectedZipkinSpan = - zipkinSpan(Span.Kind.SERVER, localIp).toBuilder() - .localEndpoint(expectedLocalEndpoint) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - assertThat(transformer.generateSpan(data)).isEqualTo(expectedZipkinSpan); - } - - @ParameterizedTest - @EnumSource( - value = SpanKind.class, - names = {"CLIENT", "PRODUCER"}) - void generateSpan_RemoteEndpointMapping(SpanKind spanKind) { - Attributes attributes = - Attributes.builder() - .put(stringKey("peer.service"), "remote-test-service") - .put(stringKey("server.socket.address"), "8.8.8.8") - .put(longKey("server.socket.port"), 42L) - .build(); - - SpanData spanData = - spanBuilder() - .setKind(spanKind) - .setResource(Resource.empty()) - .setAttributes(attributes) - .build(); - - Endpoint expectedLocalEndpoint = - Endpoint.newBuilder() - .serviceName(Resource.getDefault().getAttribute(stringKey("service.name"))) - .ip(localIp) - .build(); - - Endpoint expectedRemoteEndpoint = - Endpoint.newBuilder().serviceName("remote-test-service").ip("8.8.8.8").port(42).build(); - - Span expectedSpan = - zipkinSpan(toZipkinSpanKind(spanKind), localIp).toBuilder() - .localEndpoint(expectedLocalEndpoint) - .remoteEndpoint(expectedRemoteEndpoint) - .putTag("peer.service", "remote-test-service") - .putTag("server.socket.address", "8.8.8.8") - .putTag("server.socket.port", "42") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - - assertThat(transformer.generateSpan(spanData)).isEqualTo(expectedSpan); - } - - @ParameterizedTest - @EnumSource( - value = SpanKind.class, - names = {"SERVER", "CONSUMER", "INTERNAL"}) - void generateSpan_RemoteEndpointMappingWhenKindIsNotClientOrProducer(SpanKind spanKind) { - Attributes attributes = - Attributes.builder() - .put("peer.service", "remote-test-service") - .put("server.socket.address", "8.8.8.8") - .put("server.socket.port", 42L) - .build(); - - SpanData spanData = - spanBuilder() - .setKind(spanKind) - .setResource(Resource.empty()) - .setAttributes(attributes) - .build(); - - Endpoint expectedLocalEndpoint = - Endpoint.newBuilder() - .serviceName(Resource.getDefault().getAttribute(stringKey("service.name"))) - .ip(localIp) - .build(); - - Span expectedSpan = - zipkinSpan(toZipkinSpanKind(spanKind), localIp).toBuilder() - .localEndpoint(expectedLocalEndpoint) - .remoteEndpoint(null) - .putTag("peer.service", "remote-test-service") - .putTag("server.socket.address", "8.8.8.8") - .putTag("server.socket.port", "42") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - - assertThat(transformer.generateSpan(spanData)).isEqualTo(expectedSpan); - } - - @ParameterizedTest - @EnumSource( - value = SpanKind.class, - names = {"CLIENT", "PRODUCER"}) - void generateSpan_RemoteEndpointMappingWhenServiceNameIsMissing(SpanKind spanKind) { - Attributes attributes = - Attributes.builder() - .put("server.socket.address", "8.8.8.8") - .put("server.socket.port", 42L) - .build(); - - SpanData spanData = - spanBuilder() - .setKind(spanKind) - .setResource(Resource.empty()) - .setAttributes(attributes) - .build(); - - Endpoint expectedLocalEndpoint = - Endpoint.newBuilder() - .serviceName(Resource.getDefault().getAttribute(stringKey("service.name"))) - .ip(localIp) - .build(); - - Span expectedSpan = - zipkinSpan(toZipkinSpanKind(spanKind), localIp).toBuilder() - .localEndpoint(expectedLocalEndpoint) - .remoteEndpoint(null) - .putTag("server.socket.address", "8.8.8.8") - .putTag("server.socket.port", "42") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - - assertThat(transformer.generateSpan(spanData)).isEqualTo(expectedSpan); - } - - @ParameterizedTest - @EnumSource( - value = SpanKind.class, - names = {"CLIENT", "PRODUCER"}) - void generateSpan_RemoteEndpointMappingWhenPortIsMissing(SpanKind spanKind) { - Attributes attributes = - Attributes.builder() - .put("peer.service", "remote-test-service") - .put("server.socket.address", "8.8.8.8") - .build(); - - SpanData spanData = - spanBuilder() - .setKind(spanKind) - .setResource(Resource.empty()) - .setAttributes(attributes) - .build(); - - Endpoint expectedLocalEndpoint = - Endpoint.newBuilder() - .serviceName(Resource.getDefault().getAttribute(stringKey("service.name"))) - .ip(localIp) - .build(); - - Endpoint expectedRemoteEndpoint = - Endpoint.newBuilder().serviceName("remote-test-service").ip("8.8.8.8").build(); - - Span expectedSpan = - zipkinSpan(toZipkinSpanKind(spanKind), localIp).toBuilder() - .localEndpoint(expectedLocalEndpoint) - .remoteEndpoint(expectedRemoteEndpoint) - .putTag("peer.service", "remote-test-service") - .putTag("server.socket.address", "8.8.8.8") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - - assertThat(transformer.generateSpan(spanData)).isEqualTo(expectedSpan); - } - - @ParameterizedTest - @EnumSource( - value = SpanKind.class, - names = {"CLIENT", "PRODUCER"}) - void generateSpan_RemoteEndpointMappingWhenIpAndPortAreMissing(SpanKind spanKind) { - Attributes attributes = Attributes.builder().put("peer.service", "remote-test-service").build(); - - SpanData spanData = - spanBuilder() - .setKind(spanKind) - .setResource(Resource.empty()) - .setAttributes(attributes) - .build(); - - Endpoint expectedLocalEndpoint = - Endpoint.newBuilder() - .serviceName(Resource.getDefault().getAttribute(stringKey("service.name"))) - .ip(localIp) - .build(); - - Endpoint expectedRemoteEndpoint = - Endpoint.newBuilder().serviceName("remote-test-service").build(); - - Span expectedSpan = - zipkinSpan(toZipkinSpanKind(spanKind), localIp).toBuilder() - .localEndpoint(expectedLocalEndpoint) - .remoteEndpoint(expectedRemoteEndpoint) - .putTag("peer.service", "remote-test-service") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - - assertThat(transformer.generateSpan(spanData)).isEqualTo(expectedSpan); - } - - @Test - void generateSpan_WithAttributes() { - Attributes attributes = - Attributes.builder() - .put(stringKey("string"), "string value") - .put(booleanKey("boolean"), false) - .put(longKey("long"), 9999L) - .put(doubleKey("double"), 222.333d) - .put(booleanArrayKey("booleanArray"), Arrays.asList(true, false)) - .put(stringArrayKey("stringArray"), Collections.singletonList("Hello")) - .put(doubleArrayKey("doubleArray"), Arrays.asList(32.33d, -98.3d)) - .put(longArrayKey("longArray"), Arrays.asList(33L, 999L)) - .put(valueKey("bytes"), Value.of(new byte[] {1, 2, 3})) - .put(valueKey("map"), Value.of(KeyValue.of("nested", Value.of("value")))) - .put(valueKey("heterogeneousArray"), Value.of(Value.of("string"), Value.of(123L))) - .put(valueKey("empty"), Value.empty()) - .build(); - SpanData data = - spanBuilder() - .setAttributes(attributes) - .setTotalAttributeCount(32) - .setTotalRecordedEvents(3) - .setKind(SpanKind.CLIENT) - .build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpan(Span.Kind.CLIENT, localIp).toBuilder() - .putTag("string", "string value") - .putTag("boolean", "false") - .putTag("long", "9999") - .putTag("double", "222.333") - .putTag("booleanArray", "true,false") - .putTag("stringArray", "Hello") - .putTag("doubleArray", "32.33,-98.3") - .putTag("longArray", "33,999") - .putTag("bytes", "AQID") - .putTag("map", "{\"nested\":\"value\"}") - .putTag("heterogeneousArray", "[\"string\",123]") - .putTag("empty", "") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .putTag(OtelToZipkinSpanTransformer.OTEL_DROPPED_ATTRIBUTES_COUNT, "20") - .putTag(OtelToZipkinSpanTransformer.OTEL_DROPPED_EVENTS_COUNT, "1") - .build()); - } - - @Test - void generateSpan_WithInstrumentationLibraryInfo() { - SpanData data = - spanBuilder() - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder("io.opentelemetry.auto") - .setVersion("1.0.0") - .build()) - .setKind(SpanKind.CLIENT) - .build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpan(Span.Kind.CLIENT, localIp).toBuilder() - .putTag("otel.scope.name", "io.opentelemetry.auto") - .putTag("otel.scope.version", "1.0.0") - .putTag("otel.library.name", "io.opentelemetry.auto") - .putTag("otel.library.version", "1.0.0") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build()); - } - - @Test - void generateSpan_AlreadyHasHttpStatusInfo() { - Attributes attributes = - Attributes.of( - longKey("http.response.status.code"), - 404L, - stringKey("error"), - "A user provided error"); - SpanData data = - spanBuilder() - .setAttributes(attributes) - .setKind(SpanKind.CLIENT) - .setStatus(StatusData.error()) - .setTotalAttributeCount(2) - .build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpan(Span.Kind.CLIENT, localIp).toBuilder() - .clearTags() - .putTag("http.response.status.code", "404") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "ERROR") - .putTag("error", "A user provided error") - .build()); - } - - @Test - void generateSpan_WithRpcTimeoutErrorStatus_WithTimeoutErrorDescription() { - Attributes attributes = Attributes.of(stringKey("rpc.service"), "my service name"); - - String errorMessage = "timeout"; - - SpanData data = - spanBuilder() - .setStatus(StatusData.create(StatusCode.ERROR, errorMessage)) - .setAttributes(attributes) - .setTotalAttributeCount(1) - .build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpan(Span.Kind.SERVER, localIp).toBuilder() - .putTag("rpc.service", "my service name") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "ERROR") - .putTag(OtelToZipkinSpanTransformer.STATUS_ERROR.getKey(), errorMessage) - .build()); - } - - @Test - void generateSpan_WithRpcErrorStatus_WithEmptyErrorDescription() { - Attributes attributes = Attributes.of(stringKey("rpc.service"), "my service name"); - - SpanData data = - spanBuilder() - .setStatus(StatusData.create(StatusCode.ERROR, "")) - .setAttributes(attributes) - .setTotalAttributeCount(1) - .build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpan(Span.Kind.SERVER, localIp).toBuilder() - .putTag("rpc.service", "my service name") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "ERROR") - .putTag(OtelToZipkinSpanTransformer.STATUS_ERROR.getKey(), "") - .build()); - } - - @Test - void generateSpan_WithRpcUnsetStatus() { - Attributes attributes = Attributes.of(stringKey("rpc.service"), "my service name"); - - SpanData data = - spanBuilder() - .setStatus(StatusData.create(StatusCode.UNSET, "")) - .setAttributes(attributes) - .setTotalAttributeCount(1) - .build(); - - assertThat(transformer.generateSpan(data)) - .isEqualTo( - zipkinSpan(Span.Kind.SERVER, localIp).toBuilder() - .putTag("rpc.service", "my service name") - .build()); - } - - @Nullable - private static Span.Kind toZipkinSpanKind(SpanKind spanKind) { - return spanKind != SpanKind.INTERNAL ? Span.Kind.valueOf(spanKind.name()) : null; - } -} diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java deleted file mode 100644 index 38fb9ee078e..00000000000 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterEndToEndHttpTest.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.mockito.Mockito.mock; - -import com.linecorp.armeria.client.WebClient; -import com.linecorp.armeria.common.AggregatedHttpResponse; -import com.linecorp.armeria.common.HttpData; -import com.linecorp.armeria.common.HttpStatus; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.metrics.MeterProvider; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; -import io.opentelemetry.internal.testing.slf4j.SuppressLogger; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; -import io.opentelemetry.sdk.testing.trace.TestSpanData; -import io.opentelemetry.sdk.trace.IdGenerator; -import io.opentelemetry.sdk.trace.data.EventData; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import java.net.InetAddress; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import zipkin2.Endpoint; -import zipkin2.Span; -import zipkin2.codec.SpanBytesDecoder; -import zipkin2.reporter.BytesMessageSender; -import zipkin2.reporter.Encoding; -import zipkin2.reporter.SpanBytesEncoder; -import zipkin2.reporter.okhttp3.OkHttpSender; - -@SuppressWarnings("deprecation") // testing deprecated code -@Testcontainers(disabledWithoutDocker = true) -class ZipkinSpanExporterEndToEndHttpTest { - private static final WebClient client = WebClient.of(); - - private static final int ZIPKIN_API_PORT = 9411; - - private static final String SPAN_ID = "9cc1e3049173be09"; - private static final String PARENT_SPAN_ID = "8b03ab423da481c5"; - private static final String SPAN_NAME = "Recv.helloworld.Greeter.SayHello"; - private static final long START_EPOCH_NANOS = 1505855794_194009601L; - private static final long END_EPOCH_NANOS = 1505855799_465726528L; - private static final long RECEIVED_TIMESTAMP_NANOS = 1505855799_433901068L; - private static final long SENT_TIMESTAMP_NANOS = 1505855799_459486280L; - private static final Attributes attributes = Attributes.empty(); - private static final List annotations = - Collections.unmodifiableList( - Arrays.asList( - EventData.create(RECEIVED_TIMESTAMP_NANOS, "RECEIVED", Attributes.empty()), - EventData.create(SENT_TIMESTAMP_NANOS, "SENT", Attributes.empty()))); - - private static final String ENDPOINT_V1_SPANS = "/api/v1/spans"; - private static final String ENDPOINT_V2_SPANS = "/api/v2/spans"; - private static final String SERVICE_NAME = "myService"; - - private static final Attributes SEEN_ATTRIBUTES = - Attributes.of(AttributeKey.stringKey("type"), "span"); - private static final Attributes EXPORTED_SUCCESS_ATTRIBUTES = - SEEN_ATTRIBUTES.toBuilder().put(AttributeKey.booleanKey("success"), true).build(); - private static final Attributes EXPORTED_FAILED_ATTRIBUTES = - SEEN_ATTRIBUTES.toBuilder().put(AttributeKey.booleanKey("success"), false).build(); - - @Container - public static final GenericContainer zipkinContainer = - new GenericContainer<>("ghcr.io/openzipkin/zipkin:2.27") - .withExposedPorts(ZIPKIN_API_PORT) - .waitingFor(Wait.forHttp("/health").forPort(ZIPKIN_API_PORT)); - - private final InMemoryMetricReader sdkMeterReader = InMemoryMetricReader.create(); - private final SdkMeterProvider sdkMeterProvider = - SdkMeterProvider.builder().registerMetricReader(sdkMeterReader).build(); - - private static final InetAddress localIp = mock(InetAddress.class); - - @AfterEach - void tearDown() { - sdkMeterProvider.close(); - } - - @Test - void testExportWithDefaultEncoding() { - ZipkinSpanExporter exporter = - ZipkinSpanExporter.builder() - .setEndpoint(zipkinUrl(ENDPOINT_V2_SPANS)) - .setMeterProvider(sdkMeterProvider) - .setLocalIpAddressSupplier(() -> localIp) - .build(); - exportAndVerify(exporter); - - exporter.close(); - verifyMetrics(sdkMeterReader, "http-json", EXPORTED_SUCCESS_ATTRIBUTES); - } - - @Test - void testExportAsProtobuf() { - ZipkinSpanExporter exporter = - buildZipkinExporter( - zipkinUrl(ENDPOINT_V2_SPANS), - Encoding.PROTO3, - SpanBytesEncoder.PROTO3, - sdkMeterProvider); - exportAndVerify(exporter); - - exporter.close(); - verifyMetrics(sdkMeterReader, "http", EXPORTED_SUCCESS_ATTRIBUTES); - } - - @Test - void testExportAsThrift() { - @SuppressWarnings("deprecation") // we have to use the deprecated thrift encoding to test it - ZipkinSpanExporter exporter = - buildZipkinExporter( - zipkinUrl(ENDPOINT_V1_SPANS), - Encoding.THRIFT, - SpanBytesEncoder.THRIFT, - sdkMeterProvider); - exportAndVerify(exporter); - - exporter.close(); - verifyMetrics(sdkMeterReader, "http", EXPORTED_SUCCESS_ATTRIBUTES); - } - - @Test - void testExportAsJsonV1() { - ZipkinSpanExporter exporter = - buildZipkinExporter( - zipkinUrl(ENDPOINT_V1_SPANS), - Encoding.JSON, - SpanBytesEncoder.JSON_V1, - sdkMeterProvider); - exportAndVerify(exporter); - - exporter.close(); - verifyMetrics(sdkMeterReader, "http-json", EXPORTED_SUCCESS_ATTRIBUTES); - } - - @Test - @SuppressLogger(ZipkinSpanExporter.class) - void testExportFailedAsWrongEncoderUsed() { - ZipkinSpanExporter exporter = - buildZipkinExporter( - zipkinUrl(ENDPOINT_V2_SPANS), Encoding.JSON, SpanBytesEncoder.PROTO3, sdkMeterProvider); - - String traceId = IdGenerator.random().generateTraceId(); - SpanData spanData = buildStandardSpan(traceId).build(); - CompletableResultCode resultCode = exporter.export(Collections.singleton(spanData)); - - assertThat(resultCode.isSuccess()).isFalse(); - List zipkinSpans = getTrace(traceId); - assertThat(zipkinSpans).isEmpty(); - - exporter.close(); - verifyMetrics(sdkMeterReader, "http-json", EXPORTED_FAILED_ATTRIBUTES); - } - - private static ZipkinSpanExporter buildZipkinExporter( - String endpoint, Encoding encoding, SpanBytesEncoder encoder, MeterProvider meterProvider) { - BytesMessageSender sender = - OkHttpSender.newBuilder().endpoint(endpoint).encoding(encoding).build(); - return ZipkinSpanExporter.builder() - .setSender(sender) - .setEncoder(encoder) - .setMeterProvider(meterProvider) - .setLocalIpAddressSupplier(() -> localIp) - .build(); - } - - /** - * Exports a span, verify that it was received by Zipkin, and check that the span stored by Zipkin - * matches what was sent. - */ - private static void exportAndVerify(ZipkinSpanExporter zipkinSpanExporter) { - String traceId = IdGenerator.random().generateTraceId(); - SpanData spanData = buildStandardSpan(traceId).build(); - CompletableResultCode resultCode = zipkinSpanExporter.export(Collections.singleton(spanData)); - resultCode.join(10, TimeUnit.SECONDS); - - assertThat(resultCode.isSuccess()).isTrue(); - List zipkinSpans = getTrace(traceId); - - assertThat(zipkinSpans).isNotNull(); - assertThat(zipkinSpans.size()).isEqualTo(1); - assertThat(zipkinSpans.get(0)).isEqualTo(buildZipkinSpan(localIp, traceId)); - } - - private static TestSpanData.Builder buildStandardSpan(String traceId) { - return TestSpanData.builder() - .setSpanContext( - SpanContext.create(traceId, SPAN_ID, TraceFlags.getSampled(), TraceState.getDefault())) - .setParentSpanContext( - SpanContext.create( - traceId, PARENT_SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault())) - .setStatus(StatusData.ok()) - .setKind(SpanKind.SERVER) - .setName(SPAN_NAME) - .setStartEpochNanos(START_EPOCH_NANOS) - .setAttributes(attributes) - .setTotalAttributeCount(attributes.size()) - .setTotalRecordedEvents(annotations.size()) - .setEvents(annotations) - .setLinks(Collections.emptyList()) - .setEndEpochNanos(END_EPOCH_NANOS) - .setHasEnded(true) - .setResource(Resource.create(Attributes.of(stringKey("service.name"), SERVICE_NAME))); - } - - private static Span buildZipkinSpan(InetAddress localAddress, String traceId) { - return Span.newBuilder() - .traceId(traceId) - .parentId(PARENT_SPAN_ID) - .id(SPAN_ID) - .kind(Span.Kind.SERVER) - .name(SPAN_NAME) - .timestamp(START_EPOCH_NANOS / 1000) - .duration((END_EPOCH_NANOS / 1000) - (START_EPOCH_NANOS / 1000)) - .localEndpoint(Endpoint.newBuilder().serviceName(SERVICE_NAME).ip(localAddress).build()) - .addAnnotation(RECEIVED_TIMESTAMP_NANOS / 1000, "\"RECEIVED\":{}") - .addAnnotation(SENT_TIMESTAMP_NANOS / 1000, "\"SENT\":{}") - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - } - - private static List getTrace(String traceId) { - AggregatedHttpResponse response = - client.get(zipkinUrl("/api/v2/trace/" + traceId)).aggregate().join(); - if (response.status().equals(HttpStatus.NOT_FOUND)) { - return Collections.emptyList(); - } - try (HttpData content = response.content()) { - return SpanBytesDecoder.JSON_V2.decodeList(content.array()); - } - } - - private static String zipkinUrl(String endpoint) { - return "http://localhost:" + zipkinContainer.getMappedPort(ZIPKIN_API_PORT) + endpoint; - } - - private static void verifyMetrics( - InMemoryMetricReader sdkMeterReader, String transportName, Attributes exportedAttributes) { - assertThat(sdkMeterReader.collectAllMetrics()) - .allSatisfy( - metric -> - assertThat(metric) - .hasInstrumentationScope( - InstrumentationScopeInfo.create( - "io.opentelemetry.exporters.zipkin-" + transportName))) - .satisfiesExactlyInAnyOrder( - metric -> - assertThat(metric) - .hasName("zipkin.exporter.seen") - .hasLongSumSatisfying( - sum -> - sum.isMonotonic() - .isCumulative() - .hasPointsSatisfying( - point -> point.hasAttributes(SEEN_ATTRIBUTES).hasValue(1))), - metric -> - assertThat(metric) - .hasName("zipkin.exporter.exported") - .hasLongSumSatisfying( - sum -> - sum.isMonotonic() - .isCumulative() - .hasPointsSatisfying( - point -> point.hasAttributes(exportedAttributes).hasValue(1)))); - } -} diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java deleted file mode 100644 index 2227c8ff76a..00000000000 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterTest.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.spanBuilder; -import static io.opentelemetry.exporter.zipkin.ZipkinTestUtil.zipkinSpanBuilder; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import io.github.netmikey.logunit.api.LogCapturer; -import io.opentelemetry.api.impl.InstrumentationUtil; -import io.opentelemetry.api.metrics.MeterProvider; -import io.opentelemetry.context.Context; -import io.opentelemetry.internal.testing.slf4j.SuppressLogger; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.common.InternalTelemetryVersion; -import io.opentelemetry.sdk.testing.trace.TestSpanData; -import java.io.IOException; -import java.net.InetAddress; -import java.time.Duration; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import zipkin2.Span; -import zipkin2.reporter.BytesEncoder; -import zipkin2.reporter.BytesMessageSender; -import zipkin2.reporter.Encoding; -import zipkin2.reporter.SpanBytesEncoder; - -@ExtendWith(MockitoExtension.class) -@SuppressWarnings("deprecation") // testing deprecated code -class ZipkinSpanExporterTest { - - @Mock private BytesMessageSender mockSender; - @Mock private SpanBytesEncoder mockEncoder; - @Mock private OtelToZipkinSpanTransformer mockTransformer; - @Mock private InetAddress localIp; - - @RegisterExtension - LogCapturer logs = LogCapturer.create().captureForType(ZipkinSpanExporter.class); - - @Test - void testExport() throws IOException { - TestSpanData testSpanData = spanBuilder().build(); - - ZipkinSpanExporter zipkinSpanExporter = - new ZipkinSpanExporter( - new ZipkinSpanExporterBuilder(), - mockEncoder, - mockSender, - MeterProvider::noop, - InternalTelemetryVersion.LATEST, - "http://testing:1234", - mockTransformer); - - byte[] someBytes = new byte[0]; - Span zipkinSpan = - zipkinSpanBuilder(Span.Kind.SERVER, localIp) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - when(mockTransformer.generateSpan(testSpanData)).thenReturn(zipkinSpan); - when(mockEncoder.encode(zipkinSpan)).thenReturn(someBytes); - - CompletableResultCode resultCode = - zipkinSpanExporter.export(Collections.singleton(testSpanData)); - - assertThat(resultCode.isSuccess()).isTrue(); - - verify(mockSender).send(Collections.singletonList(someBytes)); - } - - @Test - @SuppressLogger(ZipkinSpanExporter.class) - void testExport_failed() throws IOException { - TestSpanData testSpanData = spanBuilder().build(); - - ZipkinSpanExporter zipkinSpanExporter = - new ZipkinSpanExporter( - new ZipkinSpanExporterBuilder(), - mockEncoder, - mockSender, - MeterProvider::noop, - InternalTelemetryVersion.LATEST, - "http://testing:1234", - mockTransformer); - - byte[] someBytes = new byte[0]; - Span zipkinSpan = - zipkinSpanBuilder(Span.Kind.SERVER, localIp) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - when(mockTransformer.generateSpan(testSpanData)).thenReturn(zipkinSpan); - when(mockEncoder.encode(zipkinSpan)).thenReturn(someBytes); - doThrow(new IOException()).when(mockSender).send(Collections.singletonList(someBytes)); - - CompletableResultCode resultCode = - zipkinSpanExporter.export(Collections.singleton(testSpanData)); - - assertThat(resultCode.isSuccess()).isFalse(); - - verify(mockSender).send(Collections.singletonList(someBytes)); - } - - @Test - void testCreate() { - ZipkinSpanExporter exporter = ZipkinSpanExporter.builder().setSender(mockSender).build(); - - assertThat(exporter).isNotNull(); - } - - @Test - @SuppressLogger(ZipkinSpanExporter.class) - void testShutdown() throws IOException { - ZipkinSpanExporter exporter = ZipkinSpanExporter.builder().setSender(mockSender).build(); - - assertThat(exporter.shutdown().isSuccess()).isTrue(); - verify(mockSender).close(); - assertThat(logs.getEvents()).isEmpty(); - assertThat( - exporter - .export(Collections.singletonList(spanBuilder().build())) - .join(10, TimeUnit.SECONDS) - .isSuccess()) - .isFalse(); - assertThat(exporter.shutdown().isSuccess()).isTrue(); - logs.assertContains("Calling shutdown() multiple times."); - } - - @Test - @SuppressWarnings({"PreferJavaTimeOverload", "deprecation"}) - // we have to use the deprecated setEncoder overload to test it - void invalidConfig() { - assertThatThrownBy(() -> ZipkinSpanExporter.builder().setReadTimeout(-1, TimeUnit.MILLISECONDS)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("timeout must be non-negative"); - - assertThatThrownBy(() -> ZipkinSpanExporter.builder().setReadTimeout(1, null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("unit"); - - assertThatThrownBy(() -> ZipkinSpanExporter.builder().setReadTimeout(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("timeout"); - - assertThatThrownBy(() -> ZipkinSpanExporter.builder().setEndpoint(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("endpoint"); - - assertThatThrownBy(() -> ZipkinSpanExporter.builder().setCompression(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("compressionMethod"); - - assertThatThrownBy(() -> ZipkinSpanExporter.builder().setSender(null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("sender"); - - assertThatThrownBy( - () -> ZipkinSpanExporter.builder().setEncoder((zipkin2.codec.BytesEncoder) null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("encoder"); - - assertThatThrownBy(() -> ZipkinSpanExporter.builder().setEncoder((BytesEncoder) null)) - .isInstanceOf(NullPointerException.class) - .hasMessage("encoder"); - } - - @Test - void encoderProtobuf() { - @SuppressWarnings("deprecation") // we have to use the deprecated setEncoderto test it - ZipkinSpanExporter exporter = - ZipkinSpanExporter.builder().setEncoder(zipkin2.codec.SpanBytesEncoder.PROTO3).build(); - try { - assertThat(exporter).extracting("encoder.encoding").isEqualTo(Encoding.PROTO3); - } finally { - exporter.shutdown(); - } - - exporter = ZipkinSpanExporter.builder().setEncoder(SpanBytesEncoder.PROTO3).build(); - try { - assertThat(exporter).extracting("encoder").isEqualTo(SpanBytesEncoder.PROTO3); - } finally { - exporter.shutdown(); - } - } - - @Test - void compressionDefault() { - ZipkinSpanExporter exporter = ZipkinSpanExporter.builder().build(); - try { - assertThat(exporter).extracting("sender.delegate.compressionEnabled").isEqualTo(true); - } finally { - exporter.shutdown(); - } - } - - @Test - void compressionNone() { - ZipkinSpanExporter exporter = ZipkinSpanExporter.builder().setCompression("none").build(); - try { - assertThat(exporter).extracting("sender.delegate.compressionEnabled").isEqualTo(false); - } finally { - exporter.shutdown(); - } - } - - @Test - void compressionGzip() { - ZipkinSpanExporter exporter = ZipkinSpanExporter.builder().setCompression("gzip").build(); - try { - assertThat(exporter).extracting("sender.delegate.compressionEnabled").isEqualTo(true); - } finally { - exporter.shutdown(); - } - } - - @Test - void compressionEnabledAndDisabled() { - ZipkinSpanExporter exporter = - ZipkinSpanExporter.builder().setCompression("gzip").setCompression("none").build(); - try { - assertThat(exporter).extracting("sender.delegate.compressionEnabled").isEqualTo(false); - } finally { - exporter.shutdown(); - } - } - - @Test - @SuppressWarnings("PreferJavaTimeOverload") - void readTimeout_Zero() { - ZipkinSpanExporter exporter = - ZipkinSpanExporter.builder().setReadTimeout(0, TimeUnit.SECONDS).build(); - - try { - assertThat(exporter) - .extracting("sender.delegate.client.readTimeoutMillis") - .isEqualTo(Integer.MAX_VALUE); - } finally { - exporter.shutdown(); - } - } - - @Test - void stringRepresentation() { - try (ZipkinSpanExporter exporter = ZipkinSpanExporter.builder().build()) { - assertThat(exporter.toString()) - .isEqualTo( - "ZipkinSpanExporter{endpoint=http://localhost:9411/api/v2/spans, compressionEnabled=true, readTimeoutMillis=10000, internalTelemetrySchemaVersion=LEGACY}"); - } - try (ZipkinSpanExporter exporter = - ZipkinSpanExporter.builder() - .setEndpoint("http://zipkin:9411/api/v2/spans") - .setReadTimeout(Duration.ofSeconds(15)) - .setCompression("none") - .build()) { - assertThat(exporter.toString()) - .isEqualTo( - "ZipkinSpanExporter{endpoint=http://zipkin:9411/api/v2/spans, compressionEnabled=false, readTimeoutMillis=15000, internalTelemetrySchemaVersion=LEGACY}"); - } - } - - @Test - void suppressInstrumentation() { - TestSpanData testSpanData = spanBuilder().build(); - - SuppressCatchingSender suppressCatchingSender = new SuppressCatchingSender(Encoding.JSON); - ZipkinSpanExporter zipkinSpanExporter = - new ZipkinSpanExporter( - new ZipkinSpanExporterBuilder(), - mockEncoder, - suppressCatchingSender, - MeterProvider::noop, - InternalTelemetryVersion.LATEST, - "http://testing:1234", - mockTransformer); - - byte[] someBytes = new byte[0]; - Span zipkinSpan = - zipkinSpanBuilder(Span.Kind.SERVER, localIp) - .putTag(OtelToZipkinSpanTransformer.OTEL_STATUS_CODE, "OK") - .build(); - when(mockTransformer.generateSpan(testSpanData)).thenReturn(zipkinSpan); - when(mockEncoder.encode(zipkinSpan)).thenReturn(someBytes); - - zipkinSpanExporter.export(Collections.singleton(testSpanData)); - - // Instrumentation should be suppressed on send, to avoid incidental spans related to span - // export. - assertTrue(suppressCatchingSender.sent.get()); - assertTrue(suppressCatchingSender.suppressed.get()); - } - - static class SuppressCatchingSender extends BytesMessageSender.Base { - - final AtomicBoolean sent = new AtomicBoolean(); - final AtomicBoolean suppressed = new AtomicBoolean(); - - protected SuppressCatchingSender(Encoding encoding) { - super(encoding); - } - - @Override - public int messageMaxBytes() { - return 1024; - } - - @Override - public void send(List list) throws IOException { - sent.set(true); - suppressed.set(InstrumentationUtil.shouldSuppressInstrumentation(Context.current())); - } - - @Override - public void close() throws IOException {} - } -} diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinTestUtil.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinTestUtil.java deleted file mode 100644 index e964be3c85e..00000000000 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/ZipkinTestUtil.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin; - -import static io.opentelemetry.api.common.AttributeKey.stringKey; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.testing.trace.TestSpanData; -import io.opentelemetry.sdk.trace.data.EventData; -import io.opentelemetry.sdk.trace.data.StatusData; -import java.net.InetAddress; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import javax.annotation.Nullable; -import zipkin2.Endpoint; -import zipkin2.Span; - -class ZipkinTestUtil { - - static final String TRACE_ID = "d239036e7d5cec116b562147388b35bf"; - static final String SPAN_ID = "9cc1e3049173be09"; - static final String PARENT_SPAN_ID = "8b03ab423da481c5"; - - private static final Attributes attributes = Attributes.empty(); - private static final List annotations = - Collections.unmodifiableList( - Arrays.asList( - EventData.create(1505855799_433901068L, "RECEIVED", Attributes.empty()), - EventData.create(1505855799_459486280L, "SENT", Attributes.empty()))); - - private ZipkinTestUtil() {} - - static TestSpanData.Builder spanBuilder() { - return TestSpanData.builder() - .setSpanContext( - SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getSampled(), TraceState.getDefault())) - .setParentSpanContext( - SpanContext.create( - TRACE_ID, PARENT_SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault())) - .setResource( - Resource.create( - Attributes.builder().put(stringKey("service.name"), "tweetiebird").build())) - .setStatus(StatusData.ok()) - .setKind(SpanKind.SERVER) - .setName("Recv.helloworld.Greeter.SayHello") - .setStartEpochNanos(1505855794_194009601L) - .setEndEpochNanos(1505855799_465726528L) - .setAttributes(attributes) - .setTotalAttributeCount(attributes.size()) - .setTotalRecordedEvents(annotations.size()) - .setEvents(annotations) - .setLinks(Collections.emptyList()) - .setHasEnded(true); - } - - static Span zipkinSpan(@Nullable Span.Kind kind, InetAddress localIp) { - return zipkinSpanBuilder(kind, localIp).build(); - } - - static Span.Builder zipkinSpanBuilder(@Nullable Span.Kind kind, InetAddress localIp) { - return Span.newBuilder() - .traceId(TRACE_ID) - .parentId(PARENT_SPAN_ID) - .id(SPAN_ID) - .kind(kind) - .name("Recv.helloworld.Greeter.SayHello") - .timestamp(1505855794000000L + 194009601L / 1000) - .duration((1505855799000000L + 465726528L / 1000) - (1505855794000000L + 194009601L / 1000)) - .localEndpoint(Endpoint.newBuilder().ip(localIp).serviceName("tweetiebird").build()) - .addAnnotation(1505855799000000L + 433901068L / 1000, "\"RECEIVED\":{}") - .addAnnotation(1505855799000000L + 459486280L / 1000, "\"SENT\":{}"); - } -} diff --git a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProviderTest.java b/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProviderTest.java deleted file mode 100644 index e21b2a7d6f1..00000000000 --- a/exporters/zipkin/src/test/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProviderTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.zipkin.internal; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("deprecation") // testing deprecated code -class ZipkinSpanExporterProviderTest { - - private static final ZipkinSpanExporterProvider provider = new ZipkinSpanExporterProvider(); - - @Test - void getName() { - assertThat(provider.getName()).isEqualTo("zipkin"); - } - - @Test - void createExporter_Default() { - try (SpanExporter spanExporter = - provider.createExporter(DefaultConfigProperties.createFromMap(Collections.emptyMap()))) { - assertThat(spanExporter).isInstanceOf(ZipkinSpanExporter.class); - assertThat(spanExporter) - .extracting("sender") - .extracting("delegate") - .extracting("client") - .extracting("readTimeoutMillis") - .isEqualTo(10_000); - assertThat(spanExporter) - .extracting("sender") - .extracting("endpoint") - .isEqualTo("http://localhost:9411/api/v2/spans"); - } - } - - @Test - void createExporter_WithConfiguration() { - Map config = new HashMap<>(); - config.put("otel.exporter.zipkin.endpoint", "http://localhost:8080/spans"); - config.put("otel.exporter.zipkin.timeout", "1s"); - - try (SpanExporter spanExporter = - provider.createExporter(DefaultConfigProperties.createFromMap(config))) { - assertThat(spanExporter).isInstanceOf(ZipkinSpanExporter.class); - assertThat(spanExporter) - .extracting("sender") - .extracting("delegate") - .extracting("client") - .extracting("readTimeoutMillis") - .isEqualTo(1000); - assertThat(spanExporter) - .extracting("sender") - .extracting("endpoint") - .isEqualTo("http://localhost:8080/spans"); - } - } -} diff --git a/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java b/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java index 4acebd2c399..c984ec7f2c6 100644 --- a/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java +++ b/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/JaegerPropagator.java @@ -291,7 +291,11 @@ private static Baggage getBaggageFromHeader(@Nullable C carrier, TextMapGett } } } - return builder == null ? null : builder.build(); + if (builder == null) { + return null; + } + Baggage baggage = builder.build(); + return baggage.isEmpty() ? null : baggage; } /** Returns a two-element array of {@code [entriesAdded, bytesAdded]}. */ diff --git a/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/OtTracePropagator.java b/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/OtTracePropagator.java index 10f3c8d8e8e..a8ebb8c7a6e 100644 --- a/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/OtTracePropagator.java +++ b/extensions/trace-propagators/src/main/java/io/opentelemetry/extension/trace/propagation/OtTracePropagator.java @@ -146,7 +146,8 @@ public Context extract(Context context, @Nullable C carrier, TextMapGetter carrier = new LinkedHashMap<>(); + carrier.put(BAGGAGE_HEADER, headerValue); + + Context context = Context.root().with(existingBaggage); + assertThat(fromContext(jaegerPropagator.extract(context, carrier, getter))) + .isEqualTo(existingBaggage); + } + + static Stream extract_baggageOnly_withHeader_invalid_keepsExistingBaggage() { + return Stream.of( + Arguments.argumentSet("no separator", "nometa+novalue"), + Arguments.argumentSet("empty value", "user="), + Arguments.argumentSet("empty header", ""), + Arguments.argumentSet("too many separators", "a=b=c")); + } + @Test void extract_baggageOnly_withHeader_andPrefix() { Map carrier = new LinkedHashMap<>(); diff --git a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracePropagatorTest.java b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracePropagatorTest.java index c2b7f67200e..6a7fa9789f5 100644 --- a/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracePropagatorTest.java +++ b/extensions/trace-propagators/src/test/java/io/opentelemetry/extension/trace/propagation/OtTracePropagatorTest.java @@ -390,6 +390,19 @@ void extract_Baggage_CapitalizedHeaders() { assertThat(Baggage.fromContext(context)).isEqualTo(expectedBaggage); } + @Test + void extract_Baggage_EmptyKey() { + Map carrier = new LinkedHashMap<>(); + carrier.put(OtTracePropagator.TRACE_ID_HEADER, TRACE_ID); + carrier.put(OtTracePropagator.SPAN_ID_HEADER, SPAN_ID); + carrier.put(OtTracePropagator.SAMPLED_HEADER, Common.TRUE_INT); + carrier.put(OtTracePropagator.PREFIX_BAGGAGE_HEADER, "value"); // Not really a valid key. + + Context context = propagator.extract(Context.current(), carrier, getter); + + assertThat(Baggage.fromContext(context)).isEqualTo(Baggage.empty()); + } + @Test void extract_Baggage_InvalidContext() { Map carrier = new LinkedHashMap<>(); diff --git a/opencensus-shim/README.md b/opencensus-shim/README.md index 69c9f3319cd..9e8b58a2a7d 100644 --- a/opencensus-shim/README.md +++ b/opencensus-shim/README.md @@ -1,5 +1,11 @@ # OpenTelemetry OpenCensus Shim +> [!WARNING] +> OpenCensus compatibility is deprecated in the OpenTelemetry specification, and this shim is +> deprecated accordingly. It remains available for legacy compatibility, but users should migrate +> OpenCensus instrumentation to the OpenTelemetry APIs. See the +> [OpenTelemetry specification guidance on OpenCensus compatibility](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/compatibility/opencensus.md). + The OpenCensus shim allows applications and libraries that are instrumented with OpenTelemetry, but depend on other libraries instrumented with OpenCensus, to export trace spans from both OpenTelemetry and OpenCensus with the correct @@ -38,8 +44,3 @@ SdkMeterProvider.builder() .registerMetricReader(OpenCensusMetrics.attachTo(PeriodicMetricReader.create(metricExporter))) .build(); ``` - -## Known Problems - -* OpenCensus links added after an OpenCensus span is created will not be -exported, as OpenTelemetry only supports links added when a span is created. diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducer.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducer.java index 121c61360f1..ddb7b30b068 100644 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducer.java +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducer.java @@ -21,7 +21,12 @@ * both OpenTelemetry and OpenCensus metrics. * *

To use, register with {@link SdkMeterProviderBuilder#registerMetricProducer(MetricProducer)}. + * + * @deprecated OpenCensus compatibility is deprecated in the OpenTelemetry specification (see #5138). + * Expect this artifact to no longer be published after June 2027. */ +@Deprecated public final class OpenCensusMetricProducer implements MetricProducer { private final MetricProducerManager openCensusMetricStorage; @@ -32,7 +37,12 @@ private OpenCensusMetricProducer(MetricProducerManager openCensusMetricStorage) /** * Constructs a new {@link OpenCensusMetricProducer} that reports against the given {@link * Resource}. + * + * @deprecated OpenCensus compatibility is deprecated in the OpenTelemetry specification (see #5138). + * Expect this artifact to no longer be published after June 2027. */ + @Deprecated public static MetricProducer create() { return new OpenCensusMetricProducer(Metrics.getExportComponent().getMetricProducerManager()); } diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryContextManager.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryContextManager.java index 24d9818645c..65c7a9174b4 100644 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryContextManager.java +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryContextManager.java @@ -16,12 +16,25 @@ * {@link io.opencensus.trace.unsafe.ContextManagerImpl}. It is loaded by OpenCensus via reflection * automatically in {@link io.opencensus.trace.unsafe.ContextHandleUtils} when the OpenCensus shim * library exists as a dependency. + * + * @deprecated OpenCensus compatibility is deprecated in the OpenTelemetry specification (see #5138). + * Expect this artifact to no longer be published after June 2027. */ +@Deprecated public final class OpenTelemetryContextManager implements ContextManager { private static final Logger LOGGER = Logger.getLogger(OpenTelemetryContextManager.class.getName()); + /** + * Creates the context manager loaded by OpenCensus through reflection. + * + * @deprecated OpenCensus compatibility is deprecated in the OpenTelemetry specification (see #5138). + * Expect this artifact to no longer be published after June 2027. + */ + @Deprecated @SuppressWarnings("unused") // Loaded via reflection public OpenTelemetryContextManager() {} diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java index 87c41b1ff07..5e4c76a386b 100644 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImpl.java @@ -39,6 +39,8 @@ import io.opencensus.trace.MessageEvent; import io.opencensus.trace.Span; import io.opencensus.trace.Status; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.Tracestate; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -46,13 +48,12 @@ import io.opentelemetry.api.trace.StatusCode; import java.util.EnumSet; import java.util.Map; -import java.util.logging.Logger; class OpenTelemetrySpanImpl extends Span implements io.opentelemetry.api.trace.Span, DelegatingSpan { - private static final Logger LOGGER = Logger.getLogger(OpenTelemetrySpanImpl.class.getName()); private static final EnumSet RECORD_EVENTS_SPAN_OPTIONS = EnumSet.of(Span.Options.RECORD_EVENTS); + private static final Tracestate OC_TRACESTATE_DEFAULT = Tracestate.builder().build(); private final io.opentelemetry.api.trace.Span otelSpan; @@ -104,7 +105,20 @@ public void addAnnotation(Annotation annotation) { @Override public void addLink(Link link) { - LOGGER.warning("OpenTelemetry does not support links added after a span is created."); + Preconditions.checkNotNull(link, "link"); + AttributesBuilder attributesBuilder = Attributes.builder(); + mapAttributes(link.getAttributes(), attributesBuilder); + // DelegatingSpan does not override addLink, so DelegatingSpan.super.addLink(..) would call the + // no-op default method of Span and drop the link. Call the delegate directly instead. + getDelegate() + .addLink( + mapSpanContext( + io.opencensus.trace.SpanContext.create( + link.getTraceId(), + link.getSpanId(), + TraceOptions.DEFAULT, + OC_TRACESTATE_DEFAULT)), + attributesBuilder.build()); } @Override diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryTraceComponentImpl.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryTraceComponentImpl.java index 503220353b4..4a306aa5627 100644 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryTraceComponentImpl.java +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/OpenTelemetryTraceComponentImpl.java @@ -21,7 +21,12 @@ * Implementation of the {@link TraceComponent} for OpenTelemetry migration, which uses the * OpenTelemetry migration StartEndHandler. This class is loaded by reflection in {@link * io.opencensus.trace.Tracing} and overrides the OpenCensus default implementation when present. + * + * @deprecated OpenCensus compatibility is deprecated in the OpenTelemetry specification (see #5138). + * Expect this artifact to no longer be published after June 2027. */ +@Deprecated public final class OpenTelemetryTraceComponentImpl extends TraceComponent { private final PropagationComponent propagationComponent = new OpenTelemetryPropagationComponentImpl(); @@ -30,6 +35,14 @@ public final class OpenTelemetryTraceComponentImpl extends TraceComponent { private final TraceConfig traceConfig = makeTraceConfig(); private final Tracer tracer; + /** + * Creates the trace component loaded by OpenCensus through reflection. + * + * @deprecated OpenCensus compatibility is deprecated in the OpenTelemetry specification (see #5138). + * Expect this artifact to no longer be published after June 2027. + */ + @Deprecated @SuppressWarnings("unused") // Loaded via reflection public OpenTelemetryTraceComponentImpl() { clock = MillisClock.getInstance(); diff --git a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/package-info.java b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/package-info.java index 36b5be41e94..9428c9c7dab 100644 --- a/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/package-info.java +++ b/opencensus-shim/src/main/java/io/opentelemetry/opencensusshim/package-info.java @@ -3,7 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** The OpenCensus to OpenTelemetry shim. */ +/** + * Legacy OpenCensus compatibility built on top of OpenTelemetry. + * + *

OpenCensus compatibility is deprecated in the OpenTelemetry specification, and this shim is + * deprecated accordingly. Use this package only for legacy compatibility while migrating OpenCensus + * instrumentation to the OpenTelemetry APIs. + */ @ParametersAreNonnullByDefault package io.opentelemetry.opencensusshim; diff --git a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducerTest.java b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducerTest.java index 67996be5e06..c4fbca32ff3 100644 --- a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducerTest.java +++ b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenCensusMetricProducerTest.java @@ -30,6 +30,7 @@ import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; +@SuppressWarnings("deprecation") // testing deprecated shim API class OpenCensusMetricProducerTest { private final MetricProducer openCensusMetrics = OpenCensusMetricProducer.create(); diff --git a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenCensusMetricsTest.java b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenCensusMetricsTest.java index 4539045c435..3c0db5297b1 100644 --- a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenCensusMetricsTest.java +++ b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenCensusMetricsTest.java @@ -19,6 +19,7 @@ import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; +@SuppressWarnings("deprecation") // testing deprecated shim API class OpenCensusMetricsTest { private static final StatsRecorder STATS_RECORDER = Stats.getStatsRecorder(); diff --git a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImplTest.java b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImplTest.java index 448c032a3f8..e700d5b30f6 100644 --- a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImplTest.java +++ b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/OpenTelemetrySpanImplTest.java @@ -5,16 +5,27 @@ package io.opentelemetry.opencensusshim; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.opencensus.trace.AttributeValue; +import io.opencensus.trace.Link; +import io.opencensus.trace.SpanId; import io.opencensus.trace.Status; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.Tracestate; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import java.util.Collections; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -26,6 +37,9 @@ @MockitoSettings(strictness = Strictness.LENIENT) class OpenTelemetrySpanImplTest { + private static final String TRACE_ID = "0123456789abcdef0123456789abcdef"; + private static final String SPAN_ID = "fedcba9876543210"; + @Mock private Span otelSpan; private OpenTelemetrySpanImpl shimSpan() { @@ -47,4 +61,35 @@ void setStatus_withoutDescription_setsCodeOnly() { verify(otelSpan).setStatus(StatusCode.OK); verify(otelSpan, never()).setStatus(any(StatusCode.class), anyString()); } + + @Test + void addLink_delegatesToOtelSpan() { + shimSpan().addLink(Link.fromSpanContext(ocSpanContext(), Link.Type.CHILD_LINKED_SPAN)); + + verify(otelSpan).addLink(expectedOtelSpanContext(), Attributes.empty()); + } + + @Test + void addLink_mapsAttributes() { + shimSpan() + .addLink( + Link.fromSpanContext( + ocSpanContext(), + Link.Type.PARENT_LINKED_SPAN, + Collections.singletonMap("key", AttributeValue.stringAttributeValue("value")))); + + verify(otelSpan).addLink(expectedOtelSpanContext(), Attributes.of(stringKey("key"), "value")); + } + + private static io.opencensus.trace.SpanContext ocSpanContext() { + return io.opencensus.trace.SpanContext.create( + TraceId.fromLowerBase16(TRACE_ID), + SpanId.fromLowerBase16(SPAN_ID), + TraceOptions.DEFAULT, + Tracestate.builder().build()); + } + + private static SpanContext expectedOtelSpanContext() { + return SpanContext.create(TRACE_ID, SPAN_ID, TraceFlags.getDefault(), TraceState.getDefault()); + } } diff --git a/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/Propagation.java b/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/Propagation.java index 04f0407b4a8..e67bf4fa73d 100644 --- a/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/Propagation.java +++ b/opentracing-shim/src/main/java/io/opentelemetry/opentracingshim/Propagation.java @@ -46,7 +46,9 @@ SpanContextShim extractTextMap(Format format, TextMapExtract carrier) { Span span = Span.fromContext(context); Baggage baggage = Baggage.fromContext(context); - if (!span.getSpanContext().isValid() && baggage.isEmpty()) { + if (!span.getSpanContext().isValid() + && !span.getSpanContext().isSampled() + && baggage.isEmpty()) { return null; } diff --git a/opentracing-shim/src/test/java/io/opentelemetry/opentracingshim/TracerShimTest.java b/opentracing-shim/src/test/java/io/opentelemetry/opentracingshim/TracerShimTest.java index f539e5e3bfa..199b6209e26 100644 --- a/opentracing-shim/src/test/java/io/opentelemetry/opentracingshim/TracerShimTest.java +++ b/opentracing-shim/src/test/java/io/opentelemetry/opentracingshim/TracerShimTest.java @@ -14,9 +14,15 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; +import io.opentelemetry.api.trace.SpanId; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceId; +import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; import io.opentelemetry.internal.testing.slf4j.SuppressLogger; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; @@ -32,9 +38,11 @@ import io.opentracing.tag.StringTag; import io.opentracing.tag.Tag; import io.opentracing.tag.Tags; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -350,6 +358,21 @@ void extract_onlyBaggage() { assertThat(spanContextShim.getBaggage()).isEqualTo(baggage); } + @Test + void extract_invalidButSampledSpanContext() { + tracerShim = + new TracerShim(provider, new InvalidButSampledPropagator(), TextMapPropagator.noop()); + + SpanContext spanContext = + tracerShim.extract(Format.Builtin.TEXT_MAP, new TextMapAdapter(new HashMap<>())); + + assertThat(spanContext).isNotNull(); + SpanContextShim spanContextShim = (SpanContextShim) spanContext; + assertThat(spanContextShim.getSpanContext().isValid()).isFalse(); + assertThat(spanContextShim.getSpanContext().isSampled()).isTrue(); + assertThat(spanContextShim.getBaggage().isEmpty()).isTrue(); + } + @Test void extract_emptyCarrier_withActiveSpan_returnsNull() { Span span = tracerShim.buildSpan("one").start(); @@ -467,4 +490,26 @@ void noopDoesNotCrash() { assertThat(((SpanShim) span).getSpan().isRecording()).isFalse(); } + + /** Extracts an invalid but sampled {@code SpanContext}, as {@code jaeger-debug-id} headers do. */ + private static class InvalidButSampledPropagator implements TextMapPropagator { + @Override + public Collection fields() { + return Collections.emptyList(); + } + + @Override + public void inject(Context context, @Nullable C carrier, TextMapSetter setter) {} + + @Override + public Context extract(Context context, @Nullable C carrier, TextMapGetter getter) { + return context.with( + io.opentelemetry.api.trace.Span.wrap( + io.opentelemetry.api.trace.SpanContext.create( + TraceId.getInvalid(), + SpanId.getInvalid(), + TraceFlags.getSampled(), + TraceState.getDefault()))); + } + } } diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DefaultConfigProperties.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DefaultConfigProperties.java index f8818c015bd..25f4821670e 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DefaultConfigProperties.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DefaultConfigProperties.java @@ -225,8 +225,7 @@ public Map getMap(String name) { entry -> { String[] split = entry.split("=", 2); if (split.length != 2 || StringUtils.isNullOrEmpty(split[0])) { - throw new ConfigurationException( - "Invalid map property: " + name + "=" + config.get(name)); + throw new ConfigurationException("Invalid map property: " + name); } return filterBlanksAndNulls(split); }) diff --git a/sdk-extensions/autoconfigure-spi/src/test/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ConfigPropertiesTest.java b/sdk-extensions/autoconfigure-spi/src/test/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ConfigPropertiesTest.java index 4a119e600cc..4981c8c4f07 100644 --- a/sdk-extensions/autoconfigure-spi/src/test/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ConfigPropertiesTest.java +++ b/sdk-extensions/autoconfigure-spi/src/test/java/io/opentelemetry/sdk/autoconfigure/spi/internal/ConfigPropertiesTest.java @@ -161,16 +161,17 @@ void uncleanMap() { void invalidMap() { assertThatThrownBy( () -> - DefaultConfigProperties.createFromMap(Collections.singletonMap("map", "a=1,b")) + DefaultConfigProperties.createFromMap( + Collections.singletonMap("map", "authorization=Bearer s3cr3t,malformed")) .getMap("map")) .isInstanceOf(ConfigurationException.class) - .hasMessage("Invalid map property: map=a=1,b"); + .hasMessage("Invalid map property: map"); assertThatThrownBy( () -> DefaultConfigProperties.createFromMap(Collections.singletonMap("map", "a=1,=b")) .getMap("map")) .isInstanceOf(ConfigurationException.class) - .hasMessage("Invalid map property: map=a=1,=b"); + .hasMessage("Invalid map property: map"); } @Test diff --git a/sdk-extensions/autoconfigure/build.gradle.kts b/sdk-extensions/autoconfigure/build.gradle.kts index 288fff4be41..987b0f903cf 100644 --- a/sdk-extensions/autoconfigure/build.gradle.kts +++ b/sdk-extensions/autoconfigure/build.gradle.kts @@ -76,7 +76,6 @@ testing { implementation("io.prometheus:prometheus-metrics-exporter-httpserver") { exclude(group = "io.prometheus", module = "prometheus-metrics-exposition-formats") } - implementation(project(":exporters:zipkin")) implementation(project(":sdk:testing")) implementation(project(":sdk:trace-shaded-deps")) implementation(project(":sdk-extensions:jaeger-remote-sampler")) diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java index 37dbe4262fd..fd5a985ca55 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java @@ -367,9 +367,9 @@ public AutoConfiguredOpenTelemetrySdkBuilder addLogRecordExporterCustomizer( * Adds a {@link BiFunction} to invoke for all autoconfigured {@link * io.opentelemetry.sdk.logs.LogRecordProcessor}s. The return value of the {@link BiFunction} will * replace the passed-in argument. In contrast to {@link - * #addLogRecordExporterCustomizer(BiFunction)} (BiFunction)} this allows modifications to happen - * before batching occurs. As a result, it is possible to efficiently filter logs, add artificial - * logs or delay logs for enhancing them with external, delayed data. + * #addLogRecordExporterCustomizer(BiFunction)} this allows modifications to happen before + * batching occurs. As a result, it is possible to efficiently filter logs, add artificial logs or + * delay logs for enhancing them with external, delayed data. * *

Multiple calls will execute the customizers in order. */ diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfiguration.java index 1cb0f84181c..f98fe404330 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfiguration.java @@ -32,7 +32,6 @@ final class SpanExporterConfiguration { EXPORTER_ARTIFACT_ID_BY_NAME.put("logging", "opentelemetry-exporter-logging"); EXPORTER_ARTIFACT_ID_BY_NAME.put("logging-otlp", "opentelemetry-exporter-logging-otlp"); EXPORTER_ARTIFACT_ID_BY_NAME.put("otlp", "opentelemetry-exporter-otlp"); - EXPORTER_ARTIFACT_ID_BY_NAME.put("zipkin", "opentelemetry-exporter-zipkin"); } // Visible for testing diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java index 3ac7ce6c269..79c848db2d7 100644 --- a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java +++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java @@ -45,11 +45,6 @@ void configureExporter_KnownSpiExportersNotOnClasspath() { .hasMessage( "otel.traces.exporter set to \"otlp\" but opentelemetry-exporter-otlp" + " not found on classpath. Make sure to add it as a dependency."); - assertThatThrownBy(() -> configureExporter("zipkin", spiExportersManager)) - .isInstanceOf(ConfigurationException.class) - .hasMessage( - "otel.traces.exporter set to \"zipkin\" but opentelemetry-exporter-zipkin" - + " not found on classpath. Make sure to add it as a dependency."); // Unrecognized exporter assertThatThrownBy(() -> configureExporter("foo", spiExportersManager)) diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableSpanExporterTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableSpanExporterTest.java index d09dd4ce0d5..aeea76a4d87 100644 --- a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableSpanExporterTest.java +++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/ConfigurableSpanExporterTest.java @@ -13,7 +13,6 @@ import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.exporter.otlp.internal.OtlpSpanExporterProvider; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; -import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.autoconfigure.internal.NamedSpiManager; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; @@ -164,14 +163,14 @@ void configureSpanProcessors_simpleSpanProcessor() { @Test void configureSpanProcessors_batchSpanProcessor() { - String exporterName = "zipkin"; + String exporterName = "noop"; List closeables = new ArrayList<>(); List spanProcessors = TracerProviderConfiguration.configureSpanProcessors( DefaultConfigProperties.createFromMap( Collections.singletonMap("otel.traces.exporter", exporterName)), - ImmutableMap.of(exporterName, ZipkinSpanExporter.builder().build()), + ImmutableMap.of(exporterName, SpanExporter.noop()), InternalTelemetryVersion.LEGACY, MeterProvider.noop(), closeables); @@ -188,12 +187,9 @@ void configureSpanProcessors_multipleExporters() { List spanProcessors = TracerProviderConfiguration.configureSpanProcessors( DefaultConfigProperties.createFromMap( - Collections.singletonMap("otel.traces.exporter", "otlp,zipkin")), + Collections.singletonMap("otel.traces.exporter", "otlp,noop")), ImmutableMap.of( - "otlp", - OtlpGrpcSpanExporter.builder().build(), - "zipkin", - ZipkinSpanExporter.builder().build()), + "otlp", OtlpGrpcSpanExporter.builder().build(), "noop", SpanExporter.noop()), InternalTelemetryVersion.LEGACY, MeterProvider.noop(), closeables); @@ -217,7 +213,7 @@ void configureSpanProcessors_multipleExporters() { spanExporters -> { assertThat(spanExporters.length).isEqualTo(2); assertThat(spanExporters) - .hasAtLeastOneElementOfType(ZipkinSpanExporter.class) + .hasAtLeastOneElementOfType(SpanExporter.noop().getClass()) .hasAtLeastOneElementOfType(OtlpGrpcSpanExporter.class); }); }); @@ -231,12 +227,8 @@ void configureSpanProcessors_multipleExportersWithLogging() { List spanProcessors = TracerProviderConfiguration.configureSpanProcessors( DefaultConfigProperties.createFromMap( - Collections.singletonMap("otel.traces.exporter", "logging,zipkin")), - ImmutableMap.of( - "logging", - LoggingSpanExporter.create(), - "zipkin", - ZipkinSpanExporter.builder().build()), + Collections.singletonMap("otel.traces.exporter", "logging,noop")), + ImmutableMap.of("logging", LoggingSpanExporter.create(), "noop", SpanExporter.noop()), InternalTelemetryVersion.LEGACY, MeterProvider.noop(), closeables); diff --git a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java index 95d014f615c..a31280c4177 100644 --- a/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java +++ b/sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfigurationTest.java @@ -12,7 +12,6 @@ import io.opentelemetry.exporter.logging.LoggingSpanExporter; import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; -import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; import io.opentelemetry.sdk.autoconfigure.internal.NamedSpiManager; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -42,8 +41,6 @@ void configureExporter_KnownSpiExportersOnClasspath() { .isInstanceOf(OtlpJsonLoggingSpanExporter.class); assertThat(SpanExporterConfiguration.configureExporter("otlp", spiExportersManager)) .isInstanceOf(OtlpGrpcSpanExporter.class); - assertThat(SpanExporterConfiguration.configureExporter("zipkin", spiExportersManager)) - .isInstanceOf(ZipkinSpanExporter.class); } @Test diff --git a/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSampler.java b/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSampler.java index 52a8b350e7c..96d196da719 100644 --- a/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSampler.java +++ b/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSampler.java @@ -18,6 +18,7 @@ import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingResult; import java.io.IOException; +import java.net.URI; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -45,14 +46,19 @@ public final class JaegerRemoteSampler implements Sampler { private final AtomicBoolean isShutdown = new AtomicBoolean(); private final GrpcSender grpcSender; + private final URI endpoint; + private final int pollingIntervalMs; JaegerRemoteSampler( GrpcSender grpcSender, + URI endpoint, @Nullable String serviceName, int pollingIntervalMs, Sampler initialSampler) { this.serviceName = serviceName != null ? serviceName : ""; this.grpcSender = grpcSender; + this.endpoint = endpoint; + this.pollingIntervalMs = pollingIntervalMs; this.sampler = initialSampler; pollExecutor = Executors.newScheduledThreadPool(1, new DaemonThreadFactory(WORKER_THREAD_NAME)); pollFuture = @@ -161,7 +167,13 @@ private static Sampler updateSampler(SamplingStrategyResponse response) throws I @Override public String getDescription() { - return String.format("JaegerRemoteSampler{%s}", this.sampler); + return "JaegerRemoteSampler{sampler=" + + this.sampler + + ", endpoint=" + + this.endpoint + + ", pollingIntervalMs=" + + this.pollingIntervalMs + + "}"; } @Override diff --git a/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerBuilder.java b/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerBuilder.java index 2228956c47f..c6b2783e1a2 100644 --- a/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerBuilder.java +++ b/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerBuilder.java @@ -181,7 +181,8 @@ public JaegerRemoteSamplerBuilder setChannel(ManagedChannel channel) { */ public JaegerRemoteSampler build() { GrpcSender grpcSender = resolveGrpcSender(); - return new JaegerRemoteSampler(grpcSender, serviceName, pollingIntervalMillis, initialSampler); + return new JaegerRemoteSampler( + grpcSender, endpoint, serviceName, pollingIntervalMillis, initialSampler); } private GrpcSender resolveGrpcSender() { diff --git a/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/internal/JaegerRemoteSamplerComponentProvider.java b/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/internal/JaegerRemoteSamplerComponentProvider.java index b6dd93b77c1..b983f6751ef 100644 --- a/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/internal/JaegerRemoteSamplerComponentProvider.java +++ b/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/internal/JaegerRemoteSamplerComponentProvider.java @@ -47,7 +47,7 @@ public Sampler create(DeclarativeConfigProperties config) { } builder.setInitialSampler(DeclarativeConfiguration.createSampler(initialSamplerModel)); - Long pollingIntervalMs = config.getLong("internal"); + Long pollingIntervalMs = config.getLong("interval"); if (pollingIntervalMs != null) { builder.setPollingInterval(Duration.ofMillis(pollingIntervalMs)); } diff --git a/sdk-extensions/jaeger-remote-sampler/src/test/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerTest.java b/sdk-extensions/jaeger-remote-sampler/src/test/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerTest.java index 88047b9f92d..4ed67399f87 100644 --- a/sdk-extensions/jaeger-remote-sampler/src/test/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerTest.java +++ b/sdk-extensions/jaeger-remote-sampler/src/test/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerTest.java @@ -284,7 +284,8 @@ void description() { assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class); assertThat(sampler.getDescription()) - .startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}"); + .startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}") + .contains("endpoint=" + server.httpUri(), "pollingIntervalMs=1000"); // wait until the sampling strategy is retrieved before exiting test method await().untilAsserted(samplerIsType(sampler, RateLimitingSampler.class)); @@ -301,7 +302,8 @@ void initialSampler() { .build()) { assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class); - assertThat(sampler.getDescription()).startsWith("JaegerRemoteSampler{AlwaysOnSampler}"); + assertThat(sampler.getDescription()) + .startsWith("JaegerRemoteSampler{sampler=AlwaysOnSampler"); } } @@ -400,7 +402,7 @@ void perOperationSampling() { () -> { assertThat(sampler.getDescription()) .startsWith( - "JaegerRemoteSampler{ParentBased{root:PerOperationSampler{default=TraceIdRatioBased{0.550000}, perOperation={foo=TraceIdRatioBased{0.900000}, bar=TraceIdRatioBased{0.700000}}}"); + "JaegerRemoteSampler{sampler=ParentBased{root:PerOperationSampler{default=TraceIdRatioBased{0.550000}, perOperation={foo=TraceIdRatioBased{0.900000}, bar=TraceIdRatioBased{0.700000}}}"); assertThat(sampler.getDescription()).contains("bar"); }); } @@ -419,7 +421,7 @@ void internal_error_server_response() { assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class); assertThat(sampler.getDescription()) - .startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}"); + .startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}"); await() .untilAsserted( @@ -444,7 +446,7 @@ void unavailable_error_server_response() { assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class); assertThat(sampler.getDescription()) - .startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}"); + .startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}"); await() .untilAsserted( @@ -468,7 +470,7 @@ void unimplemented_error_server_response() { assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class); assertThat(sampler.getDescription()) - .startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}"); + .startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}"); await() .untilAsserted( diff --git a/sdk-extensions/jaeger-remote-sampler/src/testGrpcNetty/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerGrpcNettyTest.java b/sdk-extensions/jaeger-remote-sampler/src/testGrpcNetty/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerGrpcNettyTest.java index c2ddc1987ad..7ef47608f9f 100644 --- a/sdk-extensions/jaeger-remote-sampler/src/testGrpcNetty/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerGrpcNettyTest.java +++ b/sdk-extensions/jaeger-remote-sampler/src/testGrpcNetty/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerGrpcNettyTest.java @@ -159,7 +159,7 @@ void description() { assertThat(sampler).extracting("grpcSender").isInstanceOf(UpstreamGrpcSender.class); assertThat(sampler.getDescription()) - .startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}"); + .startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}"); // wait until the sampling strategy is retrieved before exiting test method await().untilAsserted(samplerIsType(sampler, RateLimitingSampler.class)); @@ -178,7 +178,8 @@ void initialSampler() { .build()) { assertThat(sampler).extracting("grpcSender").isInstanceOf(UpstreamGrpcSender.class); - assertThat(sampler.getDescription()).startsWith("JaegerRemoteSampler{AlwaysOnSampler}"); + assertThat(sampler.getDescription()) + .startsWith("JaegerRemoteSampler{sampler=AlwaysOnSampler"); } } @@ -277,7 +278,7 @@ void perOperationSampling() { () -> { assertThat(sampler.getDescription()) .startsWith( - "JaegerRemoteSampler{ParentBased{root:PerOperationSampler{default=TraceIdRatioBased{0.550000}, perOperation={foo=TraceIdRatioBased{0.900000}, bar=TraceIdRatioBased{0.700000}}}"); + "JaegerRemoteSampler{sampler=ParentBased{root:PerOperationSampler{default=TraceIdRatioBased{0.550000}, perOperation={foo=TraceIdRatioBased{0.900000}, bar=TraceIdRatioBased{0.700000}}}"); assertThat(sampler.getDescription()).contains("bar"); }); } @@ -297,7 +298,7 @@ void internal_error_server_response() { assertThat(sampler).extracting("grpcSender").isInstanceOf(UpstreamGrpcSender.class); assertThat(sampler.getDescription()) - .startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}"); + .startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}"); await() .untilAsserted( @@ -323,7 +324,7 @@ void unavailable_error_server_response() { assertThat(sampler).extracting("grpcSender").isInstanceOf(UpstreamGrpcSender.class); assertThat(sampler.getDescription()) - .startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}"); + .startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}"); await() .untilAsserted( @@ -348,7 +349,7 @@ void unimplemented_error_server_response() { assertThat(sampler).extracting("grpcSender").isInstanceOf(UpstreamGrpcSender.class); assertThat(sampler.getDescription()) - .startsWith("JaegerRemoteSampler{ParentBased{root:TraceIdRatioBased{0.001000}"); + .startsWith("JaegerRemoteSampler{sampler=ParentBased{root:TraceIdRatioBased{0.001000}"); await() .untilAsserted( diff --git a/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdk.java b/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdk.java index 667f5abe14c..5600a676da3 100644 --- a/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdk.java +++ b/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdk.java @@ -202,7 +202,7 @@ public SdkMeterProvider unobfuscate() { * *

Static global providers are obfuscated when they are returned from the API to prevent users * from casting them to their SDK specific implementation. For example, we do not want users to - * use patterns like {@code (SdkMeterProvider) openTelemetry.getMeterProvider()}. + * use patterns like {@code (SdkLoggerProvider) openTelemetry.getLogsBridge()}. */ @ThreadSafe // Visible for testing diff --git a/sdk/common/src/jmh/java/io/opentelemetry/sdk/common/internal/AttributesMapBenchmark.java b/sdk/common/src/jmh/java/io/opentelemetry/sdk/common/internal/AttributesMapBenchmark.java new file mode 100644 index 00000000000..e0baa45e405 --- /dev/null +++ b/sdk/common/src/jmh/java/io/opentelemetry/sdk/common/internal/AttributesMapBenchmark.java @@ -0,0 +1,139 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.common.internal; + +import static io.opentelemetry.api.common.AttributeKey.booleanKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import io.opentelemetry.api.common.AttributeKey; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * Microbenchmark for {@link AttributesMap}. Parametrized by number of attributes. + * + *

Write scenarios: + * + *

+ * + *

Read scenarios (run on a pre-filled map of {@code numAttributes} unique string entries): + * + *

+ */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 200, timeUnit = TimeUnit.MILLISECONDS) +@Fork(2) +@State(Scope.Thread) +public class AttributesMapBenchmark { + + // Default SpanLimits attribute count limit. + private static final int CAPACITY = 128; + + @Param({"4", "16", "20", "32", "128"}) + int numAttributes; + + private List> stringKeys; + private List> boolKeys; + private List values; + + // Pre-filled map used by read benchmarks — populated once in @Setup. + private AttributesMap filledMap; + + @Setup + public void setup() { + stringKeys = new ArrayList<>(numAttributes); + boolKeys = new ArrayList<>(numAttributes); + values = new ArrayList<>(numAttributes); + for (int i = 0; i < numAttributes; i++) { + stringKeys.add(stringKey("key" + i)); + boolKeys.add(booleanKey("key" + i)); + values.add("value" + i); + } + filledMap = AttributesMap.create(CAPACITY, Integer.MAX_VALUE); + for (int i = 0; i < numAttributes; i++) { + filledMap.put(stringKeys.get(i), values.get(i)); + } + } + + /** Each key name is unique — the common production case. */ + @Benchmark + public AttributesMap uniqueKeys() { + AttributesMap map = AttributesMap.create(CAPACITY, Integer.MAX_VALUE); + for (int i = 0; i < numAttributes; i++) { + map.put(stringKeys.get(i), values.get(i)); + } + return map; + } + + // ---- Read benchmarks (operate on pre-filled map) ---- + + /** + * Lookup with the exact stored key type — always a hit. Measures the cost of a successful {@code + * get()} for each entry in the map. + */ + @Benchmark + public void getHit(Blackhole bh) { + for (int i = 0; i < numAttributes; i++) { + bh.consume(filledMap.get(stringKeys.get(i))); + } + } + + /** + * Lookup with a different type for the same key name — always returns null. + * + *

The map holds N string-typed entries; boolean keys for the same names locate each entry by + * name but fail the type check. Isolates the cost of a name-hit / type-miss lookup. + */ + @Benchmark + public void getTypeMiss(Blackhole bh) { + for (int i = 0; i < numAttributes; i++) { + bh.consume(filledMap.get(boolKeys.get(i))); + } + } + + /** Full iteration over all entries via {@code forEach}. */ + @Benchmark + public void forEachAll(Blackhole bh) { + filledMap.forEach((k, v) -> bh.consume(v)); + } + + /** + * Combined write + read cycle: fill a fresh map with N unique string keys, then iterate all + * entries once. Models the dominant production path: N puts during span building, followed by one + * forEach at export time. + */ + @Benchmark + public void putThenForEach(Blackhole bh) { + AttributesMap map = AttributesMap.create(CAPACITY, Integer.MAX_VALUE); + for (int i = 0; i < numAttributes; i++) { + map.put(stringKeys.get(i), values.get(i)); + } + map.forEach((k, v) -> bh.consume(v)); + } +} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/AttributesMap.java b/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/AttributesMap.java index 0ddf9599752..2f66aef39b2 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/AttributesMap.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/common/internal/AttributesMap.java @@ -8,7 +8,9 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import java.util.Arrays; import java.util.Collections; +import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.Map; import java.util.function.BiConsumer; @@ -18,28 +20,82 @@ * A map with a fixed capacity that drops attributes when the map gets full, and which truncates * string and array string attribute values to the {@link #lengthLimit}. * - *

WARNING: In order to reduce memory allocation, this class extends {@link HashMap} when it - * would be more appropriate to delegate. The problem with extending is that we don't enforce that - * all {@link HashMap} methods for reading / writing data conform to the configured attribute - * limits. Therefore, it's easy to accidentally call something like {@link Map#putAll(Map)} and - * bypass the restrictions (see #7135). Callers MUST - * take care to only call methods from {@link AttributesMap}, and not {@link HashMap}. + *

Keyed internally by attribute name, so that attributes with the same name but different types + * are treated as the same key (last-value-wins), consistent with the OpenTelemetry specification. + * + *

Backed by parallel arrays and an open-addressing {@code int[]} hash table (linear probing, + * load factor ≤ 0.5). Avoids per-entry object allocation; {@code forEach} is a tight sequential + * array loop with no pointer chasing. + * + *

Not thread-safe. Callers sharing an instance across threads must externally + * synchronize. Concurrent mutation is undefined behavior and may throw {@link + * ArrayIndexOutOfBoundsException} as readers observe the parallel arrays and hash table in + * inconsistent states. {@link #forEach}'s {@link ConcurrentModificationException} on structural + * modification is a same-thread misuse detector, not a synchronization primitive. * *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. */ -public final class AttributesMap extends HashMap, Object> implements Attributes { +public final class AttributesMap implements Attributes { - private static final long serialVersionUID = -5072696312123632376L; + /** + * Sentinel meaning "slot is empty" in the hash table. This is a value stored in {@code + * hashTable[slot]}, not a slot address; a name whose {@link String#hashCode()} is 0 simply hashes + * to slot 0 like any other slot address, and occupancy is decided by comparing the stored value. + * + *

Using 0 lets {@code new int[n]} (JVM zero-initialization) serve as the initial fill, + * eliminating explicit {@code Arrays.fill} calls. Occupied slots store {@code entryIndex + 1} so + * that entry index 0 is distinguishable from EMPTY. + */ + private static final int EMPTY = 0; - private final long capacity; + private final int capacity; private final int lengthLimit; private int totalAddedValues = 0; + private int size = 0; + + /** + * Open-addressing hash table: {@code hashTable[slot]} = index into entry arrays, or {@link + * #EMPTY}. Length is always a power of 2 and ≥ 2× the entry array length (load factor ≤ 0.5). + */ + private int[] hashTable; + + /** Cached {@code hashTable.length - 1}; kept in sync with {@link #hashTable}. */ + private int mask; + + /** + * Parallel entry arrays. For entry {@code i} (in insertion order): + * + *

    + *
  • {@link #entryNames}{@code [i]} is the attribute name (cached from {@code + * entryKeys[i].getKey()} to avoid an extra dereference on every probe step). + *
  • {@link #entryKeys}{@code [i]} is the last-put {@link AttributeKey} for that name, + * preserving the caller's type at query time via {@link #get}. + *
  • {@link #entryValues}{@code [i]} is the last-put value, post-length-limit application. + *
+ * + *

All three are reallocated together in {@link #grow}; entry positions never change. + */ + private String[] entryNames; + + private AttributeKey[] entryKeys; + private Object[] entryValues; + + /** + * Incremented on every mutation that changes observable state (insert or overwrite). Snapshotted + * by {@link #forEach} to detect same-thread structural modification during iteration. + */ + private int modCount = 0; private AttributesMap(long capacity, int lengthLimit) { - this.capacity = capacity; + this.capacity = (int) Math.min(capacity, Integer.MAX_VALUE); this.lengthLimit = lengthLimit; + int init = (int) Math.min(capacity, 16L); + entryNames = new String[init]; + entryKeys = new AttributeKey[init]; + entryValues = new Object[init]; + hashTable = new int[tableSizeFor(init)]; // JVM zero-init == EMPTY + mask = hashTable.length - 1; } /** @@ -55,18 +111,45 @@ public static AttributesMap create(long capacity, int lengthLimit) { /** * Add the attribute key value pair, applying capacity and length limits. Callers MUST ensure the * {@code value} type matches the type required by {@code key}. + * + *

If an attribute with the same string key name already exists (regardless of type), it is + * overwritten — last-value-wins, consistent with the OTel spec. */ - @Override @Nullable public Object put(AttributeKey key, @Nullable Object value) { if (value == null) { return null; } totalAddedValues++; - if (size() >= capacity && !containsKey(key)) { + String name = key.getKey(); + int slot = findSlot(name); + int stored = hashTable[slot]; + if (stored == EMPTY && size >= capacity) { + // Drop new entry per spec. totalAddedValues++ above captures the drop for + // getTotalAddedValues() / downstream drop-count metrics. return null; } - return super.put(key, AttributeUtil.applyAttributeLengthLimit(value, lengthLimit)); + Object limitedValue = AttributeUtil.applyAttributeLengthLimit(value, lengthLimit); + int idx; + Object old; + if (stored == EMPTY) { + if (size == entryNames.length) { + grow(); + slot = findSlot(name); // grow() rebuilt hashTable + } + idx = size; + entryNames[idx] = name; + hashTable[slot] = idx + 1; + size++; + old = null; + } else { + idx = stored - 1; + old = entryValues[idx]; + } + modCount++; + entryKeys[idx] = key; + entryValues[idx] = limitedValue; + return old; } /** Generic overload of {@link #put(AttributeKey, Object)}. */ @@ -83,17 +166,34 @@ public int getTotalAddedValues() { @Override @Nullable public T get(AttributeKey key) { - return (T) super.get(key); + int stored = hashTable[findSlot(key.getKey())]; + if (stored == EMPTY) { + return null; + } + int idx = stored - 1; + if (!entryKeys[idx].getType().equals(key.getType())) { + return null; + } + return (T) entryValues[idx]; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; } @Override public Map, Object> asMap() { - // Because Attributes is marked Immutable, IDEs may recognize this as redundant usage. However, - // this class is private and is actually mutable, so we need to wrap with unmodifiableMap - // anyways. We implement the immutable Attributes for this class to support the - // Attributes.builder().putAll usage - it is tricky but an implementation detail of this private - // class. - return Collections.unmodifiableMap(this); + Map, Object> snapshot = new HashMap<>(size); + for (int i = 0; i < size; i++) { + snapshot.put(entryKeys[i], entryValues[i]); + } + return Collections.unmodifiableMap(snapshot); } @Override @@ -103,17 +203,36 @@ public AttributesBuilder toBuilder() { @Override public void forEach(BiConsumer, ? super Object> action) { - // https://github.com/open-telemetry/opentelemetry-java/issues/4161 - // Help out android desugaring by having an explicit call to HashMap.forEach, when forEach is - // just called through Attributes.forEach desugaring is unable to correctly handle it. - super.forEach(action); + int expectedModCount = modCount; + for (int i = 0; i < size; i++) { + action.accept(entryKeys[i], entryValues[i]); + } + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof AttributesMap)) { + return false; + } + return asMap().equals(((AttributesMap) o).asMap()); + } + + @Override + public int hashCode() { + return asMap().hashCode(); } @Override public String toString() { return "AttributesMap{" + "data=" - + super.toString() + + asMap() + ", capacity=" + capacity + ", totalAddedValues=" @@ -125,4 +244,47 @@ public String toString() { public Attributes immutableCopy() { return Attributes.builder().putAll(this).build(); } + + /** + * Returns the hash table slot that either contains the entry for {@code name} or is the first + * empty slot available for insertion. Single shared probe loop used by {@code put}, {@code get}, + * and {@code grow}. Slots store {@code entryIndex + 1}; 0 ({@link #EMPTY}) means unoccupied. + */ + private int findSlot(String name) { + // Linear probe: stop on empty slot (name absent; insertion point) or matching-name slot (name + // found). `& mask` wraps at the end of the table. + int slot = name.hashCode() & mask; + int stored; + while ((stored = hashTable[slot]) != EMPTY && !entryNames[stored - 1].equals(name)) { + slot = (slot + 1) & mask; + } + return slot; + } + + private void grow() { + long maxLen = Math.min(capacity, (long) Integer.MAX_VALUE - 8); + int newLen = (int) Math.min((long) entryNames.length * 2, maxLen); + entryNames = Arrays.copyOf(entryNames, newLen); + entryKeys = Arrays.copyOf(entryKeys, newLen); + entryValues = Arrays.copyOf(entryValues, newLen); + hashTable = new int[tableSizeFor(newLen)]; // JVM zero-init == EMPTY + mask = hashTable.length - 1; + // Rehash: entry positions in the arrays don't change, but their slot addresses do (new mask). + for (int i = 0; i < size; i++) { + int slot = findSlot(entryNames[i]); + hashTable[slot] = i + 1; + } + } + + /** + * Returns the smallest power of 2 that is ≥ 2n, guaranteeing load factor ≤ 0.5. Using {@code + * (2n-1)} instead of {@code 2n} prevents doubling the result when {@code n} is itself a power of + * 2. + */ + private static int tableSizeFor(int n) { + if (n <= 2) { + return 4; + } + return Integer.highestOneBit(2 * n - 1) << 1; + } } diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java index a7a1f8ecb2e..596ab9f926d 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/AttributesMapTest.java @@ -5,14 +5,190 @@ package io.opentelemetry.sdk.common.internal; +import static io.opentelemetry.api.common.AttributeKey.booleanKey; import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.entry; +import com.google.common.testing.EqualsTester; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import java.util.AbstractList; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; import org.junit.jupiter.api.Test; class AttributesMapTest { + // ---- put ---- + + @Test + void put_returnsNullForNewEntry() { + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + + assertThat(map.put(stringKey("k"), "v")).isNull(); + } + + @Test + void put_returnsOldValueOnOverwrite() { + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + map.put(stringKey("k"), "first"); + + assertThat(map.put(stringKey("k"), "second")).isEqualTo("first"); + assertThat(map.get(stringKey("k"))).isEqualTo("second"); + } + + @Test + void put_ignoresNullValue() { + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + map.put(stringKey("k"), null); + + assertThat(map.size()).isEqualTo(0); + assertThat(map.isEmpty()).isTrue(); + assertThat(map.getTotalAddedValues()).isEqualTo(0); + } + + @Test + void putSameKeyDifferentType_lastValueWins() { + AttributesMap map = AttributesMap.create(128, Integer.MAX_VALUE); + map.put(stringKey("k"), "hello"); + map.put(booleanKey("k"), false); + + assertThat(map.size()).isEqualTo(1); + assertThat(map.get(booleanKey("k"))).isEqualTo(false); + assertThat(map.get(stringKey("k"))).isNull(); + } + + @Test + void putSameKeyDifferentType_doesNotConsumeExtraCapacity() { + AttributesMap map = AttributesMap.create(2, Integer.MAX_VALUE); + map.put(stringKey("a"), "v1"); + map.put(booleanKey("a"), false); // overwrite — must not consume a new capacity slot + map.put(longKey("b"), 42L); + + assertThat(map.size()).isEqualTo(2); + assertThat(map.get(booleanKey("a"))).isEqualTo(false); + assertThat(map.get(longKey("b"))).isEqualTo(42L); + } + + @Test + void putSameKeyDifferentType_previousTypeGetReturnsNull() { + AttributesMap map = AttributesMap.create(128, Integer.MAX_VALUE); + map.put(stringKey("k"), "hello"); + map.put(booleanKey("k"), true); + + assertThat(map.get(stringKey("k"))).isNull(); + assertThat(map.get(booleanKey("k"))).isEqualTo(true); + } + + // ---- get ---- + + @Test + void get_returnsNullForAbsentKey() { + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + + assertThat(map.get(stringKey("absent"))).isNull(); + } + + // ---- capacity ---- + + @Test + void capacity_dropsEntriesBeyondLimit() { + AttributesMap map = AttributesMap.create(2, Integer.MAX_VALUE); + map.put(stringKey("a"), "v1"); + map.put(stringKey("b"), "v2"); + map.put(stringKey("c"), "v3"); // dropped — capacity reached + + assertThat(map.size()).isEqualTo(2); + assertThat(map.getTotalAddedValues()).isEqualTo(3); + assertThat(map.get(stringKey("c"))).isNull(); + } + + @Test + void capacity_zeroDropsAllEntries() { + AttributesMap map = AttributesMap.create(0, Integer.MAX_VALUE); + map.put(stringKey("k"), "v"); + + assertThat(map.size()).isEqualTo(0); + assertThat(map.isEmpty()).isTrue(); + } + + // ---- grow ---- + + @Test + void grow_preservesAllEntriesWhenSizeExceedsInitialArrayLength() { + // init = min(capacity, 16) = 16; grow() is triggered when the 17th entry is inserted + int n = 20; + AttributesMap map = AttributesMap.create(n, Integer.MAX_VALUE); + for (int i = 0; i < n; i++) { + map.put(stringKey("key" + i), "val" + i); + } + + assertThat(map.size()).isEqualTo(n); + for (int i = 0; i < n; i++) { + assertThat(map.get(stringKey("key" + i))).isEqualTo("val" + i); + } + } + + // ---- lengthLimit ---- + + @Test + void lengthLimit_truncatesStringValues() { + AttributesMap map = AttributesMap.create(10, 3); + map.put(stringKey("k"), "hello"); + + assertThat(map.get(stringKey("k"))).isEqualTo("hel"); + } + + @Test + void lengthLimit_failureDoesNotInsertPartialEntry() { + AttributesMap map = AttributesMap.create(10, 3); + + assertThatThrownBy(() -> map.put(stringArrayKey("k"), throwingList())) + .isInstanceOf(IllegalStateException.class); + + assertThat(map.isEmpty()).isTrue(); + assertThat(map.asMap()).isEmpty(); + } + + @Test + void lengthLimit_failureDoesNotPartiallyOverwriteEntry() { + AttributesMap map = AttributesMap.create(10, 3); + map.put(stringKey("k"), "old"); + + assertThatThrownBy(() -> map.put(stringArrayKey("k"), throwingList())) + .isInstanceOf(IllegalStateException.class); + + assertThat(map.size()).isEqualTo(1); + assertThat(map.get(stringKey("k"))).isEqualTo("old"); + assertThat(map.get(stringArrayKey("k"))).isNull(); + } + + // ---- forEach ---- + + @Test + void forEach_iteratesInInsertionOrder() { + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + map.put(stringKey("first"), "v1"); + map.put(stringKey("second"), "v2"); + map.put(stringKey("third"), "v3"); + + List keys = new ArrayList<>(); + map.forEach((k, v) -> keys.add(k.getKey())); + + assertThat(keys).containsExactly("first", "second", "third"); + } + + // ---- views ---- + @Test void asMap() { AttributesMap attributesMap = AttributesMap.create(2, Integer.MAX_VALUE); @@ -22,4 +198,173 @@ void asMap() { assertThat(attributesMap.asMap()) .containsOnly(entry(longKey("one"), 1L), entry(longKey("two"), 2L)); } + + @Test + void immutableCopy_containsAllEntries() { + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + map.put(stringKey("a"), "v1"); + map.put(longKey("b"), 42L); + + Attributes copy = map.immutableCopy(); + + assertThat(copy.get(stringKey("a"))).isEqualTo("v1"); + assertThat(copy.get(longKey("b"))).isEqualTo(42L); + } + + // ---- hash collisions ---- + + @Test + void hashCollision_bothEntriesStoredAndRetrievable() { + // "Aa".hashCode() == "BB".hashCode() == 2112: collide in any table size. + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + map.put(stringKey("Aa"), "v-Aa"); + map.put(stringKey("BB"), "v-BB"); + + assertThat(map.size()).isEqualTo(2); + assertThat(map.get(stringKey("Aa"))).isEqualTo("v-Aa"); + assertThat(map.get(stringKey("BB"))).isEqualTo("v-BB"); + } + + @Test + void findSlot_wrapsAroundEndOfTable() { + // capacity=4 => mask=7. "o" (111) and "w" (119) both hash to slot 7; the second wraps to 0. + AttributesMap map = AttributesMap.create(4, Integer.MAX_VALUE); + map.put(stringKey("o"), "v-o"); + map.put(stringKey("w"), "v-w"); + + assertThat(map.size()).isEqualTo(2); + assertThat(map.get(stringKey("o"))).isEqualTo("v-o"); + assertThat(map.get(stringKey("w"))).isEqualTo("v-w"); + } + + @Test + void grow_preservesEntriesIncludingPreExistingCollision() { + // capacity=20 => init=16 => grow triggers on 17th insert. "Aa"/"BB" collide at slot 0 both + // before and after grow (2112 & 31 == 2112 & 63 == 0), so rehash must preserve the probe path. + AttributesMap map = AttributesMap.create(20, Integer.MAX_VALUE); + map.put(stringKey("Aa"), "v-Aa"); + map.put(stringKey("BB"), "v-BB"); + for (int i = 0; i < 18; i++) { + map.put(stringKey("k" + i), "v" + i); + } + + assertThat(map.size()).isEqualTo(20); + assertThat(map.get(stringKey("Aa"))).isEqualTo("v-Aa"); + assertThat(map.get(stringKey("BB"))).isEqualTo("v-BB"); + for (int i = 0; i < 18; i++) { + assertThat(map.get(stringKey("k" + i))).isEqualTo("v" + i); + } + } + + // ---- concurrent modification detection ---- + + @Test + void forEach_throwsCmeOnConcurrentModification() { + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + map.put(stringKey("a"), "v1"); + map.put(stringKey("b"), "v2"); + + assertThatThrownBy(() -> map.forEach((k, v) -> map.put(stringKey("c"), "v3"))) + .isInstanceOf(ConcurrentModificationException.class); + } + + @Test + void forEach_overwriteDuringIterationThrowsCme() { + // Overwrite (no size change) still bumps modCount. + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + map.put(stringKey("a"), "v1"); + + assertThatThrownBy(() -> map.forEach((k, v) -> map.put(stringKey("a"), "v2"))) + .isInstanceOf(ConcurrentModificationException.class); + } + + @Test + void forEach_noModification_doesNotThrow() { + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + map.put(stringKey("a"), "v1"); + map.put(stringKey("b"), "v2"); + + map.forEach((k, v) -> {}); + } + + // ---- fuzz ---- + + @Test + void fuzz_matchesReferenceHashMap() { + // Random puts vs reference HashMap. Exercises grow, overwrites, and type-varying puts to + // the same name. Fixed seed for reproducibility. + long seed = 0xC0FFEEL; + Random r = new Random(seed); + int capacity = 1000; + int ops = 5000; + int namePoolSize = 200; // ~25 overwrites per name on average + + AttributesMap map = AttributesMap.create(capacity, Integer.MAX_VALUE); + Map, Object>> reference = new HashMap<>(); + + for (int i = 0; i < ops; i++) { + String name = "key" + r.nextInt(namePoolSize); + AttributeKey key; + Object value; + switch (r.nextInt(3)) { + case 0: + key = stringKey(name); + value = "s" + i; + break; + case 1: + key = longKey(name); + value = (long) i; + break; + default: + key = booleanKey(name); + value = (i & 1) == 0; + break; + } + map.put(key, value); + reference.put(name, new AbstractMap.SimpleImmutableEntry<>(key, value)); + } + + assertThat(map.size()).isEqualTo(reference.size()); + for (Map.Entry, Object>> refEntry : reference.entrySet()) { + AttributeKey expectedKey = refEntry.getValue().getKey(); + Object expectedValue = refEntry.getValue().getValue(); + assertThat(map.get(expectedKey)).as("key=%s", expectedKey).isEqualTo(expectedValue); + } + } + + @Test + void equals_andHashCode() { + AttributesMap mapV1a = AttributesMap.create(10, Integer.MAX_VALUE); + mapV1a.put(stringKey("k"), "v1"); + AttributesMap mapV1b = AttributesMap.create(10, Integer.MAX_VALUE); + mapV1b.put(stringKey("k"), "v1"); + AttributesMap mapV2 = AttributesMap.create(10, Integer.MAX_VALUE); + mapV2.put(stringKey("k"), "v2"); + + new EqualsTester().addEqualityGroup(mapV1a, mapV1b).addEqualityGroup(mapV2).testEquals(); + } + + @Test + void equals_isSymmetricWithOtherAttributesImplementations() { + AttributesMap map = AttributesMap.create(10, Integer.MAX_VALUE); + map.put(stringKey("k"), "v"); + Attributes attributes = Attributes.of(stringKey("k"), "v"); + + assertThat(map).isNotEqualTo(attributes); + assertThat(attributes).isNotEqualTo(map); + } + + private static List throwingList() { + return new AbstractList() { + @Override + public String get(int index) { + throw new IllegalStateException("test"); + } + + @Override + public int size() { + return 1; + } + }; + } } diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/ComponentIdTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/ComponentIdTest.java index 4d3e23c2b2b..241587a97da 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/ComponentIdTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/common/internal/ComponentIdTest.java @@ -43,8 +43,5 @@ void testStandardTypesUpToDate() { // assertThat(ComponentId.StandardType.OTLP_HTTP_JSON_METRIC_EXPORTER.value) // // .isEqualTo(OtelIncubatingAttributes.OtelComponentTypeIncubatingValues.OTLP_HTTP_JSON_METRIC_EXPORTER); - // assertThat(ComponentId.StandardType.ZIPKIN_HTTP_SPAN_EXPORTER.value) - // - // .isEqualTo(OtelIncubatingAttributes.OtelComponentTypeIncubatingValues.ZIPKIN_HTTP_SPAN_EXPORTER); } } diff --git a/sdk/metrics/src/jmhBasedTest/java/io/opentelemetry/sdk/metrics/internal/state/TestInstrumentType.java b/sdk/metrics/src/jmhBasedTest/java/io/opentelemetry/sdk/metrics/internal/state/TestInstrumentType.java index 5a462fd94c8..9877d02a945 100644 --- a/sdk/metrics/src/jmhBasedTest/java/io/opentelemetry/sdk/metrics/internal/state/TestInstrumentType.java +++ b/sdk/metrics/src/jmhBasedTest/java/io/opentelemetry/sdk/metrics/internal/state/TestInstrumentType.java @@ -31,7 +31,7 @@ public enum TestInstrumentType { DOUBLE_SUM( DoubleSumTester::new, /* dataAllocRateReductionPercentage= */ 97.3f, - /* allowedPercentOffset= */ 2.0f), + /* allowedPercentOffset= */ 4.0f), LONG_LAST_VALUE( LongLastValueTester::new, /* dataAllocRateReductionPercentage= */ 97.3f, diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/data/ImmutableSummaryData.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/data/ImmutableSummaryData.java index 0c1f4db1bd6..bf5cc390727 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/data/ImmutableSummaryData.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/data/ImmutableSummaryData.java @@ -16,7 +16,7 @@ * A summary metric point. * *

See: - * https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md#summary + * https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md#summary * *

Note: This is called "DoubleSummary" to reflect which primitives are used to record it, * however "Summary" is the equivalent OTLP type. diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java index 2a69ade6702..b8f662e2791 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java @@ -1172,4 +1172,25 @@ void doNotCrash() { }) .doesNotThrowAnyException(); } + + @Test + void setAttribute_sameKeyDifferentType_lastValueWins() { + // Regression test for https://github.com/open-telemetry/opentelemetry-java/issues/7897 + // Setting the same string key with different types must overwrite, not accumulate. + SdkSpan span = + (SdkSpan) + sdkTracer + .spanBuilder("test") + .setAttribute("key", "string_value") + .setAttribute("key", false) + .startSpan(); + try { + Attributes attributes = span.toSpanData().getAttributes(); + assertThat(attributes.size()).isEqualTo(1); + assertThat(attributes.get(booleanKey("key"))).isEqualTo(false); + assertThat(attributes.get(stringKey("key"))).isNull(); + } finally { + span.end(); + } + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index d6120e3b32b..593d6e29cb4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,7 +4,7 @@ pluginManagement { id("com.gradle.develocity") version "4.5.0" id("de.undercouch.download") version "5.7.0" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("org.graalvm.buildtools.native") version "1.1.6" + id("org.graalvm.buildtools.native") version "1.1.7" id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } } @@ -47,7 +47,6 @@ include(":exporters:otlp:common") include(":exporters:otlp:profiles") include(":exporters:otlp:testing-internal") include(":exporters:prometheus") -include(":exporters:zipkin") include(":integration-tests") include(":integration-tests:otlp") include(":integration-tests:tracecontext")