diff --git a/.ci/updatecli/values.d/apm-data-spec.yml b/.ci/updatecli/values.d/apm-data-spec.yml index fd8c9e37f7..eda04f9f54 100644 --- a/.ci/updatecli/values.d/apm-data-spec.yml +++ b/.ci/updatecli/values.d/apm-data-spec.yml @@ -1,2 +1 @@ -apm_schema_specs_path: apm-agent-core/src/test/resources/apm-server-schema/current -signedcommit: true \ No newline at end of file +apm_schema_specs_path: apm-agent-core/src/test/resources/apm-server-schema/current \ No newline at end of file diff --git a/.ci/updatecli/values.d/apm-gherkin.yml b/.ci/updatecli/values.d/apm-gherkin.yml index d676b58b2a..94a53a83ae 100644 --- a/.ci/updatecli/values.d/apm-gherkin.yml +++ b/.ci/updatecli/values.d/apm-gherkin.yml @@ -1,2 +1 @@ -apm_gherkin_specs_path: apm-agent-core/src/test/resources/specs -signedcommit: true \ No newline at end of file +apm_gherkin_specs_path: apm-agent-core/src/test/resources/specs \ No newline at end of file diff --git a/.ci/updatecli/values.d/apm-json-specs.yml b/.ci/updatecli/values.d/apm-json-specs.yml index b1b70a8e69..56874ee125 100644 --- a/.ci/updatecli/values.d/apm-json-specs.yml +++ b/.ci/updatecli/values.d/apm-json-specs.yml @@ -1,2 +1 @@ -apm_json_specs_path: apm-agent-core/src/test/resources/json-specs -signedcommit: true \ No newline at end of file +apm_json_specs_path: apm-agent-core/src/test/resources/json-specs \ No newline at end of file diff --git a/.ci/updatecli/values.d/scm.yml b/.ci/updatecli/values.d/scm.yml index 5cfcec7d2a..15964e08e0 100644 --- a/.ci/updatecli/values.d/scm.yml +++ b/.ci/updatecli/values.d/scm.yml @@ -4,6 +4,7 @@ scm: repository: apm-agent-java apm_repository: apm branch: main + commitusingapi: true # begin update-compose policy values user: obltmachine email: obltmachine@users.noreply.github.com diff --git a/.ci/updatecli/values.d/update-compose.yml b/.ci/updatecli/values.d/update-compose.yml index 8804f96618..02df609f2a 100644 --- a/.ci/updatecli/values.d/update-compose.yml +++ b/.ci/updatecli/values.d/update-compose.yml @@ -1,3 +1,3 @@ spec: files: - - "update-compose.yaml" \ No newline at end of file + - "updatecli-compose.yaml" \ No newline at end of file diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 809b6eda02..bcfc707160 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -30,7 +30,7 @@ jobs: username: ${{ github.actor }} token: ${{ secrets.APM_TECH_USER_TOKEN }} - name: Add community and triage lables - if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' + if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'elastic-renovate-prod[bot]' uses: actions/github-script@v7 with: script: | @@ -41,7 +41,7 @@ jobs: labels: ["community", "triage"] }) - name: Add comment for community PR - if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' + if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'elastic-renovate-prod[bot]' uses: wow-actions/auto-comment@v1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4691fb2baa..b8ea06f00b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -340,7 +340,7 @@ jobs: - uses: actions/checkout@v4 - name: Log in to the Elastic Container registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} diff --git a/.github/workflows/release-step-3.yml b/.github/workflows/release-step-3.yml index b7dd6d3ac5..5825547d0c 100644 --- a/.github/workflows/release-step-3.yml +++ b/.github/workflows/release-step-3.yml @@ -105,7 +105,7 @@ jobs: run: tar xvf ${{ env.TARBALL_FILE }} - name: generate build provenance - uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 with: subject-path: "${{ github.workspace }}/**/target/*.jar" @@ -115,10 +115,10 @@ jobs: needs: - validate-tag steps: - - uses: elastic/apm-pipeline-library/.github/actions/await-maven-artifact@current + - uses: elastic/oblt-actions/maven/await-artifact@v1 with: - groupid: 'co.elastic.apm' - artifactid: 'elastic-apm-agent' + group-id: 'co.elastic.apm' + artifact-id: 'elastic-apm-agent' version: ${{ env.RELEASE_VERSION }} build-and-push-docker-images: @@ -136,7 +136,7 @@ jobs: fetch-depth: 0 # Load entire history as it is required for the push-script - name: Log in to the Elastic Container registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 1b58ce6f6f..c6dbef3629 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -69,7 +69,7 @@ jobs: run: tar xvf ${{ env.TARBALL_FILE }} - name: generate build provenance - uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 with: subject-path: "${{ github.workspace }}/**/target/*.jar" @@ -80,7 +80,7 @@ jobs: - uses: actions/checkout@v4 - name: Log in to the Elastic Container registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} diff --git a/.github/workflows/stash/action.yml b/.github/workflows/stash/action.yml index edcbdcbaec..27d888f618 100644 --- a/.github/workflows/stash/action.yml +++ b/.github/workflows/stash/action.yml @@ -17,7 +17,7 @@ runs: shell: bash - name: Upload stash - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ inputs.name }} path: ${{ runner.temp }}/${{ inputs.name }}.tar.zst diff --git a/.github/workflows/test-reporter.yml b/.github/workflows/test-reporter.yml index dae96d5e09..15fcb7aa74 100644 --- a/.github/workflows/test-reporter.yml +++ b/.github/workflows/test-reporter.yml @@ -17,7 +17,7 @@ jobs: report: runs-on: ubuntu-latest steps: - - uses: elastic/apm-pipeline-library/.github/actions/test-report@current + - uses: elastic/oblt-actions/test-report@v1 with: artifact: /test-results-(.*)/ name: 'Test Results $1' diff --git a/.github/workflows/unstash/action.yml b/.github/workflows/unstash/action.yml index a62a7b7d1e..320a646119 100644 --- a/.github/workflows/unstash/action.yml +++ b/.github/workflows/unstash/action.yml @@ -13,7 +13,7 @@ runs: using: "composite" steps: - name: Download stash - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ inputs.name }} path: ${{ runner.temp }} diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 99369c845d..c8f4b4fb08 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -17,7 +17,19 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + - name: Get token + id: get_token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + with: + app_id: ${{ secrets.OBS_AUTOMATION_APP_ID }} + private_key: ${{ secrets.OBS_AUTOMATION_APP_PEM }} + permissions: >- + { + "contents": "write", + "pull_requests": "write" + } + + - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -27,13 +39,13 @@ jobs: with: command: --experimental compose diff env: - GITHUB_TOKEN: ${{ secrets.UPDATECLI_GH_TOKEN }} + GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - uses: elastic/oblt-actions/updatecli/run@v1 with: command: --experimental compose apply env: - GITHUB_TOKEN: ${{ secrets.UPDATECLI_GH_TOKEN }} + GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - if: failure() uses: elastic/oblt-actions/slack/send@v1 diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 1133b4fcb7..2d294d7773 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -34,6 +34,19 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: [[release-notes-1.x]] === Java Agent version 1.x +[[release-notes-1.52.0]] +==== 1.52.0 - 2024/09/23 + +[float] +===== Bug fixes +* Fix log4j2 log correlation with shaded application jar - {pull}3764[#3764] +* Improve automatic span class name detection for Scala and nested/anonymous classes - {pull}3746[#3746] + +[float] +===== Features +* Added experimental option to capture HTTP client request bodies for Apache Http Client v4 and v5, HttpUrlConnection and Spring WebClient - {pull}3776[#3776], {pull}3962[#3962], {pull}3724[#3724], {pull}3754[#3754], {pull}3767[#3767] +* Agent health metrics now GA - {pull}3802[#3802] + [[release-notes-1.51.0]] ==== 1.51.0 - 2024/07/24 @@ -49,6 +62,7 @@ Use subheadings with the "=====" level for adding notes for unreleased changes: ===== Features * Added option to make routing-key part of RabbitMQ transaction/span names - {pull}3636[#3636] * Added internal option for capturing request bodies for apache httpclient v4 - {pull}3692[#3692] +* Added automatic module name to apm-agent-attach - {pull}3743[#3743] [[release-notes-1.50.0]] ==== 1.50.0 - 2024/05/28 diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index df2ecfc4eb..348d6681b4 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,4 +1,4 @@ -FROM docker.elastic.co/wolfi/chainguard-base@sha256:9f940409f96296ef56140bcc4665c204dd499af4c32c96cc00e792558097c3f1 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:d4def25f2fd3b0ff9bc68091cd1d89524e41b7d3fc0d3b3a665720eb92145f3b RUN mkdir /usr/agent ARG JAR_FILE ARG HANDLER_FILE diff --git a/apm-agent-api/pom.xml b/apm-agent-api/pom.xml index d427230e91..501e10e306 100644 --- a/apm-agent-api/pom.xml +++ b/apm-agent-api/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 apm-agent-api diff --git a/apm-agent-attach-cli/pom.xml b/apm-agent-attach-cli/pom.xml index 065db587e5..bffc06a48c 100644 --- a/apm-agent-attach-cli/pom.xml +++ b/apm-agent-attach-cli/pom.xml @@ -3,7 +3,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-attach/pom.xml b/apm-agent-attach/pom.xml index 5b2c32d9b6..c1d7dfa567 100644 --- a/apm-agent-attach/pom.xml +++ b/apm-agent-attach/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 apm-agent-attach @@ -165,6 +165,13 @@ + + + + ${project.groupId}.attach + + + diff --git a/apm-agent-benchmarks/pom.xml b/apm-agent-benchmarks/pom.xml index 08222ecfc4..3cb3f7fd5b 100644 --- a/apm-agent-benchmarks/pom.xml +++ b/apm-agent-benchmarks/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 apm-agent-benchmarks diff --git a/apm-agent-bootstrap/pom.xml b/apm-agent-bootstrap/pom.xml index ecc5bcec1e..c31662c60f 100644 --- a/apm-agent-bootstrap/pom.xml +++ b/apm-agent-bootstrap/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 apm-agent-bootstrap diff --git a/apm-agent-builds/apm-agent-java8/pom.xml b/apm-agent-builds/apm-agent-java8/pom.xml index 59a1a5fc3f..a7c821252e 100644 --- a/apm-agent-builds/apm-agent-java8/pom.xml +++ b/apm-agent-builds/apm-agent-java8/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-builds - 1.51.0 + 1.52.0 apm-agent-java8 diff --git a/apm-agent-builds/apm-agent/pom.xml b/apm-agent-builds/apm-agent/pom.xml index 0b7dd8d4b2..14a7edee69 100644 --- a/apm-agent-builds/apm-agent/pom.xml +++ b/apm-agent-builds/apm-agent/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-builds - 1.51.0 + 1.52.0 apm-agent diff --git a/apm-agent-builds/pom.xml b/apm-agent-builds/pom.xml index 0029da24a6..83e5fa6f19 100644 --- a/apm-agent-builds/pom.xml +++ b/apm-agent-builds/pom.xml @@ -3,7 +3,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-cached-lookup-key/pom.xml b/apm-agent-cached-lookup-key/pom.xml index ae1c75e499..826062dde5 100644 --- a/apm-agent-cached-lookup-key/pom.xml +++ b/apm-agent-cached-lookup-key/pom.xml @@ -3,7 +3,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-common/pom.xml b/apm-agent-common/pom.xml index 57cfb78bd2..7e74f56f6d 100644 --- a/apm-agent-common/pom.xml +++ b/apm-agent-common/pom.xml @@ -3,7 +3,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-core/pom.xml b/apm-agent-core/pom.xml index db09a71f45..55a88c872d 100644 --- a/apm-agent-core/pom.xml +++ b/apm-agent-core/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 apm-agent-core diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/MetricsConfigurationImpl.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/MetricsConfigurationImpl.java index db67ada95f..d0a14030f6 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/MetricsConfigurationImpl.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/MetricsConfigurationImpl.java @@ -93,7 +93,7 @@ public void assertValid(List buckets) { .key("agent_reporter_health_metrics") .configurationCategory(METRICS_CATEGORY) .description("Enables metrics which capture the health state of the agent's event reporting mechanism.") - .tags("added[1.35.0]", "experimental") + .tags("added[1.35.0]") .dynamic(false) .buildWithDefault(false); @@ -101,7 +101,7 @@ public void assertValid(List buckets) { .key("agent_background_overhead_metrics") .configurationCategory(METRICS_CATEGORY) .description("Enables metrics which capture the resource consumption of agent background tasks.") - .tags("added[1.35.0]", "experimental") + .tags("added[1.35.0]") .dynamic(false) .buildWithDefault(false); diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/BodyCaptureImpl.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/BodyCaptureImpl.java index 289a5f6fd2..8cdb1e4973 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/BodyCaptureImpl.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/BodyCaptureImpl.java @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.apm.agent.impl.context; import co.elastic.apm.agent.objectpool.Resetter; @@ -28,9 +46,11 @@ public void recycle(ByteBuffer object) { }); private enum CaptureState { - NOT_ELIGIBLE, - ELIGIBLE, - STARTED + NOT_ELIGIBLE, // initial state + ELIGIBLE, // eligible but before preconditions evaluation + PRECONDITIONS_PASSED, // post preconditions (passed), can start capture + PRECONDITIONS_FAILED, // post preconditions (failed), no body will be captured + STARTED // the body capturing has been started, a buffer was acquired } private volatile CaptureState state; @@ -56,6 +76,7 @@ public void resetState() { charset.setLength(0); if (bodyBuffer != null) { BYTE_BUFFER_POOL.recycle(bodyBuffer); + bodyBuffer = null; } } @@ -76,17 +97,42 @@ public boolean isEligibleForCapturing() { } @Override - public boolean startCapture(@Nullable String requestCharset, int numBytesToCapture) { + public boolean havePreconditionsBeenChecked() { + return state == CaptureState.PRECONDITIONS_PASSED + || state == CaptureState.PRECONDITIONS_FAILED + || state == CaptureState.STARTED; + } + + @Override + public void markPreconditionsFailed() { + synchronized (this) { + if (state == CaptureState.ELIGIBLE) { + state = CaptureState.PRECONDITIONS_FAILED; + } + } + } + + @Override + public void markPreconditionsPassed(@Nullable String requestCharset, int numBytesToCapture) { if (numBytesToCapture > WebConfiguration.MAX_BODY_CAPTURE_BYTES) { throw new IllegalArgumentException("Capturing " + numBytesToCapture + " bytes is not supported, maximum is " + WebConfiguration.MAX_BODY_CAPTURE_BYTES + " bytes"); } - if (state == CaptureState.ELIGIBLE) { + synchronized (this) { + if (state == CaptureState.ELIGIBLE) { + if (requestCharset != null) { + this.charset.append(requestCharset); + } + this.numBytesToCapture = numBytesToCapture; + state = CaptureState.PRECONDITIONS_PASSED; + } + } + } + + @Override + public boolean startCapture() { + if (state == CaptureState.PRECONDITIONS_PASSED) { synchronized (this) { - if (state == CaptureState.ELIGIBLE) { - if (requestCharset != null) { - this.charset.append(requestCharset); - } - this.numBytesToCapture = numBytesToCapture; + if (state == CaptureState.PRECONDITIONS_PASSED) { state = CaptureState.STARTED; return true; } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/SpanImpl.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/SpanImpl.java index e1d3d50e86..19ab8242e8 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/SpanImpl.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/SpanImpl.java @@ -27,21 +27,30 @@ import co.elastic.apm.agent.impl.context.SpanContextImpl; import co.elastic.apm.agent.impl.context.UrlImpl; import co.elastic.apm.agent.impl.stacktrace.StacktraceConfigurationImpl; -import co.elastic.apm.agent.tracer.Span; -import co.elastic.apm.agent.tracer.util.ResultUtil; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; import co.elastic.apm.agent.tracer.Outcome; +import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.SpanEndListener; import co.elastic.apm.agent.tracer.pooling.Recyclable; +import co.elastic.apm.agent.tracer.util.ResultUtil; import co.elastic.apm.agent.util.CharSequenceUtils; import javax.annotation.Nullable; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; public class SpanImpl extends AbstractSpanImpl implements Recyclable, Span { + /** + * Protection against excessive memory usage and span ending run times: + * We limit the maximum allowed number of end listeners. + */ + static final int MAX_END_LISTENERS = 100; private static final Logger logger = LoggerFactory.getLogger(SpanImpl.class); public static final long MAX_LOG_INTERVAL_MICRO_SECS = TimeUnit.MINUTES.toMicros(5); private static long lastSpanMaxWarningTimestamp; @@ -75,6 +84,9 @@ public class SpanImpl extends AbstractSpanImpl implements Recyclable, @Nullable private List stackFrames; + private final Set> endListeners = + Collections.newSetFromMap(new ConcurrentHashMap, Boolean>()); + /** * If a span is non-discardable, all the spans leading up to it are non-discardable as well */ @@ -174,6 +186,25 @@ public SpanImpl withAction(@Nullable String action) { return this; } + @Override + public void addEndListener(SpanEndListener listener) { + if (endListeners.size() < MAX_END_LISTENERS) { + endListeners.add(listener); + } else { + if (logger.isDebugEnabled()) { + logger.warn("Not adding span end listener because limit is reached: {}," + + " throwable stacktrace will be added for debugging", listener, new Throwable()); + } else { + logger.warn("Not adding span end listener because limit is reached: {}", listener); + } + } + } + + @Override + public void removeEndListener(SpanEndListener listener) { + endListeners.remove(listener); + } + /** * Sets span.type, span.subtype and span.action. If no subtype and action are provided, assumes the legacy usage of hierarchical @@ -221,6 +252,9 @@ public String getAction() { @Override public void beforeEnd(long epochMicros) { + for (SpanEndListener endListener : endListeners) { + endListener.onEnd(this); + } // set outcome when not explicitly set by user nor instrumentation if (outcomeNotSet()) { Outcome outcome; @@ -476,6 +510,7 @@ public void resetState() { super.resetState(); context.resetState(); composite.resetState(); + endListeners.clear(); stacktrace = null; subtype = null; action = null; diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java index 141a987f6f..2c9db32c97 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java @@ -62,8 +62,13 @@ import co.elastic.apm.agent.impl.transaction.TransactionImpl; import co.elastic.apm.agent.report.ApmServerClient; import co.elastic.apm.agent.sdk.internal.collections.LongList; +import co.elastic.apm.agent.sdk.internal.pooling.ObjectHandle; +import co.elastic.apm.agent.sdk.internal.pooling.ObjectPool; +import co.elastic.apm.agent.sdk.internal.pooling.ObjectPooling; +import co.elastic.apm.agent.sdk.internal.util.IOUtils; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; +import co.elastic.apm.agent.tracer.configuration.WebConfiguration; import co.elastic.apm.agent.tracer.metadata.PotentiallyMultiValuedMap; import co.elastic.apm.agent.tracer.metrics.DslJsonUtil; import co.elastic.apm.agent.tracer.metrics.Labels; @@ -79,8 +84,10 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.CharBuffer; +import java.nio.charset.CoderResult; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -88,6 +95,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -104,6 +112,13 @@ public class DslJsonSerializer { private static final Logger logger = LoggerFactory.getLogger(DslJsonSerializer.class); private static final List excludedStackFramesPrefixes = Arrays.asList("java.lang.reflect.", "com.sun.", "sun.", "jdk.internal."); + private static final ObjectPool> REQUEST_BODY_BUFFER_POOL = ObjectPooling.createWithDefaultFactory(new Callable() { + @Override + public CharBuffer call() throws Exception { + return CharBuffer.allocate(WebConfiguration.MAX_BODY_CAPTURE_BYTES); + } + }); + private final StacktraceConfigurationImpl stacktraceConfiguration; private final ApmServerClient apmServerClient; @@ -1054,22 +1069,7 @@ private void serializeSpanLinks(List spanLinks) { } private void serializeOTel(SpanImpl span) { - serializeOtel(span, Collections.emptyList(), requestBodyToString(span.getContext().getHttp().getRequestBody())); - } - - @Nullable - private CharSequence requestBodyToString(BodyCaptureImpl requestBody) { - //TODO: perform proper, charset aware conversion to string - ByteBuffer buffer = requestBody.getBody(); - if (buffer == null || buffer.position() == 0) { - return null; - } - buffer.flip(); - StringBuilder result = new StringBuilder(); - while (buffer.hasRemaining()) { - result.append((char) buffer.get()); - } - return result; + serializeOtel(span, Collections.emptyList(), span.getContext().getHttp().getRequestBody()); } private void serializeOTel(TransactionImpl transaction) { @@ -1079,11 +1079,12 @@ private void serializeOTel(TransactionImpl transaction) { } } - private void serializeOtel(AbstractSpanImpl span, List profilingStackTraceIds, @Nullable CharSequence httpRequestBody) { + private void serializeOtel(AbstractSpanImpl span, List profilingStackTraceIds, @Nullable BodyCaptureImpl httpRequestBody) { OTelSpanKind kind = span.getOtelKind(); Map attributes = span.getOtelAttributes(); - boolean hasAttributes = !attributes.isEmpty() || !profilingStackTraceIds.isEmpty() || httpRequestBody != null; + boolean hasRequestBody = httpRequestBody != null && httpRequestBody.getBody() != null; + boolean hasAttributes = !attributes.isEmpty() || !profilingStackTraceIds.isEmpty() || hasRequestBody; boolean hasKind = kind != null; if (hasKind || hasAttributes) { writeFieldName("otel"); @@ -1133,12 +1134,12 @@ private void serializeOtel(AbstractSpanImpl span, List profilingStack } jw.writeByte(ARRAY_END); } - if (httpRequestBody != null) { + if (hasRequestBody) { if (!isFirstAttrib) { jw.writeByte(COMMA); } writeFieldName("http.request.body.content"); - jw.writeString(httpRequestBody); + writeRequestBodyAsString(jw, httpRequestBody); } jw.writeByte(OBJECT_END); } @@ -1148,6 +1149,38 @@ private void serializeOtel(AbstractSpanImpl span, List profilingStack } } + + private void writeRequestBodyAsString(JsonWriter jw, BodyCaptureImpl requestBody) { + try (ObjectHandle charBufferHandle = REQUEST_BODY_BUFFER_POOL.createInstance()) { + CharBuffer charBuffer = charBufferHandle.get(); + try { + decodeRequestBodyBytes(requestBody, charBuffer); + charBuffer.flip(); + jw.writeString(charBuffer); + } finally { + ((Buffer) charBuffer).clear(); + } + } + } + + private void decodeRequestBodyBytes(BodyCaptureImpl requestBody, CharBuffer charBuffer) { + ByteBuffer bodyBytes = requestBody.getBody(); + ((Buffer) bodyBytes).flip(); //make ready for reading + CharSequence charset = requestBody.getCharset(); + if (charset != null) { + CoderResult result = IOUtils.decode(bodyBytes, charBuffer, charset.toString()); + if (result != null && !result.isMalformed() && !result.isUnmappable()) { + return; + } + } + //fallback to decoding by simply casting bytes to chars + ((Buffer) bodyBytes).position(0); + ((Buffer) charBuffer).clear(); + while (bodyBytes.hasRemaining()) { + charBuffer.put((char) (((int) bodyBytes.get()) & 0xFF)); + } + } + private void serializeNumber(Number n, JsonWriter jw) { if (n instanceof Integer) { NumberConverter.serialize(n.intValue(), jw); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/context/BodyCaptureTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/context/BodyCaptureImplTest.java similarity index 50% rename from apm-agent-core/src/test/java/co/elastic/apm/agent/impl/context/BodyCaptureTest.java rename to apm-agent-core/src/test/java/co/elastic/apm/agent/impl/context/BodyCaptureImplTest.java index 2c53ef665b..26ea3172c0 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/context/BodyCaptureTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/context/BodyCaptureImplTest.java @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.apm.agent.impl.context; import org.junit.jupiter.api.Test; @@ -8,13 +26,14 @@ import static co.elastic.apm.agent.testutils.assertions.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -public class BodyCaptureTest { +public class BodyCaptureImplTest { @Test public void testAppendTruncation() { BodyCaptureImpl capture = new BodyCaptureImpl(); capture.markEligibleForCapturing(); - capture.startCapture("foobar", 10); + capture.markPreconditionsPassed("foobar", 10); + capture.startCapture(); assertThat(capture.isFull()).isFalse(); capture.append("123Hello World!".getBytes(StandardCharsets.UTF_8), 3, 5); @@ -37,20 +56,31 @@ public void testLifecycle() { BodyCaptureImpl capture = new BodyCaptureImpl(); assertThat(capture.isEligibleForCapturing()).isFalse(); - assertThat(capture.startCapture("foobar", 42)) + assertThat(capture.havePreconditionsBeenChecked()).isFalse(); + assertThat(capture.startCapture()) .isFalse(); assertThatThrownBy(() -> capture.append((byte) 42)).isInstanceOf(IllegalStateException.class); capture.markEligibleForCapturing(); assertThat(capture.isEligibleForCapturing()).isTrue(); + assertThat(capture.havePreconditionsBeenChecked()).isFalse(); assertThatThrownBy(() -> capture.append((byte) 42)).isInstanceOf(IllegalStateException.class); - assertThat(capture.startCapture("foobar", 42)) - .isTrue(); + capture.markPreconditionsPassed("foobar", 42); + assertThat(capture.isEligibleForCapturing()).isTrue(); + assertThat(capture.havePreconditionsBeenChecked()).isTrue(); + + + assertThat(capture.startCapture()).isTrue(); capture.append((byte) 42); //ensure no exception thrown // startCapture should return true only once - assertThat(capture.startCapture("foobar", 42)) - .isFalse(); + assertThat(capture.havePreconditionsBeenChecked()).isTrue(); + assertThat(capture.startCapture()).isFalse(); + assertThat(capture.havePreconditionsBeenChecked()).isTrue(); + + capture.resetState(); + assertThat(capture.getCharset()).isNull(); + assertThat(capture.getBody()).isNull(); } } diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/SpanTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/SpanTest.java index 6e571b0570..a7d85f9a9c 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/SpanTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/SpanTest.java @@ -26,19 +26,25 @@ import co.elastic.apm.agent.impl.sampling.ConstantSampler; import co.elastic.apm.agent.objectpool.TestObjectPoolFactory; import co.elastic.apm.agent.tracer.Outcome; +import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.SpanEndListener; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; public class SpanTest { @@ -87,6 +93,64 @@ void testOutcomeExplicitlyToUnknown() { assertThat(span.getOutcome()).isEqualTo(Outcome.UNKNOWN); } + @Test + void checkEndListenersConcurrencySafe() { + TransactionImpl transaction = new TransactionImpl(tracer); + transaction.startRoot(0, ConstantSampler.of(true), BaggageImpl.EMPTY); + try { + SpanImpl span = new SpanImpl(tracer); + span.start(TraceContextImpl.fromParent(), transaction, BaggageImpl.EMPTY, -1L); + + AtomicInteger invocationCounter = new AtomicInteger(); + SpanEndListener> callback = new SpanEndListener>() { + @Override + public void onEnd(Span span) { + span.removeEndListener(this); + invocationCounter.incrementAndGet(); + } + }; + span.addEndListener(callback); + span.end(); + assertThat(invocationCounter.get()).isEqualTo(1); + } finally { + transaction.end(); + } + + } + + @Test + @SuppressWarnings("unchecked") + void checkEndListenersLimit() { + TransactionImpl transaction = new TransactionImpl(tracer); + transaction.startRoot(0, ConstantSampler.of(true), BaggageImpl.EMPTY); + try { + SpanImpl span = new SpanImpl(tracer); + span.start(TraceContextImpl.fromParent(), transaction, BaggageImpl.EMPTY, -1L); + + for (int i = 0; i < SpanImpl.MAX_END_LISTENERS - 1; i++) { + span.addEndListener(new SpanEndListener() { + @Override + public void onEnd(SpanImpl span) { + + } + }); + } + + SpanEndListener invokeMe = (SpanEndListener) Mockito.mock(SpanEndListener.class); + SpanEndListener dontInvokeMe = (SpanEndListener) Mockito.mock(SpanEndListener.class); + span.addEndListener(invokeMe); + span.addEndListener(dontInvokeMe); + + span.end(); + + verify(invokeMe).onEnd(span); + verifyNoInteractions(dontInvokeMe); + } finally { + transaction.end(); + } + + } + @Test void normalizeEmptyFields() { SpanImpl span = new SpanImpl(tracer) diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java index 7006fd0fa8..834a9c7f26 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java @@ -57,6 +57,7 @@ import co.elastic.apm.agent.report.ApmServerClient; import co.elastic.apm.agent.sdk.internal.collections.LongList; import co.elastic.apm.agent.sdk.internal.util.IOUtils; +import co.elastic.apm.agent.tracer.configuration.WebConfiguration; import com.dslplatform.json.JsonWriter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -74,6 +75,7 @@ import javax.annotation.Nullable; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -432,18 +434,54 @@ void testSpanHttpContextSerialization() { } @Test - void testSpanHttpRequestBodySerialization() { - SpanImpl span = new SpanImpl(tracer); + void testSpanHttpRequestBodySerialization() throws UnsupportedEncodingException { + String noBody = extractRequestBodyJson(createSpanWithRequestBody(null, "utf-8")); + assertThat(noBody).isNull(); - BodyCaptureImpl bodyCapture = span.getContext().getHttp().getRequestBody(); - bodyCapture.markEligibleForCapturing(); - bodyCapture.startCapture("utf-8", 50); - bodyCapture.append("foobar".getBytes(StandardCharsets.UTF_8), 0, 6); + String emptyBody = extractRequestBodyJson(createSpanWithRequestBody(new byte[0], "utf-8")); + assertThat(emptyBody).isEqualTo(""); + + String invalidCharset = extractRequestBodyJson(createSpanWithRequestBody("testö".getBytes("utf-8"), "bad charset!")); + assertThat(invalidCharset).isEqualTo("testö"); + + String noCharset = extractRequestBodyJson(createSpanWithRequestBody("testö".getBytes("utf-8"), null)); + assertThat(noCharset).isEqualTo("testö"); + + String utf8 = extractRequestBodyJson(createSpanWithRequestBody("special charßßß!äöü".getBytes("utf-8"), "utf-8")); + assertThat(utf8).isEqualTo("special charßßß!äöü"); + + String utf16 = extractRequestBodyJson(createSpanWithRequestBody("special charßßß!äöü".getBytes("utf-16"), "utf-16")); + assertThat(utf16).isEqualTo("special charßßß!äöü"); + + String invalidUtf8Sequence = extractRequestBodyJson(createSpanWithRequestBody(new byte[]{'t', 'e', 's', 't', (byte) 0xC2, (byte) 0xC2}, "utf-8")); + assertThat(invalidUtf8Sequence).isEqualTo("testÂÂ"); + } + private String extractRequestBodyJson(SpanImpl span) { JsonNode spanJson = readJsonString(writer.toJsonString(span)); JsonNode otel = spanJson.get("otel"); + if (otel == null) { + return null; + } JsonNode attribs = otel.get("attributes"); - assertThat(attribs.get("http.request.body.content").textValue()).isEqualTo("foobar"); + JsonNode bodyContent = attribs.get("http.request.body.content"); + if (bodyContent == null) { + return null; + } + return bodyContent.textValue(); + } + + private SpanImpl createSpanWithRequestBody(@Nullable byte[] bodyBytes, @Nullable String charset) { + SpanImpl span = new SpanImpl(tracer); + BodyCaptureImpl bodyCapture = span.getContext().getHttp().getRequestBody(); + bodyCapture.markEligibleForCapturing(); + bodyCapture.markPreconditionsPassed(charset, WebConfiguration.MAX_BODY_CAPTURE_BYTES); + bodyCapture.startCapture(); + + if (bodyBytes != null) { + bodyCapture.append(bodyBytes, 0, bodyBytes.length); + } + return span; } public static boolean[][] getContentCombinations() { diff --git a/apm-agent-lambda-layer/pom.xml b/apm-agent-lambda-layer/pom.xml index efd720b954..64f28e3f23 100644 --- a/apm-agent-lambda-layer/pom.xml +++ b/apm-agent-lambda-layer/pom.xml @@ -3,7 +3,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugin-sdk/pom.xml b/apm-agent-plugin-sdk/pom.xml index 7217eddb6f..d3e44b9f5b 100644 --- a/apm-agent-plugin-sdk/pom.xml +++ b/apm-agent-plugin-sdk/pom.xml @@ -3,7 +3,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/ClassNameParser.java b/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/ClassNameParser.java new file mode 100644 index 0000000000..d8c327d0ed --- /dev/null +++ b/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/ClassNameParser.java @@ -0,0 +1,102 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.sdk.bytebuddy; + +/** + * Utility class to extract the correct simple class name + */ +public class ClassNameParser { + /** + * Utility class, do not instantiate + */ + private ClassNameParser() {} + /** + * returns true if the string only contains digits + * @param str the string to check + * @return + */ + private static boolean isNumeric(String str) { + for(int i = 0; i< str.length(); i++) { + if(!Character.isDigit(str.charAt(i))) { + return false; + } + } + return true; + } + /** + * this method parses the anonymous class name from the full class name + * @param className + * @return + */ + private static String getAnonymousClassName(String className) { + int lastDollarIndex = className.lastIndexOf('$'); + int currentDollarIndex; + String nestedClassName; + // we do not want to show just a number for anonymous classes, so we walk back until we find a named (normal or nested) class + do { + currentDollarIndex = className.lastIndexOf('$', lastDollarIndex -1); + nestedClassName = className.substring(currentDollarIndex + 1, lastDollarIndex); + lastDollarIndex = currentDollarIndex; + } while(currentDollarIndex != -1 && isNumeric(nestedClassName)); + return className.substring(currentDollarIndex + 1); + } + /** + * Parses the class name from nested and anonymous classes (containing a $ sign) + *

+ * Note: We cannot know if the $ sign is part of the class name or denotes a nested/anonymous class. As $ signs should not be used in class names anyway, + * we handle them always as a class separator + *

+ * @param className + * @return + */ + private static String getNestedClassName(String className) { + int dollarIndex = className.lastIndexOf('$'); + String innerClassName = className.substring(dollarIndex + 1); + if(isNumeric(innerClassName)) { + // this is an anonymous inner class + className = getAnonymousClassName(className); + } else { + className = innerClassName; + } + return className; + } + /** + * Parses the simple class name from a class name + * @param className + * @return + */ + public static String parse(String className) { + //remove package name + className = className.substring(className.lastIndexOf('.') + 1); + if(className.contains("$")) { + if(className.endsWith("$")) { + // this can happen if the source code was in Scala and the object keyword was used + // https://www.toptal.com/scala/scala-bytecode-and-the-jvm + className = className.substring(0, className.length() - 1); + if(className.contains("$")) + { + className = getNestedClassName(className); + } + } else { + className = getNestedClassName(className); + } + } + return className; + } +} diff --git a/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/CustomElementMatchers.java b/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/CustomElementMatchers.java index f7af74d8a0..c2a07a593d 100644 --- a/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/CustomElementMatchers.java +++ b/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/CustomElementMatchers.java @@ -19,9 +19,9 @@ package co.elastic.apm.agent.sdk.bytebuddy; import co.elastic.apm.agent.sdk.internal.InternalUtil; +import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; -import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils; import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap; import net.bytebuddy.description.NamedElement; @@ -31,6 +31,7 @@ import javax.annotation.Nullable; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.JarURLConnection; import java.net.URISyntaxException; import java.net.URL; @@ -38,13 +39,13 @@ import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.Collection; +import java.util.Properties; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; +import java.util.zip.ZipEntry; -import static net.bytebuddy.matcher.ElementMatchers.nameContains; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; -import static net.bytebuddy.matcher.ElementMatchers.none; +import static net.bytebuddy.matcher.ElementMatchers.*; public class CustomElementMatchers { @@ -183,15 +184,23 @@ public static ElementMatcher.Junction isProxy() { * @param version the version to check against * @return an LTE SemVer matcher */ - public static ElementMatcher.Junction implementationVersionLte(final String version) { - return implementationVersion(version, Matcher.LTE); + public static ElementMatcher.Junction implementationVersionLte(String version) { + return implementationVersion(version, Matcher.LTE, null, null); } - public static ElementMatcher.Junction implementationVersionGte(final String version) { - return implementationVersion(version, Matcher.GTE); + public static ElementMatcher.Junction implementationVersionGte(String version) { + return implementationVersion(version, Matcher.GTE, null, null); } - private static ElementMatcher.Junction implementationVersion(final String version, final Matcher matcher) { + public static ElementMatcher.Junction implementationVersionLte(String version, String groupId, String artifactId) { + return implementationVersion(version, Matcher.LTE, groupId, artifactId); + } + + public static ElementMatcher.Junction implementationVersionGte(String version, String groupId, String artifactId) { + return implementationVersion(version, Matcher.GTE, groupId, artifactId); + } + + private static ElementMatcher.Junction implementationVersion(final String version, final Matcher matcher, @Nullable final String mavenGroupId, @Nullable final String mavenArtifactId) { return new ElementMatcher.Junction.AbstractBase() { /** * Returns true if the implementation version read from the manifest file referenced by the given @@ -205,7 +214,7 @@ private static ElementMatcher.Junction implementationVersion(f @Override public boolean matches(@Nullable ProtectionDomain protectionDomain) { try { - Version pdVersion = readImplementationVersionFromManifest(protectionDomain); + Version pdVersion = readImplementationVersion(protectionDomain, mavenGroupId, mavenArtifactId); if (pdVersion != null) { Version limitVersion = Version.of(version); return matcher.match(pdVersion, limitVersion); @@ -221,22 +230,51 @@ public boolean matches(@Nullable ProtectionDomain protectionDomain) { } @Nullable - private static Version readImplementationVersionFromManifest(@Nullable ProtectionDomain protectionDomain) throws IOException, URISyntaxException { + private static Version readImplementationVersion(@Nullable ProtectionDomain protectionDomain, @Nullable String mavenGroupId, @Nullable String mavenArtifactId) throws IOException, URISyntaxException { Version version = null; JarFile jarFile = null; + + if (protectionDomain == null) { + logger.info("Cannot read implementation version - got null ProtectionDomain"); + return null; + } + try { - if (protectionDomain != null) { - CodeSource codeSource = protectionDomain.getCodeSource(); - if (codeSource != null) { - URL jarUrl = codeSource.getLocation(); - if (jarUrl != null) { - // does not yet establish an actual connection - URLConnection urlConnection = jarUrl.openConnection(); - if (urlConnection instanceof JarURLConnection) { - jarFile = ((JarURLConnection) urlConnection).getJarFile(); - } else { - jarFile = new JarFile(new File(jarUrl.toURI())); + CodeSource codeSource = protectionDomain.getCodeSource(); + if (codeSource != null) { + URL jarUrl = codeSource.getLocation(); + if (jarUrl != null) { + // does not yet establish an actual connection + URLConnection urlConnection = jarUrl.openConnection(); + if (urlConnection instanceof JarURLConnection) { + jarFile = ((JarURLConnection) urlConnection).getJarFile(); + } else { + jarFile = new JarFile(new File(jarUrl.toURI())); + } + + // read maven properties first as they have higher priority over manifest + // this is mostly for shaded libraries in "fat-jar" applications + if (mavenGroupId != null && mavenArtifactId != null) { + ZipEntry zipEntry = jarFile.getEntry(String.format("META-INF/maven/%s/%s/pom.properties", mavenGroupId, mavenArtifactId)); + if (zipEntry != null) { + Properties properties = new Properties(); + try (InputStream input = jarFile.getInputStream(zipEntry)) { + properties.load(input); + } + if (mavenGroupId.equals(properties.getProperty("groupId")) && mavenArtifactId.equals(properties.getProperty("artifactId"))) { + String stringVersion = properties.getProperty("version"); + if (stringVersion != null) { + version = Version.of(stringVersion); + } + } } + } + + // reading manifest if library packaged as a jar + // + // doing this after maven properties is important as it might report executable jar version + // when application is packaged as a "fat jar" + if (version == null) { Manifest manifest = jarFile.getManifest(); if (manifest != null) { Attributes attributes = manifest.getMainAttributes(); @@ -248,12 +286,9 @@ private static Version readImplementationVersionFromManifest(@Nullable Protectio if (manifestVersion != null) { version = Version.of(manifestVersion); } - } } } - } else { - logger.info("Cannot read implementation version - got null ProtectionDomain"); } } finally { if (jarFile != null) { diff --git a/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/SimpleMethodSignatureOffsetMappingFactory.java b/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/SimpleMethodSignatureOffsetMappingFactory.java index d0bc841cd6..3df8507ea1 100644 --- a/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/SimpleMethodSignatureOffsetMappingFactory.java +++ b/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/bytebuddy/SimpleMethodSignatureOffsetMappingFactory.java @@ -51,8 +51,7 @@ public Advice.OffsetMapping make(ParameterDescription.InDefinedShape target, public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler, Sort sort) { final String className = instrumentedMethod.getDeclaringType().getTypeName(); - String simpleClassName = className.substring(className.lastIndexOf('$') + 1); - simpleClassName = simpleClassName.substring(simpleClassName.lastIndexOf('.') + 1); + final String simpleClassName = ClassNameParser.parse(className); final String signature = String.format("%s#%s", simpleClassName, instrumentedMethod.getName()); return Target.ForStackManipulation.of(signature); } diff --git a/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/internal/util/IOUtils.java b/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/internal/util/IOUtils.java index d432a08901..ac94df308e 100644 --- a/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/internal/util/IOUtils.java +++ b/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/internal/util/IOUtils.java @@ -23,32 +23,78 @@ import co.elastic.apm.agent.sdk.internal.pooling.ObjectPool; import co.elastic.apm.agent.sdk.internal.pooling.ObjectPooling; +import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.CharBuffer; +import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; +import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Collections; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; public class IOUtils { protected static final int BYTE_BUFFER_CAPACITY = 2048; - private static class DecoderWithBuffer { - final ByteBuffer byteBuffer = java.nio.ByteBuffer.allocate(BYTE_BUFFER_CAPACITY); - final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); - } - - private static final ObjectPool> POOL = ObjectPooling.createWithDefaultFactory(new Callable() { + private static final ObjectPool> BYTE_BUFFER_POOL = ObjectPooling.createWithDefaultFactory(new Callable() { @Override - public DecoderWithBuffer call() throws Exception { - return new DecoderWithBuffer(); + public ByteBuffer call() throws Exception { + return ByteBuffer.allocate(BYTE_BUFFER_CAPACITY); } }); + private static final Set UNSUPPORTED_CHARSETS = Collections.newSetFromMap(new ConcurrentHashMap()); + private static final Map>> DECODER_POOLS + = new ConcurrentHashMap>>(); + + private static final String UTF8_CHARSET_NAME = StandardCharsets.UTF_8.name().toLowerCase(); + + @Nullable + private static ObjectHandle getPooledCharsetDecoder(String charsetName) { + if (!isLowerCase(charsetName)) { + charsetName = charsetName.toLowerCase(); + } + ObjectPool> decoderPool = DECODER_POOLS.get(charsetName); + if (decoderPool != null) { + return decoderPool.createInstance(); + } + if (UNSUPPORTED_CHARSETS.contains(charsetName)) { + return null; + } + try { + final Charset charset = Charset.forName(charsetName); + decoderPool = ObjectPooling.createWithDefaultFactory(new Callable() { + @Override + public CharsetDecoder call() throws Exception { + return charset.newDecoder(); + } + }); + DECODER_POOLS.put(charsetName, decoderPool); + return decoderPool.createInstance(); + } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { + UNSUPPORTED_CHARSETS.add(charsetName); + return null; + } + } + + private static boolean isLowerCase(String charsetName) { + for (int i = 0; i < charsetName.length(); i++) { + if (!Character.isLowerCase(charsetName.charAt(i))) { + return false; + } + } + return true; + } + /** * Reads the provided {@link InputStream} into the {@link CharBuffer} without causing allocations. @@ -67,9 +113,12 @@ public DecoderWithBuffer call() throws Exception { */ public static boolean readUtf8Stream(final InputStream is, final CharBuffer charBuffer) throws IOException { // to be compatible with Java 8, we have to cast to buffer because of different return types - try (ObjectHandle pooled = POOL.createInstance()) { - final ByteBuffer buffer = pooled.get().byteBuffer; - final CharsetDecoder charsetDecoder = pooled.get().decoder; + try ( + ObjectHandle bufferHandle = BYTE_BUFFER_POOL.createInstance(); + ObjectHandle decoderHandle = getPooledCharsetDecoder(UTF8_CHARSET_NAME); + ) { + final ByteBuffer buffer = bufferHandle.get(); + final CharsetDecoder charsetDecoder = decoderHandle.get(); try { final byte[] bufferArray = buffer.array(); for (int read = is.read(bufferArray); read != -1; read = is.read(bufferArray)) { @@ -143,9 +192,11 @@ public static CoderResult decodeUtf8Bytes(final byte[] bytes, final CharBuffer c * @return a {@link CoderResult}, indicating the success or failure of the decoding */ public static CoderResult decodeUtf8Bytes(final byte[] bytes, final int offset, final int length, final CharBuffer charBuffer) { - try (ObjectHandle pooled = POOL.createInstance()) { - final ByteBuffer pooledBuffer = pooled.get().byteBuffer; - final CharsetDecoder charsetDecoder = pooled.get().decoder; + try ( + ObjectHandle bufferHandle = BYTE_BUFFER_POOL.createInstance(); + ObjectHandle decoderHandle = getPooledCharsetDecoder(UTF8_CHARSET_NAME); + ) { + final ByteBuffer pooledBuffer = bufferHandle.get(); // to be compatible with Java 8, we have to cast to buffer because of different return types final ByteBuffer buffer; if (pooledBuffer.capacity() < length) { @@ -157,7 +208,7 @@ public static CoderResult decodeUtf8Bytes(final byte[] bytes, final int offset, ((Buffer) buffer).position(0); ((Buffer) buffer).limit(length); } - return decode(charBuffer, buffer, charsetDecoder); + return decode(charBuffer, buffer, decoderHandle.get()); } } @@ -183,21 +234,25 @@ public static CoderResult decodeUtf8Bytes(final byte[] bytes, final int offset, */ public static CoderResult decodeUtf8Byte(final byte b, final CharBuffer charBuffer) { // to be compatible with Java 8, we have to cast to buffer because of different return types - try (ObjectHandle pooled = POOL.createInstance()) { - final ByteBuffer buffer = pooled.get().byteBuffer; - final CharsetDecoder charsetDecoder = pooled.get().decoder; + try ( + ObjectHandle bufferHandle = BYTE_BUFFER_POOL.createInstance(); + ObjectHandle decoderHandle = getPooledCharsetDecoder(UTF8_CHARSET_NAME); + ) { + final ByteBuffer buffer = bufferHandle.get(); buffer.put(b); ((Buffer) buffer).position(0); ((Buffer) buffer).limit(1); - return decode(charBuffer, buffer, charsetDecoder); + return decode(charBuffer, buffer, decoderHandle.get()); } } public static CoderResult decodeUtf8BytesFromSource(ByteSourceReader reader, T src, final CharBuffer dest) { // to be compatible with Java 8, we have to cast to buffer because of different return types - try (ObjectHandle pooled = POOL.createInstance()) { - final ByteBuffer buffer = pooled.get().byteBuffer; - final CharsetDecoder charsetDecoder = pooled.get().decoder; + try ( + ObjectHandle bufferHandle = BYTE_BUFFER_POOL.createInstance(); + ObjectHandle decoderHandle = getPooledCharsetDecoder(UTF8_CHARSET_NAME); + ) { + final ByteBuffer buffer = bufferHandle.get(); int readableBytes = reader.availableBytes(src); CoderResult result = null; while (readableBytes > 0) { @@ -206,7 +261,7 @@ public static CoderResult decodeUtf8BytesFromSource(ByteSourceReader read ((Buffer) buffer).position(0); reader.readInto(src, buffer); ((Buffer) buffer).position(0); - result = decode(dest, buffer, charsetDecoder); + result = decode(dest, buffer, decoderHandle.get()); if (result.isError() || result.isOverflow()) { return result; } @@ -217,12 +272,45 @@ public static CoderResult decodeUtf8BytesFromSource(ByteSourceReader read } } + @Nullable + public static byte[] copyToByteArray(@Nullable ByteBuffer buf) { + if (buf == null) { + return null; + } + byte[] data = new byte[buf.position()]; + buf.position(0); + buf.get(data); + return data; + } + public interface ByteSourceReader { int availableBytes(S source); void readInto(S source, ByteBuffer into); } + /** + * @param input the byte data to decode + * @param output the buffer to decode into + * @param charsetName the name of the charset + * @return null, if the charset is not known/supported. Otherwise the result of the decoding operation. + */ + @Nullable + public static CoderResult decode(ByteBuffer input, CharBuffer output, String charsetName) { + try (ObjectHandle decoderHandle = getPooledCharsetDecoder(charsetName)) { + if (decoderHandle == null) { + return null; //charset is unsupported + } + CharsetDecoder charsetDecoder = decoderHandle.get(); + try { + final CoderResult coderResult = charsetDecoder.decode(input, output, true); + charsetDecoder.flush(output); + return coderResult; + } finally { + charsetDecoder.reset(); + } + } + } private static CoderResult decode(CharBuffer charBuffer, ByteBuffer buffer, CharsetDecoder charsetDecoder) { try { diff --git a/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/state/CallDepth.java b/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/state/CallDepth.java index 267faca882..72e8129253 100644 --- a/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/state/CallDepth.java +++ b/apm-agent-plugin-sdk/src/main/java/co/elastic/apm/agent/sdk/state/CallDepth.java @@ -108,7 +108,7 @@ public boolean isNestedCallAndDecrement() { return decrement() != 0; } - private int get() { + public int get() { Integer callDepthForCurrentThread = callDepthPerThread.get(); if (callDepthForCurrentThread == null) { callDepthForCurrentThread = 0; diff --git a/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/ClassNameParserTest.java b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/ClassNameParserTest.java new file mode 100644 index 0000000000..bc76d3dd18 --- /dev/null +++ b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/ClassNameParserTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.sdk.bytebuddy; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import co.elastic.apm.agent.sdk.bytebuddy.clazzes.ParentClass.InnerClass; +import co.elastic.apm.agent.sdk.bytebuddy.clazzes.ParentClass.InnerClass.NestedInnerClass; +import co.elastic.apm.agent.sdk.bytebuddy.clazzes.ParentObject.ChildScalaObject$; +import co.elastic.apm.agent.sdk.bytebuddy.clazzes.SimpleClass; +import co.elastic.apm.agent.sdk.bytebuddy.clazzes.AnonymousClass; +import co.elastic.apm.agent.sdk.bytebuddy.clazzes.AnonymousNestedClass; +import co.elastic.apm.agent.sdk.bytebuddy.clazzes.Dollar$Class; +import co.elastic.apm.agent.sdk.bytebuddy.clazzes.ScalaObject$; + +class ClassNameParserTest { + + @Test + void testSimpleClassName() { + String className = ClassNameParser.parse(SimpleClass.class.getName()); + assertThat(className).isEqualTo("SimpleClass"); + } + @Test + void testNestedClassName() { + String className = ClassNameParser.parse(InnerClass.class.getName()); + assertThat(className).isEqualTo("InnerClass"); + } + @Test + void testDoubleNestedClassName() { + String className = ClassNameParser.parse(NestedInnerClass.class.getName()); + assertThat(className).isEqualTo("NestedInnerClass"); + } + @Test + void testDollarClassName() { + String className = ClassNameParser.parse(Dollar$Class.class.getName()); + //We have no way to know if the class name is Dollar$Class or Class is a nested class of Dollar + //As dollar signs should not be used in names, it should be fine to detect Class instead of Dollar$Class + assertThat(className).describedAs("Dollar signs should not be used in names").isEqualTo("Class"); + } + @Test + void testAnonymousClassName() { + String className = ClassNameParser.parse(AnonymousClass.getAnonymousClass().getName()); + assertThat(className).isEqualTo("AnonymousClass$1"); + } + @Test + void testAnonymousNestedClassName() { + String className = ClassNameParser.parse(InnerClass.getAnonymousClass().getName()); + assertThat(className).isEqualTo("InnerClass$1"); + } + @Test + void testAnonymousInAnonymousClassName() { + String className = ClassNameParser.parse(AnonymousNestedClass.getAnonymousClass().getName()); + assertThat(className).isEqualTo("AnonymousNestedClass$1$1"); + } + @Test + void testScalaObjectClassName() { + String className = ClassNameParser.parse(ScalaObject$.class.getName()); + assertThat(className).isEqualTo("ScalaObject"); + } + @Test + void testNestedScalaObjectClassName() { + String className = ClassNameParser.parse(ChildScalaObject$.class.getName()); + assertThat(className).isEqualTo("ChildScalaObject"); + } +} diff --git a/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/CustomElementMatchersTest.java b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/CustomElementMatchersTest.java index 8e6bc0ab5b..20398a47bd 100644 --- a/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/CustomElementMatchersTest.java +++ b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/CustomElementMatchersTest.java @@ -18,22 +18,36 @@ */ package co.elastic.apm.agent.sdk.bytebuddy; -import org.apache.http.client.HttpClient; - import net.bytebuddy.description.type.TypeDescription; +import org.apache.http.client.HttpClient; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.File; +import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.CodeSigner; import java.security.CodeSource; import java.security.ProtectionDomain; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.*; +import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; +import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.implementationVersionGte; +import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.implementationVersionLte; +import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.isAgentClassLoader; +import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.isInAnyPackage; +import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.isInternalPluginClassLoader; import static net.bytebuddy.matcher.ElementMatchers.none; import static org.assertj.core.api.Assertions.assertThat; @@ -63,6 +77,38 @@ void testSemVerLteWithEncodedFileUrl() throws MalformedURLException, URISyntaxEx assertThat(implementationVersionLte("2.1.9").matches(protectionDomain)).isTrue(); } + @Test + void testSemVerFallbackOnMavenProperties(@TempDir Path tempDir) throws URISyntaxException, IOException { + // Relying on Apache httpclient-4.5.6.jar + // creates a copy of the jar without the manifest so we should parse maven properties + URL originalUrl = HttpClient.class.getProtectionDomain().getCodeSource().getLocation(); + Path modifiedJar = tempDir.resolve("test.jar"); + try { + Files.copy(Paths.get(originalUrl.toURI()), modifiedJar); + + Map fsProperties = new HashMap<>(); + fsProperties.put("create", "false"); + + URI fsUri = URI.create("jar:" + modifiedJar.toUri()); + try (FileSystem zipFs = FileSystems.newFileSystem(fsUri, fsProperties)) { + Files.delete(zipFs.getPath("META-INF", "MANIFEST.MF")); + } + + ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(modifiedJar.toUri().toURL(), new CodeSigner[0]), null); + + assertThat(implementationVersionLte("4.5.5", "org.apache.httpcomponents", "httpclient").matches(protectionDomain)).isFalse(); + assertThat(implementationVersionLte("4.5.6", "org.apache.httpcomponents", "httpclient").matches(protectionDomain)).isTrue(); + assertThat(implementationVersionGte("4.5.7", "org.apache.httpcomponents", "httpclient").matches(protectionDomain)).isFalse(); + + + } finally { + if (Files.exists(modifiedJar)) { + Files.delete(modifiedJar); + } + } + + } + private void testSemVerLteMatcher(ProtectionDomain protectionDomain) { assertThat(implementationVersionLte("3").matches(protectionDomain)).isFalse(); assertThat(implementationVersionLte("3.2").matches(protectionDomain)).isFalse(); diff --git a/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/AnonymousClass.java b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/AnonymousClass.java new file mode 100644 index 0000000000..ec1234c2c7 --- /dev/null +++ b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/AnonymousClass.java @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.sdk.bytebuddy.clazzes; + +public class AnonymousClass { + public static Class getAnonymousClass() { + return new Object() { + @Override + public String toString() { + return "foo"; + } + }.getClass(); + } +} diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2_17_1ErrorCapturingTest.java b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/AnonymousNestedClass.java similarity index 51% rename from apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2_17_1ErrorCapturingTest.java rename to apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/AnonymousNestedClass.java index 06415b3b49..14c01ce0cb 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2_17_1ErrorCapturingTest.java +++ b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/AnonymousNestedClass.java @@ -16,27 +16,24 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.log4j2.error; +package co.elastic.apm.agent.sdk.bytebuddy.clazzes; -import co.elastic.apm.agent.testutils.JUnit4TestClassWithDependencyRunner; -import org.junit.jupiter.api.Test; - -import java.util.List; - -public class Log4j2_17_1ErrorCapturingTest { - private final JUnit4TestClassWithDependencyRunner runner; - - public Log4j2_17_1ErrorCapturingTest() throws Exception { - List dependencies = List.of( - "org.apache.logging.log4j:log4j-core:2.17.1", - "org.apache.logging.log4j:log4j-api:2.17.1" - ); - runner = new JUnit4TestClassWithDependencyRunner(dependencies, Log4j2ErrorCapturingTestVersions.class, - Log4j2LoggerErrorCapturingInstrumentationTest.class); +public class AnonymousNestedClass { + public static Class getAnonymousClass() { + Factory f = new Factory() { + @Override + public Object getObject() { + return new Object() { + @Override + public String toString() { + return "foo"; + } + }; + } + }; + return f.getObject().getClass(); } - - @Test - public void testVersions() { - runner.run(); + public static interface Factory { + Object getObject(); } } diff --git a/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/Dollar$Class.java b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/Dollar$Class.java new file mode 100644 index 0000000000..08aa4c7e3d --- /dev/null +++ b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/Dollar$Class.java @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.sdk.bytebuddy.clazzes; + +public class Dollar$Class { + +} diff --git a/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/ParentClass.java b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/ParentClass.java new file mode 100644 index 0000000000..99799af8bf --- /dev/null +++ b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/ParentClass.java @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.sdk.bytebuddy.clazzes; + +public class ParentClass { + + public static class InnerClass { + public static Class getAnonymousClass() { + return new Object() { + @Override + public String toString() { + return "foo"; + } + }.getClass(); + } + public static class NestedInnerClass { + + } + } +} diff --git a/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/ParentObject.java b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/ParentObject.java new file mode 100644 index 0000000000..3a73eeb973 --- /dev/null +++ b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/ParentObject.java @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.sdk.bytebuddy.clazzes; + +public class ParentObject { + /** + * Scala objects generate a class with a dollar at the end + * https://www.toptal.com/scala/scala-bytecode-and-the-jvm + */ + public class ChildScalaObject$ { + + } +} diff --git a/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/ScalaObject$.java b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/ScalaObject$.java new file mode 100644 index 0000000000..c20f336742 --- /dev/null +++ b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/ScalaObject$.java @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.sdk.bytebuddy.clazzes; + +/** + * Scala objects generate a class with a dollar at the end + * https://www.toptal.com/scala/scala-bytecode-and-the-jvm + */ +public class ScalaObject$ { + +} diff --git a/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/SimpleClass.java b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/SimpleClass.java new file mode 100644 index 0000000000..e0153bffa8 --- /dev/null +++ b/apm-agent-plugin-sdk/src/test/java/co/elastic/apm/agent/sdk/bytebuddy/clazzes/SimpleClass.java @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.sdk.bytebuddy.clazzes; + +public class SimpleClass { + +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/pom.xml b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/pom.xml index 6439fadad8..283199152c 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/pom.xml +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/pom.xml @@ -5,7 +5,7 @@ apm-apache-httpclient co.elastic.apm - 1.51.0 + 1.52.0 apm-apache-httpclient-common diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java index 1998180c3b..10e2d2727b 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java @@ -20,9 +20,9 @@ import co.elastic.apm.agent.httpclient.HttpClientHelper; -import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.dispatch.TextHeaderGetter; import co.elastic.apm.agent.tracer.dispatch.TextHeaderSetter; @@ -34,11 +34,11 @@ public abstract class AbstractApacheHttpClientAdvice { public static & - TextHeaderGetter> Span startSpan(final Tracer tracer, - final ApacheHttpClientApiAdapter adapter, - final WRAPPER request, - @Nullable final HTTPHOST httpHost, - final HeaderAccessor headerAccessor) throws URISyntaxException { + TextHeaderGetter, HTTPENTITY> Span startSpan(final Tracer tracer, + final ApacheHttpClientApiAdapter adapter, + final WRAPPER request, + @Nullable final HTTPHOST httpHost, + final HeaderAccessor headerAccessor) throws URISyntaxException { TraceState traceState = tracer.currentContext(); Span span = null; if (traceState.getSpan() != null) { @@ -51,10 +51,11 @@ TextHeaderGetter> Span startSpan(final Tracer tracer, return span; } - public static void endSpan(ApacheHttpClientApiAdapter adapter, - Object spanObj, - Throwable t, - RESPONSE response) { + public static + void endSpan(ApacheHttpClientApiAdapter adapter, + Object spanObj, + Throwable t, + RESPONSE response) { Span span = (Span) spanObj; if (span == null) { return; diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpRequestBodyCaptureAdvice.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpRequestBodyCaptureAdvice.java new file mode 100644 index 0000000000..b96bae4582 --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpRequestBodyCaptureAdvice.java @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.httpclient.common; + + +import co.elastic.apm.agent.httpclient.RequestBodyRecordingInputStream; +import co.elastic.apm.agent.httpclient.RequestBodyRecordingOutputStream; +import co.elastic.apm.agent.sdk.logging.Logger; +import co.elastic.apm.agent.sdk.logging.LoggerFactory; +import co.elastic.apm.agent.tracer.Span; + +import java.io.InputStream; +import java.io.OutputStream; + +public abstract class AbstractApacheHttpRequestBodyCaptureAdvice { + + private static final Logger logger = LoggerFactory.getLogger(AbstractApacheHttpRequestBodyCaptureAdvice.class); + + public static InputStream maybeCaptureRequestBodyInputStream(HTTPENTITY thiz, InputStream requestBody) { + Span clientSpan = RequestBodyCaptureRegistry.removeSpanFor(thiz); + if (clientSpan != null) { + logger.debug("Wrapping input stream for request body capture for HttpEntity {} ({}) for span {}", thiz.getClass().getName(), System.identityHashCode(thiz), clientSpan); + return new RequestBodyRecordingInputStream(requestBody, clientSpan); + } + return requestBody; + } + + public static OutputStream maybeCaptureRequestBodyOutputStream(HTTPENTITY thiz, OutputStream requestBody) { + Span clientSpan = RequestBodyCaptureRegistry.removeSpanFor(thiz); + if (clientSpan != null) { + logger.debug("Wrapping output stream for request body capture for HttpEntity {} ({}) for span {}", thiz.getClass().getName(), System.identityHashCode(thiz), clientSpan); + return new RequestBodyRecordingOutputStream(requestBody, clientSpan); + } + return requestBody; + } + + public static void releaseRequestBodyOutputStream(OutputStream maybeWrapped) { + if (maybeWrapped instanceof RequestBodyRecordingOutputStream) { + ((RequestBodyRecordingOutputStream) maybeWrapped).releaseSpan(); + } + } +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java index a3834035cd..ad36f5665c 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java @@ -23,7 +23,7 @@ import java.net.URI; import java.net.URISyntaxException; -public interface ApacheHttpClientApiAdapter { +public interface ApacheHttpClientApiAdapter extends ApacheHttpClientEntityAccessor { String getMethod(WRAPPER request); URI getUri(WRAPPER request) throws URISyntaxException; diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientEntityAccessor.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientEntityAccessor.java new file mode 100644 index 0000000000..0f1abfc3b6 --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientEntityAccessor.java @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.httpclient.common; + +import javax.annotation.Nullable; + +public interface ApacheHttpClientEntityAccessor { + @Nullable + HTTPENTITY getRequestEntity(REQUEST request); + + @Nullable + byte[] getSimpleBodyBytes(REQUEST request); +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/RequestBodyCaptureRegistry.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/RequestBodyCaptureRegistry.java similarity index 60% rename from apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/RequestBodyCaptureRegistry.java rename to apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/RequestBodyCaptureRegistry.java index 009cc1fe9b..052626b80a 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/RequestBodyCaptureRegistry.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/RequestBodyCaptureRegistry.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.httpclient.v4.helper; +package co.elastic.apm.agent.httpclient.common; import co.elastic.apm.agent.httpclient.HttpClientHelper; import co.elastic.apm.agent.sdk.logging.Logger; @@ -25,10 +25,8 @@ import co.elastic.apm.agent.tracer.AbstractSpan; import co.elastic.apm.agent.tracer.GlobalTracer; import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.dispatch.TextHeaderGetter; import co.elastic.apm.agent.tracer.reference.ReferenceCountedMap; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpRequest; import javax.annotation.Nullable; @@ -52,25 +50,33 @@ public static Span removeSpanFor(Object entity) { } - public static void potentiallyCaptureRequestBody(HttpRequest request, @Nullable AbstractSpan span) { - if (HttpClientHelper.startRequestBodyCapture(span, request, RequestHeaderAccessor.INSTANCE)) { - if (request instanceof HttpEntityEnclosingRequest) { - HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); - if (entity != null) { - logger.debug("Enabling request capture for entity {}() for span {}", entity.getClass().getName(), System.identityHashCode(entity), span); - MapHolder.captureBodyFor(entity, (Span) span); + public static void potentiallyCaptureRequestBody( + REQUEST request, + @Nullable AbstractSpan abstractSpan, + ApacheHttpClientEntityAccessor adapter, + TextHeaderGetter headerGetter + ) { + if (HttpClientHelper.checkAndStartRequestBodyCapture(abstractSpan, request, headerGetter)) { + Span span = (Span) abstractSpan; + byte[] simpleBytes = adapter.getSimpleBodyBytes(request); + if (simpleBytes != null) { + logger.debug("Captured simple request body for span {}", abstractSpan); + span.getContext().getHttp().getRequestBody().append(simpleBytes, 0, simpleBytes.length); + } else { + HTTPENTITY httpEntity = adapter.getRequestEntity(request); + if (httpEntity != null) { + logger.debug("Enabling request capture for entity {}() for span {}", httpEntity.getClass().getName(), System.identityHashCode(httpEntity), abstractSpan); + MapHolder.captureBodyFor(httpEntity, span); } else { - logger.debug("HttpEntity is null for span {}", span); + logger.debug("Not capturing request body because HttpEntity is null for span {}", abstractSpan); } - } else { - logger.debug("Not capturing request body because {} is not an HttpEntityEnclosingRequest", request.getClass().getName()); } } } @Nullable - public static Span removeSpanFor(HttpEntity entity) { - return MapHolder.removeSpanFor(entity); + public static Span removeSpanFor(Object httpEntity) { + return MapHolder.removeSpanFor(httpEntity); } } diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient3-plugin/pom.xml b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient3-plugin/pom.xml index ff116aeeb6..387a8e075e 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient3-plugin/pom.xml +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient3-plugin/pom.xml @@ -5,7 +5,7 @@ apm-apache-httpclient co.elastic.apm - 1.51.0 + 1.52.0 apm-apache-httpclient3-plugin diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/pom.xml b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/pom.xml index 53336fe3ab..24049343f6 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/pom.xml +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/pom.xml @@ -5,7 +5,7 @@ apm-apache-httpclient co.elastic.apm - 1.51.0 + 1.52.0 apm-apache-httpclient4-plugin diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpClientInstrumentation.java index 2bc725c95b..02033d2c98 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpClientInstrumentation.java @@ -19,8 +19,8 @@ package co.elastic.apm.agent.httpclient.v4; import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpClientAdvice; +import co.elastic.apm.agent.httpclient.common.RequestBodyCaptureRegistry; import co.elastic.apm.agent.httpclient.v4.helper.ApacheHttpClient4ApiAdapter; -import co.elastic.apm.agent.httpclient.v4.helper.RequestBodyCaptureRegistry; import co.elastic.apm.agent.httpclient.v4.helper.RequestHeaderAccessor; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; @@ -60,7 +60,7 @@ public static class ApacheHttpClient4Advice extends AbstractApacheHttpClientAdvi public static Object onBeforeExecute(@Advice.Argument(0) HttpRoute route, @Advice.Argument(1) HttpRequestWrapper request) throws URISyntaxException { Span span = startSpan(tracer, adapter, request, route.getTargetHost(), RequestHeaderAccessor.INSTANCE); - RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, tracer.getActive()); + RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, tracer.getActive(), adapter, RequestHeaderAccessor.INSTANCE); return span; } diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityGetContentInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityGetContentInstrumentation.java index b08ea36da9..ceae3061c2 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityGetContentInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityGetContentInstrumentation.java @@ -18,49 +18,25 @@ */ package co.elastic.apm.agent.httpclient.v4; -import co.elastic.apm.agent.httpclient.RequestBodyRecordingInputStream; -import co.elastic.apm.agent.httpclient.RequestBodyRecordingOutputStream; -import co.elastic.apm.agent.httpclient.v4.helper.RequestBodyCaptureRegistry; -import co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; -import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpRequestBodyCaptureAdvice; import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import org.apache.http.HttpEntity; import java.io.InputStream; -import java.io.OutputStream; -import java.net.URISyntaxException; -import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; -import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; -import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader; -import static net.bytebuddy.matcher.ElementMatchers.nameContains; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.not; -import static net.bytebuddy.matcher.ElementMatchers.returns; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; -public class ApacheHttpEntityGetContentInstrumentation extends BaseApacheHttpClientInstrumentation { +public class ApacheHttpEntityGetContentInstrumentation extends BaseApacheHttpEntityInstrumentation { - public static class ApacheHttpEntityGetContentAdvice { - - private static final Logger logger = LoggerFactory.getLogger(ApacheHttpEntityGetContentAdvice.class); + public static class ApacheHttpEntityGetContentAdvice extends AbstractApacheHttpRequestBodyCaptureAdvice { @Advice.OnMethodExit(suppress = Throwable.class, inline = false) @Advice.AssignReturned.ToReturned - public static InputStream onExit(@Advice.This HttpEntity thiz, @Advice.Return InputStream content) throws URISyntaxException { - Span clientSpan = RequestBodyCaptureRegistry.removeSpanFor(thiz); - if (clientSpan != null) { - logger.debug("Wrapping input stream for request body capture for HttpEntity {} ({}) for span {}", thiz.getClass().getName(), System.identityHashCode(thiz), clientSpan); - return new RequestBodyRecordingInputStream(content, clientSpan); - } - return content; + public static InputStream onExit(@Advice.This HttpEntity thiz, @Advice.Return InputStream content) { + return maybeCaptureRequestBodyInputStream(thiz, content); } } @@ -69,22 +45,6 @@ public String getAdviceClassName() { return "co.elastic.apm.agent.httpclient.v4.ApacheHttpEntityGetContentInstrumentation$ApacheHttpEntityGetContentAdvice"; } - @Override - public ElementMatcher.Junction getClassLoaderMatcher() { - return not(isBootstrapClassLoader()) - .and(classLoaderCanLoadClass("org.apache.http.HttpEntity")); - } - - @Override - public ElementMatcher getTypeMatcherPreFilter() { - return nameStartsWith("org.apache.http").and(nameContains("Entity")); - } - - @Override - public ElementMatcher getTypeMatcher() { - return hasSuperType(named("org.apache.http.HttpEntity")); - } - @Override public ElementMatcher getMethodMatcher() { return named("getContent") diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityWriteToInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityWriteToInstrumentation.java index 0b06c8b9fc..0f6b657dfb 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityWriteToInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityWriteToInstrumentation.java @@ -18,53 +18,31 @@ */ package co.elastic.apm.agent.httpclient.v4; -import co.elastic.apm.agent.httpclient.RequestBodyRecordingOutputStream; -import co.elastic.apm.agent.httpclient.v4.helper.RequestBodyCaptureRegistry; -import co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; -import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpRequestBodyCaptureAdvice; import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import org.apache.http.HttpEntity; import java.io.OutputStream; -import java.net.URISyntaxException; -import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; -import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; -import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader; -import static net.bytebuddy.matcher.ElementMatchers.nameContains; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.not; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; -public class ApacheHttpEntityWriteToInstrumentation extends BaseApacheHttpClientInstrumentation { +public class ApacheHttpEntityWriteToInstrumentation extends BaseApacheHttpEntityInstrumentation { - public static class ApacheHttpEntityWriteToAdvice { - - private static final Logger logger = LoggerFactory.getLogger(ApacheHttpEntityWriteToAdvice.class); + public static class ApacheHttpEntityWriteToAdvice extends AbstractApacheHttpRequestBodyCaptureAdvice { @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) @Advice.AssignReturned.ToArguments(@Advice.AssignReturned.ToArguments.ToArgument(0)) - public static OutputStream onEnter(@Advice.This HttpEntity thiz, @Advice.Argument(0) OutputStream drain) throws URISyntaxException { - Span clientSpan = RequestBodyCaptureRegistry.removeSpanFor(thiz); - if (clientSpan != null) { - logger.debug("Wrapping output stream for request body capture for HttpEntity {} ({}) for span {}", thiz.getClass().getName(), System.identityHashCode(thiz), clientSpan); - return new RequestBodyRecordingOutputStream(drain, clientSpan); - } - return drain; + public static OutputStream onEnter(@Advice.This HttpEntity thiz, @Advice.Argument(0) OutputStream drain) { + return maybeCaptureRequestBodyOutputStream(thiz, drain); } @Advice.OnMethodExit(suppress = Throwable.class, inline = false) - public static void onExit(@Advice.Enter OutputStream potentiallyWrappedStream) throws URISyntaxException { - if (potentiallyWrappedStream instanceof RequestBodyRecordingOutputStream) { - ((RequestBodyRecordingOutputStream) potentiallyWrappedStream).releaseSpan(); - } + public static void onExit(@Advice.Enter OutputStream potentiallyWrappedStream) { + releaseRequestBodyOutputStream(potentiallyWrappedStream); } } @@ -73,22 +51,6 @@ public String getAdviceClassName() { return "co.elastic.apm.agent.httpclient.v4.ApacheHttpEntityWriteToInstrumentation$ApacheHttpEntityWriteToAdvice"; } - @Override - public ElementMatcher.Junction getClassLoaderMatcher() { - return not(isBootstrapClassLoader()) - .and(classLoaderCanLoadClass("org.apache.http.HttpEntity")); - } - - @Override - public ElementMatcher getTypeMatcherPreFilter() { - return nameStartsWith("org.apache.http").and(nameContains("Entity")); - } - - @Override - public ElementMatcher getTypeMatcher() { - return hasSuperType(named("org.apache.http.HttpEntity")); - } - @Override public ElementMatcher getMethodMatcher() { return named("writeTo") diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/BaseApacheHttpEntityInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/BaseApacheHttpEntityInstrumentation.java new file mode 100644 index 0000000000..64e5f17e9a --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/BaseApacheHttpEntityInstrumentation.java @@ -0,0 +1,34 @@ +package co.elastic.apm.agent.httpclient.v4; + +import net.bytebuddy.description.NamedElement; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader; +import static net.bytebuddy.matcher.ElementMatchers.nameContains; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +public abstract class BaseApacheHttpEntityInstrumentation extends BaseApacheHttpClientInstrumentation { + @Override + public ElementMatcher.Junction getClassLoaderMatcher() { + return not(isBootstrapClassLoader()) + .and(classLoaderCanLoadClass("org.apache.http.HttpEntity")); + } + + @Override + public ElementMatcher getTypeMatcherPreFilter() { + return nameContains("Entity").and( + nameStartsWith("org.apache.http") + .or(nameStartsWith("org.springframework.http")) //spring implements its own HttpEntities + ); + } + + @Override + public ElementMatcher getTypeMatcher() { + return hasSuperType(named("org.apache.http.HttpEntity")); + } +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/LegacyApacheHttpClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/LegacyApacheHttpClientInstrumentation.java index 21fe4ad545..0698839563 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/LegacyApacheHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/LegacyApacheHttpClientInstrumentation.java @@ -19,7 +19,8 @@ package co.elastic.apm.agent.httpclient.v4; import co.elastic.apm.agent.httpclient.HttpClientHelper; -import co.elastic.apm.agent.httpclient.v4.helper.RequestBodyCaptureRegistry; +import co.elastic.apm.agent.httpclient.common.RequestBodyCaptureRegistry; +import co.elastic.apm.agent.httpclient.v4.helper.ApacheHttpClient4ApiAdapter; import co.elastic.apm.agent.httpclient.v4.helper.RequestHeaderAccessor; import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.agent.tracer.Span; @@ -106,7 +107,7 @@ public static Object onBeforeExecute(@Advice.Argument(0) @Nullable HttpHost host } } - RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, tracer.getActive()); + RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, tracer.getActive(), ApacheHttpClient4ApiAdapter.get(), RequestHeaderAccessor.INSTANCE); tracer.currentContext().propagateContext(request, RequestHeaderAccessor.INSTANCE, RequestHeaderAccessor.INSTANCE); return span; } diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/ApacheHttpClient4ApiAdapter.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/ApacheHttpClient4ApiAdapter.java index fd763f23d5..7d8b834e0f 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/ApacheHttpClient4ApiAdapter.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/ApacheHttpClient4ApiAdapter.java @@ -23,6 +23,8 @@ import co.elastic.apm.agent.tracer.GlobalTracer; import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.configuration.CoreConfiguration; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.StatusLine; @@ -30,9 +32,10 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpRequestWrapper; +import javax.annotation.Nullable; import java.net.URI; -public class ApacheHttpClient4ApiAdapter implements ApacheHttpClientApiAdapter { +public class ApacheHttpClient4ApiAdapter implements ApacheHttpClientApiAdapter { private static final ApacheHttpClient4ApiAdapter INSTANCE = new ApacheHttpClient4ApiAdapter(); private final Tracer tracer = GlobalTracer.get(); @@ -59,6 +62,20 @@ public CharSequence getHostName(HttpHost httpHost, HttpRequestWrapper request) { return httpHost.getHostName(); } + @Override + public HttpEntity getRequestEntity(HttpRequest request) { + if (request instanceof HttpEntityEnclosingRequest) { + return ((HttpEntityEnclosingRequest) request).getEntity(); + } + return null; + } + + @Override + @Nullable + public byte[] getSimpleBodyBytes(HttpRequest request) { + return null; //Apache v4 client only provides body via HttpEntity + } + @Override public int getResponseCode(CloseableHttpResponse closeableHttpResponse) { final StatusLine statusLine = closeableHttpResponse.getStatusLine(); diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/HttpAsyncRequestProducerWrapper.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/HttpAsyncRequestProducerWrapper.java index a8648894ba..918fd06f07 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/HttpAsyncRequestProducerWrapper.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/HttpAsyncRequestProducerWrapper.java @@ -19,8 +19,9 @@ package co.elastic.apm.agent.httpclient.v4.helper; import co.elastic.apm.agent.httpclient.HttpClientHelper; -import co.elastic.apm.agent.tracer.TraceState; +import co.elastic.apm.agent.httpclient.common.RequestBodyCaptureRegistry; import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.pooling.Recyclable; import org.apache.http.HttpException; import org.apache.http.HttpHost; @@ -85,7 +86,7 @@ public HttpRequest generateRequest() throws IOException, HttpException { // trace context propagation if (request != null) { if (span != null) { - RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, span); + RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, span, ApacheHttpClient4ApiAdapter.get(), RequestHeaderAccessor.INSTANCE); RequestLine requestLine = request.getRequestLine(); if (requestLine != null) { String method = requestLine.getMethod(); diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/test/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpAsyncClientInstrumentationTest.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/test/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpAsyncClientInstrumentationTest.java index b2698ebc2e..54ca4dcaa6 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/test/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpAsyncClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/test/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpAsyncClientInstrumentationTest.java @@ -97,6 +97,11 @@ protected boolean isBodyCapturingSupported() { return true; } + @Override + public void testPostBodyCaptureForExistingSpan() throws Exception { + //TODO: async http client instrumentation does not support capturing bodies for existing spans yet + } + @Override protected void performPost(String path, byte[] data, String contentTypeHeader) throws Exception { final CompletableFuture responseFuture = new CompletableFuture<>(); diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/pom.xml b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/pom.xml index 4d3590271f..5bd35ad3fb 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/pom.xml +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm apm-apache-httpclient - 1.51.0 + 1.52.0 apm-apache-httpclient5-plugin diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityGetContentInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityGetContentInstrumentation.java new file mode 100644 index 0000000000..d51d5c016c --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityGetContentInstrumentation.java @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.httpclient.v5; + +import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpRequestBodyCaptureAdvice; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.hc.core5.http.HttpEntity; + +import java.io.InputStream; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class ApacheHttp5EntityGetContentInstrumentation extends BaseApacheHttp5EntityInstrumentation { + + public static class ApacheHttpEntityGetContentAdvice extends AbstractApacheHttpRequestBodyCaptureAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class, inline = false) + @Advice.AssignReturned.ToReturned + public static InputStream onExit(@Advice.This HttpEntity thiz, @Advice.Return InputStream content) { + return maybeCaptureRequestBodyInputStream(thiz, content); + } + } + + @Override + public String getAdviceClassName() { + return "co.elastic.apm.agent.httpclient.v5.ApacheHttp5EntityGetContentInstrumentation$ApacheHttpEntityGetContentAdvice"; + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("getContent") + .and(takesArguments(0)); + } + +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityWriteToInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityWriteToInstrumentation.java new file mode 100644 index 0000000000..994ac3f26f --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityWriteToInstrumentation.java @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.httpclient.v5; + +import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpRequestBodyCaptureAdvice; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.hc.core5.http.HttpEntity; + +import java.io.OutputStream; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class ApacheHttp5EntityWriteToInstrumentation extends BaseApacheHttp5EntityInstrumentation { + + public static class ApacheHttpEntityWriteToAdvice extends AbstractApacheHttpRequestBodyCaptureAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + @Advice.AssignReturned.ToArguments(@Advice.AssignReturned.ToArguments.ToArgument(0)) + public static OutputStream onEnter(@Advice.This HttpEntity thiz, @Advice.Argument(0) OutputStream drain) { + return maybeCaptureRequestBodyOutputStream(thiz, drain); + } + + @Advice.OnMethodExit(suppress = Throwable.class, inline = false) + public static void onExit(@Advice.Enter OutputStream potentiallyWrappedStream) { + releaseRequestBodyOutputStream(potentiallyWrappedStream); + } + } + + @Override + public String getAdviceClassName() { + return "co.elastic.apm.agent.httpclient.v5.ApacheHttp5EntityWriteToInstrumentation$ApacheHttpEntityWriteToAdvice"; + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("writeTo") + .and(takesArguments(1)) + .and(takesArgument(0, OutputStream.class)); + } + +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClient5Instrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClient5Instrumentation.java index 5170a37f3e..5fe23bf705 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClient5Instrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClient5Instrumentation.java @@ -20,8 +20,10 @@ import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpClientAdvice; +import co.elastic.apm.agent.httpclient.common.RequestBodyCaptureRegistry; import co.elastic.apm.agent.httpclient.v5.helper.ApacheHttpClient5ApiAdapter; import co.elastic.apm.agent.httpclient.v5.helper.RequestHeaderAccessor; +import co.elastic.apm.agent.tracer.Span; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.method.MethodDescription; @@ -50,7 +52,9 @@ public static class ApacheHttpClient5Advice extends AbstractApacheHttpClientAdvi @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) public static Object onBeforeExecute(@Advice.Argument(0) @Nullable HttpHost httpHost, @Advice.Argument(1) ClassicHttpRequest request) throws URISyntaxException { - return startSpan(tracer, adapter, request, httpHost, RequestHeaderAccessor.INSTANCE); + Span resultSpan = startSpan(tracer, adapter, request, httpHost, RequestHeaderAccessor.INSTANCE); + RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, tracer.getActive(), adapter, RequestHeaderAccessor.INSTANCE); + return resultSpan; } @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false) diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/BaseApacheHttp5EntityInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/BaseApacheHttp5EntityInstrumentation.java new file mode 100644 index 0000000000..6628a50dce --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/BaseApacheHttp5EntityInstrumentation.java @@ -0,0 +1,33 @@ +package co.elastic.apm.agent.httpclient.v5; + +import net.bytebuddy.description.NamedElement; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader; +import static net.bytebuddy.matcher.ElementMatchers.nameContains; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +public abstract class BaseApacheHttp5EntityInstrumentation extends BaseApacheHttpClient5Instrumentation { + @Override + public ElementMatcher.Junction getClassLoaderMatcher() { + return not(isBootstrapClassLoader()).and(classLoaderCanLoadClass("org.apache.hc.core5.http.HttpEntity")); + } + + @Override + public ElementMatcher getTypeMatcherPreFilter() { + return nameContains("Entity").and( + nameStartsWith("org.apache.hc") + .or(nameStartsWith("org.springframework.http")) //spring implements its own HttpEntities + ); + } + + @Override + public ElementMatcher getTypeMatcher() { + return hasSuperType(named("org.apache.hc.core5.http.HttpEntity")); + } +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/ApacheHttpClient5ApiAdapter.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/ApacheHttpClient5ApiAdapter.java index 233a92188a..46d63539b9 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/ApacheHttpClient5ApiAdapter.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/ApacheHttpClient5ApiAdapter.java @@ -25,9 +25,12 @@ import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.configuration.CoreConfiguration; import org.apache.hc.client5.http.CircularRedirectException; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.routing.RoutingSupport; import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpEntityContainer; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; @@ -36,7 +39,7 @@ import java.net.URI; import java.net.URISyntaxException; -public class ApacheHttpClient5ApiAdapter implements ApacheHttpClientApiAdapter { +public class ApacheHttpClient5ApiAdapter implements ApacheHttpClientApiAdapter { private static final ApacheHttpClient5ApiAdapter INSTANCE = new ApacheHttpClient5ApiAdapter(); private static final Logger logger = LoggerFactory.getLogger(ApacheHttpClient5ApiAdapter.class); @@ -76,6 +79,15 @@ public CharSequence getHostName(@Nullable HttpHost httpHost, ClassicHttpRequest } } + @Nullable + @Override + public byte[] getSimpleBodyBytes(HttpRequest httpRequest) { + if (httpRequest instanceof SimpleHttpRequest) { + return ((SimpleHttpRequest) httpRequest).getBodyBytes(); + } + return null; + } + @Override public int getResponseCode(CloseableHttpResponse closeableHttpResponse) { return closeableHttpResponse.getCode(); @@ -94,4 +106,13 @@ public boolean isNotNullStatusLine(CloseableHttpResponse closeableHttpResponse) // HTTP response messages in HttpClient 5.x no longer have a status line. return true; } + + @Nullable + @Override + public HttpEntity getRequestEntity(HttpRequest httpRequest) { + if (httpRequest instanceof HttpEntityContainer) { + return ((HttpEntityContainer) httpRequest).getEntity(); + } + return null; + } } diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/RequestChannelWrapper.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/RequestChannelWrapper.java index 0edd80e85e..51722905ab 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/RequestChannelWrapper.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/RequestChannelWrapper.java @@ -20,10 +20,11 @@ import co.elastic.apm.agent.httpclient.HttpClientHelper; +import co.elastic.apm.agent.httpclient.common.RequestBodyCaptureRegistry; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; -import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.pooling.Recyclable; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; @@ -78,6 +79,7 @@ public void sendRequest(HttpRequest httpRequest, EntityDetails entityDetails, Ht if (httpRequest != null) { if (span != null) { + RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(httpRequest, span, ApacheHttpClient5ApiAdapter.get(), RequestHeaderAccessor.INSTANCE); String host = null; String protocol = null; int port = -1; diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 04dd6ced97..a684cb014f 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -1,2 +1,4 @@ co.elastic.apm.agent.httpclient.v5.ApacheHttpClient5Instrumentation co.elastic.apm.agent.httpclient.v5.ApacheHttpAsyncClient5Instrumentation +co.elastic.apm.agent.httpclient.v5.ApacheHttp5EntityWriteToInstrumentation +co.elastic.apm.agent.httpclient.v5.ApacheHttp5EntityGetContentInstrumentation diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpAsyncClientInstrumentationTest.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpAsyncClientInstrumentationTest.java index ed117da025..e709e71ebe 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpAsyncClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpAsyncClientInstrumentationTest.java @@ -29,6 +29,8 @@ import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.net.URIAuthority; import org.junit.AfterClass; @@ -86,6 +88,44 @@ public void cancelled() { responseFuture.get(); } + @Override + protected boolean isBodyCapturingSupported() { + return true; + } + + @Override + public void testPostBodyCaptureForExistingSpan() throws Exception { + //TODO: async http client instrumentation does not support capturing bodies for existing spans yet + } + + @Override + protected void performPost(String path, byte[] data, String contentTypeHeader) throws Exception { + final CompletableFuture responseFuture = new CompletableFuture<>(); + SimpleHttpRequest req = SimpleRequestBuilder.get().setPath(path) + .addHeader("Content-Type", contentTypeHeader) + .setBody(data, ContentType.parse(contentTypeHeader)) + .build(); + HttpClientContext httpClientContext = HttpClientContext.create(); + client.execute(req, httpClientContext, new FutureCallback() { + @Override + public void completed(SimpleHttpResponse simpleHttpResponse) { + responseFuture.complete(simpleHttpResponse); + } + + @Override + public void failed(Exception e) { + responseFuture.completeExceptionally(e); + } + + @Override + public void cancelled() { + responseFuture.cancel(true); + } + }); + + responseFuture.get(); + } + @Test public void testSpanFinishOnEarlyException() throws Exception { diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java index ef04c9f59e..0ec0db4f15 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java @@ -22,15 +22,19 @@ import co.elastic.apm.agent.httpclient.AbstractHttpClientInstrumentationTest; import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; import org.junit.AfterClass; import org.junit.BeforeClass; +import java.io.ByteArrayInputStream; import java.io.IOException; public class ApacheHttpClientExecuteOpenInstrumentationTest extends AbstractHttpClientInstrumentationTest { @@ -59,20 +63,34 @@ public boolean isTestHttpCallWithUserInfoEnabled() { return false; } + private final HttpClientResponseHandler responseHandler = response -> { + int status = response.getCode(); + if (status >= 200 && status < 300) { + HttpEntity entity = response.getEntity(); + return entity != null ? EntityUtils.toString(entity) : null; + } else { + throw new ClientProtocolException("Unexpected response status: " + status); + } + }; + @Override protected void performGet(String path) throws Exception { - HttpClientResponseHandler responseHandler = response -> { - int status = response.getCode(); - if (status >= 200 && status < 300) { - HttpEntity entity = response.getEntity(); - String res = entity != null ? EntityUtils.toString(entity) : null; - return res; - } else { - throw new ClientProtocolException("Unexpected response status: " + status); - } - }; - ClassicHttpResponse response = client.executeOpen(null, new HttpGet(path), null); responseHandler.handleResponse(response); } + + @Override + protected boolean isBodyCapturingSupported() { + return true; + } + + @Override + protected void performPost(String path, byte[] content, String contentTypeHeader) throws Exception { + HttpPost request = new HttpPost(path); + request.setEntity(new InputStreamEntity(new ByteArrayInputStream(content), ContentType.parse(contentTypeHeader))); + request.setHeader("Content-Type", contentTypeHeader); + + ClassicHttpResponse response = client.executeOpen(null, request, null); + responseHandler.handleResponse(response); + } } diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientInstrumentationTest.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientInstrumentationTest.java index 7ed5a2163c..0bb2a70457 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientInstrumentationTest.java @@ -22,14 +22,18 @@ import co.elastic.apm.agent.httpclient.AbstractHttpClientInstrumentationTest; import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; import org.junit.AfterClass; import org.junit.BeforeClass; +import java.io.ByteArrayInputStream; import java.io.IOException; public class ApacheHttpClientInstrumentationTest extends AbstractHttpClientInstrumentationTest { @@ -58,18 +62,33 @@ public boolean isTestHttpCallWithUserInfoEnabled() { return false; } + private final HttpClientResponseHandler responseHandler = response -> { + int status = response.getCode(); + if (status >= 200 && status < 300) { + HttpEntity entity = response.getEntity(); + String res = entity != null ? EntityUtils.toString(entity) : null; + return res; + } else { + throw new ClientProtocolException("Unexpected response status: " + status); + } + }; + @Override protected void performGet(String path) throws Exception { - HttpClientResponseHandler responseHandler = response -> { - int status = response.getCode(); - if (status >= 200 && status < 300) { - HttpEntity entity = response.getEntity(); - String res = entity != null ? EntityUtils.toString(entity) : null; - return res; - } else { - throw new ClientProtocolException("Unexpected response status: " + status); - } - }; - String response = client.execute(new HttpGet(path), responseHandler); + client.execute(new HttpGet(path), responseHandler); + } + + @Override + protected boolean isBodyCapturingSupported() { + return true; + } + + @Override + protected void performPost(String path, byte[] content, String contentTypeHeader) throws Exception { + HttpPost request = new HttpPost(path); + request.setEntity(new InputStreamEntity(new ByteArrayInputStream(content), ContentType.parse(contentTypeHeader))); + request.setHeader("Content-Type", contentTypeHeader); + + client.execute(request, responseHandler); } } diff --git a/apm-agent-plugins/apm-apache-httpclient/pom.xml b/apm-agent-plugins/apm-apache-httpclient/pom.xml index a8374d09fc..c566875f9d 100644 --- a/apm-agent-plugins/apm-apache-httpclient/pom.xml +++ b/apm-agent-plugins/apm-apache-httpclient/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-apache-httpclient diff --git a/apm-agent-plugins/apm-api-plugin/pom.xml b/apm-agent-plugins/apm-api-plugin/pom.xml index 9f3cbf2225..1447181eea 100644 --- a/apm-agent-plugins/apm-api-plugin/pom.xml +++ b/apm-agent-plugins/apm-api-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 diff --git a/apm-agent-plugins/apm-asynchttpclient-plugin/pom.xml b/apm-agent-plugins/apm-asynchttpclient-plugin/pom.xml index 66e6229ccc..6080baed61 100644 --- a/apm-agent-plugins/apm-asynchttpclient-plugin/pom.xml +++ b/apm-agent-plugins/apm-asynchttpclient-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-asynchttpclient-plugin diff --git a/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-1-plugin/pom.xml b/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-1-plugin/pom.xml index 7f915e089f..a0eb0a6ed3 100644 --- a/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-1-plugin/pom.xml +++ b/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-1-plugin/pom.xml @@ -3,7 +3,7 @@ apm-aws-sdk co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 @@ -12,7 +12,7 @@ ${project.basedir}/../../.. - 1.12.755 + 1.12.770 1.1.0 diff --git a/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-2-plugin/pom.xml b/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-2-plugin/pom.xml index c1ff02d58e..d5358ec712 100644 --- a/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-2-plugin/pom.xml +++ b/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-2-plugin/pom.xml @@ -3,7 +3,7 @@ apm-aws-sdk co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 @@ -13,7 +13,7 @@ ${project.basedir}/../../.. - 2.26.12 + 2.27.17 2.0.0 8 diff --git a/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-common/pom.xml b/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-common/pom.xml index 3dc2c1668b..86af8747a5 100644 --- a/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-common/pom.xml +++ b/apm-agent-plugins/apm-aws-sdk/apm-aws-sdk-common/pom.xml @@ -3,7 +3,7 @@ apm-aws-sdk co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-aws-sdk/pom.xml b/apm-agent-plugins/apm-aws-sdk/pom.xml index 61dd193c09..3e6d602bbf 100644 --- a/apm-agent-plugins/apm-aws-sdk/pom.xml +++ b/apm-agent-plugins/apm-aws-sdk/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-awslambda-plugin/pom.xml b/apm-agent-plugins/apm-awslambda-plugin/pom.xml index 8b4eca780b..6afae0bd70 100644 --- a/apm-agent-plugins/apm-awslambda-plugin/pom.xml +++ b/apm-agent-plugins/apm-awslambda-plugin/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-cassandra/apm-cassandra-core-plugin/pom.xml b/apm-agent-plugins/apm-cassandra/apm-cassandra-core-plugin/pom.xml index 9cd920e9a2..d25fd6ffd5 100644 --- a/apm-agent-plugins/apm-cassandra/apm-cassandra-core-plugin/pom.xml +++ b/apm-agent-plugins/apm-cassandra/apm-cassandra-core-plugin/pom.xml @@ -3,7 +3,7 @@ apm-cassandra co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-cassandra/apm-cassandra3-plugin/pom.xml b/apm-agent-plugins/apm-cassandra/apm-cassandra3-plugin/pom.xml index 4cc7446cab..2399d0f835 100644 --- a/apm-agent-plugins/apm-cassandra/apm-cassandra3-plugin/pom.xml +++ b/apm-agent-plugins/apm-cassandra/apm-cassandra3-plugin/pom.xml @@ -3,7 +3,7 @@ apm-cassandra co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-cassandra/apm-cassandra4-plugin/pom.xml b/apm-agent-plugins/apm-cassandra/apm-cassandra4-plugin/pom.xml index f7dbbbf480..7dd6eba97c 100644 --- a/apm-agent-plugins/apm-cassandra/apm-cassandra4-plugin/pom.xml +++ b/apm-agent-plugins/apm-cassandra/apm-cassandra4-plugin/pom.xml @@ -3,7 +3,7 @@ apm-cassandra co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-cassandra/pom.xml b/apm-agent-plugins/apm-cassandra/pom.xml index fab826204d..1fa5d7586a 100644 --- a/apm-agent-plugins/apm-cassandra/pom.xml +++ b/apm-agent-plugins/apm-cassandra/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-dubbo-plugin/pom.xml b/apm-agent-plugins/apm-dubbo-plugin/pom.xml index a707d06b56..38eea4570a 100644 --- a/apm-agent-plugins/apm-dubbo-plugin/pom.xml +++ b/apm-agent-plugins/apm-dubbo-plugin/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml b/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml index ffc9e4033d..712b820c21 100644 --- a/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml +++ b/apm-agent-plugins/apm-ecs-logging-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-ecs-logging-plugin @@ -41,7 +41,8 @@ org.apache.logging.log4j log4j-core - 2.23.1 + + ${version.log4j} provided true diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/pom.xml index 758569046c..3b9644c7ea 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/pom.xml +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/pom.xml @@ -5,7 +5,7 @@ apm-es-restclient-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-es-restclient-plugin-5_6 diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/pom.xml index 300ccab1b1..ad2babc3ef 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/pom.xml +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/pom.xml @@ -5,7 +5,7 @@ apm-es-restclient-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-es-restclient-plugin-6_4 diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-7_x/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-7_x/pom.xml index 7660076c6f..39e84c5f4f 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-7_x/pom.xml +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-7_x/pom.xml @@ -5,7 +5,7 @@ apm-es-restclient-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-es-restclient-plugin-7_x diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml index 7a4a7b46fd..4316d6c2b0 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-8_x/pom.xml @@ -3,7 +3,7 @@ apm-es-restclient-plugin co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/pom.xml index 2778da0d9f..ff66886574 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/pom.xml +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/pom.xml @@ -5,7 +5,7 @@ apm-es-restclient-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-es-restclient-plugin-common diff --git a/apm-agent-plugins/apm-es-restclient-plugin/pom.xml b/apm-agent-plugins/apm-es-restclient-plugin/pom.xml index 09250d1596..7300a18b8c 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/pom.xml +++ b/apm-agent-plugins/apm-es-restclient-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-es-restclient-plugin diff --git a/apm-agent-plugins/apm-finagle-httpclient-plugin/pom.xml b/apm-agent-plugins/apm-finagle-httpclient-plugin/pom.xml index d25f5d4319..9b3c2c04de 100644 --- a/apm-agent-plugins/apm-finagle-httpclient-plugin/pom.xml +++ b/apm-agent-plugins/apm-finagle-httpclient-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-finagle-httpclient-plugin diff --git a/apm-agent-plugins/apm-grails-plugin/pom.xml b/apm-agent-plugins/apm-grails-plugin/pom.xml index 4017aadf9a..c91d2ce1d1 100644 --- a/apm-agent-plugins/apm-grails-plugin/pom.xml +++ b/apm-agent-plugins/apm-grails-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-grails-plugin diff --git a/apm-agent-plugins/apm-grpc/apm-grpc-plugin/pom.xml b/apm-agent-plugins/apm-grpc/apm-grpc-plugin/pom.xml index f1fde89f83..1d39ea0665 100644 --- a/apm-agent-plugins/apm-grpc/apm-grpc-plugin/pom.xml +++ b/apm-agent-plugins/apm-grpc/apm-grpc-plugin/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-grpc - 1.51.0 + 1.52.0 apm-grpc-plugin diff --git a/apm-agent-plugins/apm-grpc/apm-grpc-test-1.6.1/pom.xml b/apm-agent-plugins/apm-grpc/apm-grpc-test-1.6.1/pom.xml index 1655b678aa..1425fc863a 100644 --- a/apm-agent-plugins/apm-grpc/apm-grpc-test-1.6.1/pom.xml +++ b/apm-agent-plugins/apm-grpc/apm-grpc-test-1.6.1/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-grpc - 1.51.0 + 1.52.0 apm-grpc-test-1.6.1 diff --git a/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/pom.xml b/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/pom.xml index a5bb1f5e22..af8cb8106e 100644 --- a/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/pom.xml +++ b/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-grpc - 1.51.0 + 1.52.0 apm-grpc-test-latest diff --git a/apm-agent-plugins/apm-grpc/pom.xml b/apm-agent-plugins/apm-grpc/pom.xml index 6d38a3cc0e..cc1c00a915 100644 --- a/apm-agent-plugins/apm-grpc/pom.xml +++ b/apm-agent-plugins/apm-grpc/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-plugins - 1.51.0 + 1.52.0 apm-grpc diff --git a/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-5_x/pom.xml b/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-5_x/pom.xml index ef65db9947..272274d6be 100644 --- a/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-5_x/pom.xml +++ b/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-5_x/pom.xml @@ -5,7 +5,7 @@ apm-hibernate-search-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-hibernate-search-plugin-5_x diff --git a/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-6_x/pom.xml b/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-6_x/pom.xml index 7bc19091d6..b84962208e 100644 --- a/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-6_x/pom.xml +++ b/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-6_x/pom.xml @@ -5,7 +5,7 @@ apm-hibernate-search-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-hibernate-search-plugin-6_x diff --git a/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-common/pom.xml b/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-common/pom.xml index 1a47414528..127a750017 100644 --- a/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-common/pom.xml +++ b/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-common/pom.xml @@ -5,7 +5,7 @@ apm-hibernate-search-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-hibernate-search-plugin-common diff --git a/apm-agent-plugins/apm-hibernate-search-plugin/pom.xml b/apm-agent-plugins/apm-hibernate-search-plugin/pom.xml index 757f11b5f7..31b9946fd9 100644 --- a/apm-agent-plugins/apm-hibernate-search-plugin/pom.xml +++ b/apm-agent-plugins/apm-hibernate-search-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-hibernate-search-plugin diff --git a/apm-agent-plugins/apm-httpclient-core/pom.xml b/apm-agent-plugins/apm-httpclient-core/pom.xml index eaf24bebbb..ba0643a70a 100644 --- a/apm-agent-plugins/apm-httpclient-core/pom.xml +++ b/apm-agent-plugins/apm-httpclient-core/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-httpclient-core diff --git a/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/HttpClientHelper.java b/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/HttpClientHelper.java index 61bb7e7e66..2641adb865 100644 --- a/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/HttpClientHelper.java +++ b/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/HttpClientHelper.java @@ -27,6 +27,7 @@ import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.configuration.WebConfiguration; import co.elastic.apm.agent.tracer.dispatch.TextHeaderGetter; +import co.elastic.apm.agent.tracer.metadata.BodyCapture; import javax.annotation.Nullable; import java.net.URI; @@ -75,18 +76,23 @@ public static Span startHttpClientSpan(TraceState activeContext, String me return span; } - public static boolean startRequestBodyCapture(@Nullable AbstractSpan abstractSpan, R request, TextHeaderGetter headerGetter) { + public static void checkBodyCapturePreconditions(@Nullable AbstractSpan abstractSpan, R request, TextHeaderGetter headerGetter) { if (!(abstractSpan instanceof Span)) { - return false; + return; } Span span = (Span) abstractSpan; - if (!span.getContext().getHttp().getRequestBody().isEligibleForCapturing()) { - return false; + BodyCapture bodyCapture = span.getContext().getHttp().getRequestBody(); + if (!bodyCapture.isEligibleForCapturing()) { + return; + } + if (bodyCapture.havePreconditionsBeenChecked()) { + return; } WebConfiguration webConfig = GlobalTracer.get().getConfig(WebConfiguration.class); int byteCount = webConfig.getCaptureClientRequestBytes(); if (byteCount == 0) { - return false; + bodyCapture.markPreconditionsFailed(); + return; } List contentTypes = webConfig.getCaptureContentTypes(); String contentTypeHeader = headerGetter.getFirstHeader("Content-Type", request); @@ -94,10 +100,19 @@ public static boolean startRequestBodyCapture(@Nullable AbstractSpan abst contentTypeHeader = ""; } if (WildcardMatcher.anyMatch(contentTypes, contentTypeHeader) == null) { + bodyCapture.markPreconditionsFailed(); + return; + } + bodyCapture.markPreconditionsPassed(extractCharsetFromContentType(contentTypeHeader), byteCount); + } + + public static boolean checkAndStartRequestBodyCapture(@Nullable AbstractSpan abstractSpan, R request, TextHeaderGetter headerGetter) { + if (!(abstractSpan instanceof Span)) { return false; } - String charset = extractCharsetFromContentType(contentTypeHeader); - return span.getContext().getHttp().getRequestBody().startCapture(charset, byteCount); + checkBodyCapturePreconditions(abstractSpan, request, headerGetter); + Span span = (Span) abstractSpan; + return span.getContext().getHttp().getRequestBody().startCapture(); } //Visible for testing diff --git a/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingHelper.java b/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingHelper.java new file mode 100644 index 0000000000..a75274426a --- /dev/null +++ b/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingHelper.java @@ -0,0 +1,63 @@ +package co.elastic.apm.agent.httpclient; + +import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.SpanEndListener; +import co.elastic.apm.agent.tracer.metadata.BodyCapture; + +public class RequestBodyRecordingHelper implements SpanEndListener> { + + /** + * We do not need to participate in span reference counting here. + * Instead, we only hold a reference to the span for the time it is not ended. + * This is ensured via the {@link #onEnd(Span)} callback. + */ + // Visible for testing + Span clientSpan; + + public RequestBodyRecordingHelper(Span clientSpan) { + if (!clientSpan.isFinished()) { + this.clientSpan = clientSpan; + clientSpan.addEndListener(this); + } + } + + + /** + * @param b the byte to append + * @return false, if the body buffer is full and future calls would be no-op. True otherwise. + */ + public boolean appendToBody(byte b) { + if (clientSpan != null) { + BodyCapture requestBody = clientSpan.getContext().getHttp().getRequestBody(); + requestBody.append(b); + if (requestBody.isFull()) { + releaseSpan(); + } else { + return true; + } + } + return false; + } + + public void appendToBody(byte[] b, int off, int len) { + if (clientSpan != null) { + BodyCapture requestBody = clientSpan.getContext().getHttp().getRequestBody(); + requestBody.append(b, off, len); + if (requestBody.isFull()) { + releaseSpan(); + } + } + } + + void releaseSpan() { + if (clientSpan != null) { + clientSpan.removeEndListener(this); + } + clientSpan = null; + } + + @Override + public void onEnd(Span span) { + releaseSpan(); + } +} diff --git a/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingInputStream.java b/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingInputStream.java index f8e228eaf8..207fb4c274 100644 --- a/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingInputStream.java +++ b/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingInputStream.java @@ -19,9 +19,7 @@ package co.elastic.apm.agent.httpclient; import co.elastic.apm.agent.tracer.Span; -import co.elastic.apm.agent.tracer.metadata.BodyCapture; -import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; @@ -29,50 +27,21 @@ public class RequestBodyRecordingInputStream extends InputStream { private final InputStream delegate; - @Nullable - private Span clientSpan; + private final RequestBodyRecordingHelper recordingHelper; public RequestBodyRecordingInputStream(InputStream delegate, Span clientSpan) { this.delegate = delegate; - clientSpan.incrementReferences(); - this.clientSpan = clientSpan; + this.recordingHelper = new RequestBodyRecordingHelper(clientSpan); } - protected void appendToBody(byte b) { - if (clientSpan != null) { - BodyCapture requestBody = clientSpan.getContext().getHttp().getRequestBody(); - requestBody.append(b); - if (requestBody.isFull()) { - releaseSpan(); - } - } - } - - protected void appendToBody(byte[] b, int off, int len) { - if (clientSpan != null) { - BodyCapture requestBody = clientSpan.getContext().getHttp().getRequestBody(); - requestBody.append(b, off, len); - if (requestBody.isFull()) { - releaseSpan(); - } - } - } - - public void releaseSpan() { - if (clientSpan != null) { - clientSpan.decrementReferences(); - clientSpan = null; - } - } - @Override public int read() throws IOException { int character = delegate.read(); if (character == -1) { - releaseSpan(); + recordingHelper.releaseSpan(); } else { - appendToBody((byte) character); + recordingHelper.appendToBody((byte) character); } return character; } @@ -81,9 +50,9 @@ public int read() throws IOException { public int read(byte[] b, int off, int len) throws IOException { int readBytes = delegate.read(b, off, len); if (readBytes == -1) { - releaseSpan(); + recordingHelper.releaseSpan(); } else { - appendToBody(b, off, readBytes); + recordingHelper.appendToBody(b, off, readBytes); } return readBytes; } @@ -96,7 +65,7 @@ public int available() throws IOException { @Override public void close() throws IOException { try { - releaseSpan(); + recordingHelper.releaseSpan(); } finally { delegate.close(); } diff --git a/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingOutputStream.java b/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingOutputStream.java index aaf640c1d3..87f7280821 100644 --- a/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingOutputStream.java +++ b/apm-agent-plugins/apm-httpclient-core/src/main/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingOutputStream.java @@ -19,9 +19,7 @@ package co.elastic.apm.agent.httpclient; import co.elastic.apm.agent.tracer.Span; -import co.elastic.apm.agent.tracer.metadata.BodyCapture; -import javax.annotation.Nullable; import java.io.IOException; import java.io.OutputStream; @@ -29,38 +27,26 @@ public class RequestBodyRecordingOutputStream extends OutputStream { private final OutputStream delegate; - @Nullable - private Span clientSpan; + private final RequestBodyRecordingHelper recordingHelper; public RequestBodyRecordingOutputStream(OutputStream delegate, Span clientSpan) { this.delegate = delegate; - clientSpan.incrementReferences(); - this.clientSpan = clientSpan; + this.recordingHelper = new RequestBodyRecordingHelper(clientSpan); } @Override public void write(int b) throws IOException { try { - appendToBody((byte) b); + recordingHelper.appendToBody((byte) b); } finally { delegate.write(b); } } - protected void appendToBody(byte b) { - if (clientSpan != null) { - BodyCapture body = clientSpan.getContext().getHttp().getRequestBody(); - body.append(b); - if (body.isFull()) { - releaseSpan(); - } - } - } - @Override public void write(byte[] b) throws IOException { try { - appendToBody(b, 0, b.length); + recordingHelper.appendToBody(b, 0, b.length); } finally { delegate.write(b); } @@ -69,33 +55,16 @@ public void write(byte[] b) throws IOException { @Override public void write(byte[] b, int off, int len) throws IOException { try { - appendToBody(b, off, len); + recordingHelper.appendToBody(b, off, len); } finally { delegate.write(b, off, len); } } - protected void appendToBody(byte[] b, int off, int len) { - if (clientSpan != null) { - BodyCapture body = clientSpan.getContext().getHttp().getRequestBody(); - body.append(b, off, len); - if (body.isFull()) { - releaseSpan(); - } - } - } - - public void releaseSpan() { - if (clientSpan != null) { - clientSpan.decrementReferences(); - clientSpan = null; - } - } - @Override public void close() throws IOException { try { - releaseSpan(); + recordingHelper.releaseSpan(); } finally { delegate.close(); } @@ -105,4 +74,8 @@ public void close() throws IOException { public void flush() throws IOException { delegate.flush(); } + + public void releaseSpan() { + recordingHelper.releaseSpan(); + } } diff --git a/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java b/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java index 788757662f..a5ed7ca364 100644 --- a/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java @@ -27,6 +27,7 @@ import co.elastic.apm.agent.impl.transaction.SpanImpl; import co.elastic.apm.agent.impl.transaction.TraceContextImpl; import co.elastic.apm.agent.impl.transaction.TransactionImpl; +import co.elastic.apm.agent.sdk.internal.util.IOUtils; import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.agent.tracer.Scope; import co.elastic.apm.agent.tracer.TraceState; @@ -132,16 +133,58 @@ public void testPostBodyCapture() throws Exception { .withRequestBodySatisfying(body -> { ByteBuffer buffer = body.getBody(); assertThat(buffer).isNotNull(); - int numBytes = buffer.position(); - buffer.position(0); - byte[] contentBytes = new byte[numBytes]; - buffer.get(contentBytes); - assertThat(contentBytes).isEqualTo("Hello".getBytes(StandardCharsets.UTF_8)); + assertThat(IOUtils.copyToByteArray(buffer)).isEqualTo("Hello".getBytes(StandardCharsets.UTF_8)); assertThat(Objects.toString(body.getCharset())).isEqualTo("utf-8"); }) .verify(); } + + /** + * This test verifies + * + * @throws Exception + */ + @Test + public void testPostBodyCaptureForExistingSpan() throws Exception { + if (!isBodyCapturingSupported()) { + return; + } + doReturn(1024).when(config.getConfig(WebConfiguration.class)).getCaptureClientRequestBytes(); + byte[] content = "Hello World!".getBytes(StandardCharsets.UTF_8); + String path = "/"; + + SpanImpl capture = createExitSpan("capture"); + capture.getContext().getHttp().getRequestBody().markEligibleForCapturing(); + capture.activate(); + try { + performPost(getBaseUrl() + path, content, "application/json; charset=iso-8859-1"); + } finally { + capture.deactivate().end(); + } + + //Do not not capture body for "noCapture" because it is not marked eligible + SpanImpl noCapture = createExitSpan("no-capture"); + noCapture.activate(); + try { + performPost(getBaseUrl() + path, content, "application/json; charset=iso-8859-1"); + } finally { + noCapture.deactivate().end(); + } + + assertThat(reporter.getSpans()) + .containsExactly(capture, noCapture); + + BodyCaptureImpl captureBody = capture.getContext().getHttp().getRequestBody(); + assertThat(captureBody.getBody()).isNotNull(); + assertThat(Objects.toString(captureBody.getCharset())).isEqualTo("iso-8859-1"); + assertThat(IOUtils.copyToByteArray(captureBody.getBody())).isEqualTo(content); + + BodyCaptureImpl noCaptureBody = noCapture.getContext().getHttp().getRequestBody(); + assertThat(noCaptureBody.getBody()).isNull(); + assertThat(noCaptureBody.getCharset()).isNull(); + } + @Test public void testDisabledOutgoingHeaders() { doReturn(true).when(config.getConfig(CoreConfigurationImpl.class)).isOutgoingTraceContextHeadersInjectionDisabled(); @@ -153,11 +196,8 @@ public void testDisabledOutgoingHeaders() { @Test public void testContextPropagationFromExitParent() { String path = "/"; - SpanImpl exitSpan = Objects.requireNonNull(Objects.requireNonNull(Objects.requireNonNull(tracer.currentTransaction()).createExitSpan())); + SpanImpl exitSpan = createExitSpan("exit"); try { - exitSpan.withType("custom").withSubtype("exit"); - exitSpan.getContext().getDestination().withAddress("test-host").withPort(6000); - exitSpan.getContext().getServiceTarget().withType("test-resource"); exitSpan.activate(); performGetWithinTransaction(path); verifyTraceContextHeaders(exitSpan, path); @@ -167,6 +207,14 @@ public void testContextPropagationFromExitParent() { } } + private static SpanImpl createExitSpan(String name) { + SpanImpl exitSpan = Objects.requireNonNull(Objects.requireNonNull(Objects.requireNonNull(tracer.currentTransaction()).createExitSpan())); + exitSpan.withName(name).withType("custom").withSubtype("exit"); + exitSpan.getContext().getDestination().withAddress("test-host").withPort(6000); + exitSpan.getContext().getServiceTarget().withType("test-resource"); + return exitSpan; + } + @Test public void testBaggagePropagatedWithoutTrace() { TraceState baggageOnly = emptyContext.withUpdatedBaggage() diff --git a/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingHelperTest.java b/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingHelperTest.java new file mode 100644 index 0000000000..ad534b361f --- /dev/null +++ b/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/RequestBodyRecordingHelperTest.java @@ -0,0 +1,84 @@ +package co.elastic.apm.agent.httpclient; + +import co.elastic.apm.agent.MockReporter; +import co.elastic.apm.agent.MockTracer; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.context.BodyCaptureImpl; +import co.elastic.apm.agent.impl.transaction.SpanImpl; +import co.elastic.apm.agent.impl.transaction.TransactionImpl; +import co.elastic.apm.agent.sdk.internal.util.IOUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class RequestBodyRecordingHelperTest { + + private ElasticApmTracer tracer; + private MockReporter reporter; + + private TransactionImpl rootTx; + + @BeforeEach + public void beforeEach() { + MockTracer.MockInstrumentationSetup mockInstrumentationSetup = MockTracer.createMockInstrumentationSetup(); + tracer = mockInstrumentationSetup.getTracer(); + reporter = mockInstrumentationSetup.getReporter(); + rootTx = tracer.startRootTransaction(null); + } + + @AfterEach + public void afterEach() { + rootTx.end(); + reporter.assertRecycledAfterDecrementingReferences(); + tracer.stop(); + } + + @Test + public void ensureNoModificationAfterSpanEnd() { + SpanImpl span = rootTx.createSpan(); + BodyCaptureImpl spanBody = span.getContext().getHttp().getRequestBody(); + spanBody.markEligibleForCapturing(); + spanBody.markPreconditionsPassed(null, 100); + spanBody.startCapture(); + + RequestBodyRecordingHelper helper = new RequestBodyRecordingHelper(span); + helper.appendToBody(new byte[]{1, 2, 3, 4}, 1, 2); + helper.appendToBody((byte) 5); + + assertThat(IOUtils.copyToByteArray(spanBody.getBody())).containsExactly(2, 3, 5); + assertThat(helper.clientSpan).isNotNull(); + + span.end(); + assertThat(helper.clientSpan).isNull(); + + //Those should not and have no effect + helper.appendToBody(new byte[]{1, 2, 3, 4}, 1, 2); + helper.appendToBody((byte) 5); + assertThat(IOUtils.copyToByteArray(spanBody.getBody())).containsExactly(2, 3, 5); + + RequestBodyRecordingHelper endedHelper = new RequestBodyRecordingHelper(span); + assertThat(endedHelper.clientSpan).isNull(); + } + + @Test + public void ensureLimitRespected() { + SpanImpl span = rootTx.createSpan(); + BodyCaptureImpl spanBody = span.getContext().getHttp().getRequestBody(); + spanBody.markEligibleForCapturing(); + spanBody.markPreconditionsPassed(null, 3); + spanBody.startCapture(); + + RequestBodyRecordingHelper helper = new RequestBodyRecordingHelper(span); + helper.appendToBody((byte) 1); + helper.appendToBody(new byte[]{2, 3, 4, 5}, 1, 2); + helper.appendToBody((byte) 6); + + assertThat(IOUtils.copyToByteArray(spanBody.getBody())).containsExactly(1, 3, 4); + assertThat(helper.clientSpan).isNull(); + + span.end(); + } + +} diff --git a/apm-agent-plugins/apm-httpserver-core/pom.xml b/apm-agent-plugins/apm-httpserver-core/pom.xml index 163510ffa3..8a7fcdc86d 100644 --- a/apm-agent-plugins/apm-httpserver-core/pom.xml +++ b/apm-agent-plugins/apm-httpserver-core/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-httpserver-core diff --git a/apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml b/apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml index fc0f37632e..26ac3b892f 100644 --- a/apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml +++ b/apm-agent-plugins/apm-jakarta-websocket-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-jakarta-websocket-plugin diff --git a/apm-agent-plugins/apm-java-concurrent-plugin/pom.xml b/apm-agent-plugins/apm-java-concurrent-plugin/pom.xml index 69f19dcb5d..577b2a4c84 100644 --- a/apm-agent-plugins/apm-java-concurrent-plugin/pom.xml +++ b/apm-agent-plugins/apm-java-concurrent-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-java-concurrent-plugin diff --git a/apm-agent-plugins/apm-java-ldap-plugin/pom.xml b/apm-agent-plugins/apm-java-ldap-plugin/pom.xml index 70de83e3c4..b6bf154bed 100644 --- a/apm-agent-plugins/apm-java-ldap-plugin/pom.xml +++ b/apm-agent-plugins/apm-java-ldap-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-java-ldap-plugin diff --git a/apm-agent-plugins/apm-javalin-plugin/pom.xml b/apm-agent-plugins/apm-javalin-plugin/pom.xml index 555be4c392..6acd8b47aa 100644 --- a/apm-agent-plugins/apm-javalin-plugin/pom.xml +++ b/apm-agent-plugins/apm-javalin-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-javalin-plugin diff --git a/apm-agent-plugins/apm-jaxrs-plugin-jakartaee-test/pom.xml b/apm-agent-plugins/apm-jaxrs-plugin-jakartaee-test/pom.xml index 684ec3a081..5f35c63200 100644 --- a/apm-agent-plugins/apm-jaxrs-plugin-jakartaee-test/pom.xml +++ b/apm-agent-plugins/apm-jaxrs-plugin-jakartaee-test/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-jaxrs-plugin/pom.xml b/apm-agent-plugins/apm-jaxrs-plugin/pom.xml index d520542507..dbcaaebe79 100644 --- a/apm-agent-plugins/apm-jaxrs-plugin/pom.xml +++ b/apm-agent-plugins/apm-jaxrs-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-jaxrs-plugin diff --git a/apm-agent-plugins/apm-jaxws-plugin-jakartaee-test/pom.xml b/apm-agent-plugins/apm-jaxws-plugin-jakartaee-test/pom.xml index da66ac50f6..b3d96701f2 100644 --- a/apm-agent-plugins/apm-jaxws-plugin-jakartaee-test/pom.xml +++ b/apm-agent-plugins/apm-jaxws-plugin-jakartaee-test/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-jaxws-plugin/pom.xml b/apm-agent-plugins/apm-jaxws-plugin/pom.xml index 36d0d5a2ba..13e221638f 100644 --- a/apm-agent-plugins/apm-jaxws-plugin/pom.xml +++ b/apm-agent-plugins/apm-jaxws-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-jaxws-plugin diff --git a/apm-agent-plugins/apm-jdbc-plugin/pom.xml b/apm-agent-plugins/apm-jdbc-plugin/pom.xml index 1e7a3bddc6..1a0b540ce2 100644 --- a/apm-agent-plugins/apm-jdbc-plugin/pom.xml +++ b/apm-agent-plugins/apm-jdbc-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-jdbc-plugin diff --git a/apm-agent-plugins/apm-jdk-httpclient-plugin/pom.xml b/apm-agent-plugins/apm-jdk-httpclient-plugin/pom.xml index bd7a2a381e..b206e6a993 100644 --- a/apm-agent-plugins/apm-jdk-httpclient-plugin/pom.xml +++ b/apm-agent-plugins/apm-jdk-httpclient-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-jdk-httpclient-plugin diff --git a/apm-agent-plugins/apm-jdk-httpserver-plugin/pom.xml b/apm-agent-plugins/apm-jdk-httpserver-plugin/pom.xml index 13fc68cde5..a3167046c2 100644 --- a/apm-agent-plugins/apm-jdk-httpserver-plugin/pom.xml +++ b/apm-agent-plugins/apm-jdk-httpserver-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-jdk-httpserver-plugin diff --git a/apm-agent-plugins/apm-jms-plugin/apm-jms-jakarta/pom.xml b/apm-agent-plugins/apm-jms-plugin/apm-jms-jakarta/pom.xml index b87eb8bed6..19821d1379 100644 --- a/apm-agent-plugins/apm-jms-plugin/apm-jms-jakarta/pom.xml +++ b/apm-agent-plugins/apm-jms-plugin/apm-jms-jakarta/pom.xml @@ -3,7 +3,7 @@ apm-jms-plugin co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-jms-plugin/apm-jms-javax/pom.xml b/apm-agent-plugins/apm-jms-plugin/apm-jms-javax/pom.xml index ab9b2b298c..55639e2368 100644 --- a/apm-agent-plugins/apm-jms-plugin/apm-jms-javax/pom.xml +++ b/apm-agent-plugins/apm-jms-plugin/apm-jms-javax/pom.xml @@ -3,7 +3,7 @@ apm-jms-plugin co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-jms-plugin/apm-jms-plugin-base/pom.xml b/apm-agent-plugins/apm-jms-plugin/apm-jms-plugin-base/pom.xml index ea785a8871..9a643f7f87 100644 --- a/apm-agent-plugins/apm-jms-plugin/apm-jms-plugin-base/pom.xml +++ b/apm-agent-plugins/apm-jms-plugin/apm-jms-plugin-base/pom.xml @@ -5,7 +5,7 @@ apm-jms-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-jms-plugin-base diff --git a/apm-agent-plugins/apm-jms-plugin/apm-jms-spring-plugin/pom.xml b/apm-agent-plugins/apm-jms-plugin/apm-jms-spring-plugin/pom.xml index f45ee0fa4d..83bfa46c7b 100644 --- a/apm-agent-plugins/apm-jms-plugin/apm-jms-spring-plugin/pom.xml +++ b/apm-agent-plugins/apm-jms-plugin/apm-jms-spring-plugin/pom.xml @@ -5,7 +5,7 @@ apm-jms-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-jms-spring-plugin diff --git a/apm-agent-plugins/apm-jms-plugin/pom.xml b/apm-agent-plugins/apm-jms-plugin/pom.xml index 4ca39439d7..eb664fee8d 100644 --- a/apm-agent-plugins/apm-jms-plugin/pom.xml +++ b/apm-agent-plugins/apm-jms-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-jms-plugin diff --git a/apm-agent-plugins/apm-jmx-plugin/pom.xml b/apm-agent-plugins/apm-jmx-plugin/pom.xml index 20b22ae2d2..488f2fe188 100644 --- a/apm-agent-plugins/apm-jmx-plugin/pom.xml +++ b/apm-agent-plugins/apm-jmx-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-jmx-plugin diff --git a/apm-agent-plugins/apm-jsf-plugin/pom.xml b/apm-agent-plugins/apm-jsf-plugin/pom.xml index 1148fe925a..5ac9347124 100644 --- a/apm-agent-plugins/apm-jsf-plugin/pom.xml +++ b/apm-agent-plugins/apm-jsf-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-jsf-plugin diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/pom.xml b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/pom.xml index b9fdc06b5e..5660ba49e3 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/pom.xml +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/pom.xml @@ -3,7 +3,7 @@ apm-kafka-plugin co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/pom.xml b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/pom.xml index b968cfd1d7..e31c586ce1 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/pom.xml +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/pom.xml @@ -4,7 +4,7 @@ apm-kafka-plugin co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-spring-plugin/pom.xml b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-spring-plugin/pom.xml index de89a04e68..7fcc417fe2 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-spring-plugin/pom.xml +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-spring-plugin/pom.xml @@ -4,7 +4,7 @@ apm-kafka-plugin co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-kafka-plugin/pom.xml b/apm-agent-plugins/apm-kafka-plugin/pom.xml index 5a97ca94bd..1caf2805a9 100644 --- a/apm-agent-plugins/apm-kafka-plugin/pom.xml +++ b/apm-agent-plugins/apm-kafka-plugin/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-logging-plugin/apm-jboss-logging-plugin/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-jboss-logging-plugin/pom.xml index b42612dc96..4ba7dfa61a 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-jboss-logging-plugin/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/apm-jboss-logging-plugin/pom.xml @@ -5,7 +5,7 @@ apm-logging-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-jboss-logging-plugin diff --git a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/pom.xml index db37c7ad82..dcb34f9417 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/apm-jul-plugin/pom.xml @@ -5,7 +5,7 @@ apm-logging-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-jul-plugin diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j1-plugin/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-log4j1-plugin/pom.xml index c16057dc69..1a12d5d893 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j1-plugin/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j1-plugin/pom.xml @@ -5,7 +5,7 @@ apm-logging-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-log4j1-plugin diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/pom.xml new file mode 100644 index 0000000000..a2e9b966b7 --- /dev/null +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + co.elastic.apm + apm-logging-plugin + 1.52.0 + + + apm-log4j2-plugin-tests + ${project.groupId}:${project.artifactId} + + + ${project.basedir}/../../.. + + + + + + + ${project.groupId} + apm-log4j2-plugin + ${project.version} + test + + + org.apache.logging.log4j + log4j-core + ${version.log4j} + test + + + ${project.groupId} + apm-logging-plugin-common + ${project.version} + test-jar + test + + + org.apache.ivy + ivy + test + + + + + diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTest.java new file mode 100644 index 0000000000..3a4c05ce6b --- /dev/null +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTest.java @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.log4j2; + +import co.elastic.apm.agent.testutils.TestClassWithDependencyRunner; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; + +@DisabledOnOs(OS.WINDOWS) +public class Log4j2InstrumentationTest { + + @ParameterizedTest + @ValueSource(strings = {"2.6", "2.17.1", "2.24.0"}) + public void testInstrumentation(String version) throws Exception { + List dependencies = List.of( + "org.apache.logging.log4j:log4j-core:" + version, + "org.apache.logging.log4j:log4j-api:" + version, + "co.elastic.logging:log4j2-ecs-layout:1.3.2" + ); + new TestClassWithDependencyRunner(dependencies, Log4j2InstrumentationTestDefinitions.class.getName(), + Log4j2InstrumentationTestDefinitions.Log4j2LoggerFacade.class.getName()) + .run(); + } + + @ParameterizedTest + @ValueSource(strings = {"2.6", "2.17.1", "2.24.0"}) + public void testErrorCapture(String version) throws Exception { + List dependencies = List.of( + "org.apache.logging.log4j:log4j-core:" + version, + "org.apache.logging.log4j:log4j-api:" + version + ); + new TestClassWithDependencyRunner(dependencies, Log4j2LoggerErrorCapturingInstrumentationTest.class.getName()) + .run(); + } +} diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTestDefinitions.java similarity index 93% rename from apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTest.java rename to apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTestDefinitions.java index e8cfe0480a..4482a5e096 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTest.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTestDefinitions.java @@ -18,9 +18,10 @@ */ package co.elastic.apm.agent.log4j2; -import co.elastic.apm.agent.loginstr.LoggingInstrumentationTest; -import co.elastic.apm.agent.loginstr.LoggerFacade; import co.elastic.apm.agent.logging.LoggingConfigurationImpl; +import co.elastic.apm.agent.loginstr.LoggerFacade; +import co.elastic.apm.agent.loginstr.LoggingInstrumentationTest; +import co.elastic.apm.agent.testutils.TestClassWithDependencyRunner; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -32,7 +33,6 @@ import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import java.net.URI; import java.net.URISyntaxException; @@ -45,8 +45,8 @@ * Since the agent core uses log4j 2.12.4, and since both agent core and tests are loaded by the system class loader in unit tests, * the proper way to test integration tests with log4j2 is only through dedicated class loaders. */ -@Disabled -public class Log4j2InstrumentationTest extends LoggingInstrumentationTest { +@TestClassWithDependencyRunner.DisableOutsideOfRunner +public class Log4j2InstrumentationTestDefinitions extends LoggingInstrumentationTest { @BeforeAll static void resetConfigFactory() { @@ -60,6 +60,11 @@ static void reInitLogging() { private static final Marker TEST_MARKER = MarkerManager.getMarker("TEST"); + @Override + public void testSendLogs() { + //TODO: fix me, log sending is currently broken for log4j2 + } + @Override protected LoggerFacade createLoggerFacade() { return new Log4j2LoggerFacade(); @@ -86,7 +91,7 @@ static class Log4j2LoggerFacade implements LoggerFacade { public Log4j2LoggerFacade() { try { - configLocation = Objects.requireNonNull(Log4j2InstrumentationTest.class.getClassLoader() + configLocation = Objects.requireNonNull(Log4j2InstrumentationTestDefinitions.class.getClassLoader() .getResource("log4j2.xml")).toURI(); } catch (URISyntaxException e) { e.printStackTrace(); diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/java/co/elastic/apm/agent/log4j2/Log4j2LoggerErrorCapturingInstrumentationTest.java similarity index 85% rename from apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentationTest.java rename to apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/java/co/elastic/apm/agent/log4j2/Log4j2LoggerErrorCapturingInstrumentationTest.java index 08fd54465a..19e05d94ad 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2LoggerErrorCapturingInstrumentationTest.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/java/co/elastic/apm/agent/log4j2/Log4j2LoggerErrorCapturingInstrumentationTest.java @@ -16,20 +16,20 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.log4j2.error; +package co.elastic.apm.agent.log4j2; import co.elastic.apm.agent.loginstr.error.AbstractErrorLoggingInstrumentationTest; +import co.elastic.apm.agent.testutils.TestClassWithDependencyRunner; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessageFactory; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Only tested through dedicated class loaders for latest and oldest-supported versions. - * See {@link Log4j2ErrorCapturingTestVersions} + * See {@link co.elastic.apm.agent.log4j2.Log4j2InstrumentationTest} */ -@Disabled +@TestClassWithDependencyRunner.DisableOutsideOfRunner public class Log4j2LoggerErrorCapturingInstrumentationTest extends AbstractErrorLoggingInstrumentationTest { private static final Logger logger = LogManager.getLogger(Log4j2LoggerErrorCapturingInstrumentationTest.class); @@ -42,7 +42,7 @@ void captureErrorExceptionWithStringMessage() { @Test void captureErrorExceptionWithMessageMessage() { - logger.error(ParameterizedMessageFactory.INSTANCE.newMessage("exception captured with parameter {}", "foo"), new RuntimeException("some business exception")); + logger.error(ParameterizedMessageFactory.INSTANCE.newMessage("exception captured with parameter {}", new Object[]{"foo"}), new RuntimeException("some business exception")); verifyExceptionCaptured("some business exception", RuntimeException.class); } diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/resources/log4j2.xml b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/resources/log4j2.xml similarity index 100% rename from apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/resources/log4j2.xml rename to apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin-tests/src/test/resources/log4j2.xml diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/pom.xml index e783ce77ae..b8b50f6d93 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/pom.xml @@ -5,7 +5,7 @@ apm-logging-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-log4j2-plugin @@ -24,7 +24,7 @@ org.apache.logging.log4j log4j-core - 2.23.1 + 2.24.0 provided @@ -32,18 +32,6 @@ log4j2-ecs-layout ${version.ecs.logging} - - ${project.groupId} - apm-logging-plugin-common - ${project.version} - test-jar - test - - - org.apache.ivy - ivy - test - diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/Log4j2LogCorrelationInstrumentation.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/Log4j2LogCorrelationInstrumentation.java index 4375fec954..e765fdd78b 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/Log4j2LogCorrelationInstrumentation.java +++ b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/main/java/co/elastic/apm/agent/log4j2/correlation/Log4j2LogCorrelationInstrumentation.java @@ -71,9 +71,11 @@ public ElementMatcher getMethodMatcher() { } public static class Log4J2_6LogCorrelationInstrumentation extends Log4j2LogCorrelationInstrumentation { + @Override public ElementMatcher.Junction getProtectionDomainPostFilter() { - return implementationVersionGte("2.6").and(not(implementationVersionGte("2.7"))); + return implementationVersionGte("2.6", "org.apache.logging.log4j", "log4j-core") + .and(not(implementationVersionGte("2.7", "org.apache.logging.log4j", "log4j-core"))); } public static class AdviceClass { @@ -94,7 +96,7 @@ public static void removeFromThreadContext(@Advice.Enter boolean addedToMdc) { public static class Log4J2_7PlusLogCorrelationInstrumentation extends Log4j2LogCorrelationInstrumentation { @Override public ElementMatcher.Junction getProtectionDomainPostFilter() { - return implementationVersionGte("2.7"); + return implementationVersionGte("2.7", "org.apache.logging.log4j", "log4j-core"); } public static class AdviceClass { diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTestVersions.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTestVersions.java deleted file mode 100644 index 302f0132f5..0000000000 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2InstrumentationTestVersions.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package co.elastic.apm.agent.log4j2; - -import co.elastic.apm.agent.testutils.JUnit4TestClassWithDependencyRunner; -import co.elastic.apm.agent.logging.LoggingConfigurationImpl; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.IOException; -import java.util.List; - -/** - * This class only delegates tests to the current-version log4j2 tests through JUnit 4, so that it can be ran using - * {@link JUnit4TestClassWithDependencyRunner} in a dedicated CL where an older log4j2 version is loaded. - * This is required because the agent is using log4j2 and in tests they retain their original packages (relocation only - * takes place during packaging). - */ -@Ignore -public class Log4j2InstrumentationTestVersions extends Log4j2InstrumentationTest { - - @BeforeClass - public static void resetConfigFactory() { - ConfigurationFactory.resetConfigurationFactory(); - } - - @AfterClass - public static void reInitLogging() { - LoggingConfigurationImpl.init(List.of(), ""); - } - - @Before - @Override - public void setup() throws Exception { - super.setup(); - } - - @After - @Override - public void closeLogger() { - super.closeLogger(); - } - - @Test - @Override - public void testSimpleLogReformatting() throws Exception { - super.testSimpleLogReformatting(); - } - - @Test - @Override - public void testMarkers() throws Exception { - super.testMarkers(); - } - - @Test - @Override - public void testShadingIntoOriginalLogsDir() throws Exception { - super.testShadingIntoOriginalLogsDir(); - } - - @Test - @Override - public void testLazyEcsFileCreation() throws Exception { - super.testLazyEcsFileCreation(); - } - - @Test - @Override - public void testLogReformattingReplaceOriginal() throws IOException { - super.testLogReformattingReplaceOriginal(); - } - - @Test - @Override - public void testDynamicConfiguration() throws Exception { - super.testDynamicConfiguration(); - } - - @Test - @Override - public void testLogOverride() throws IOException { - super.testLogOverride(); - } - - @Test - @Override - public void testEmptyFormatterAllowList() throws Exception { - super.testEmptyFormatterAllowList(); - } - - @Test - @Override - public void testReformattedLogRolling() throws IOException { - super.testReformattedLogRolling(); - } -} diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2_17_1InstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2_17_1InstrumentationTest.java deleted file mode 100644 index 4f6e009153..0000000000 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2_17_1InstrumentationTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package co.elastic.apm.agent.log4j2; - -import co.elastic.apm.agent.testutils.JUnit4TestClassWithDependencyRunner; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; - -import java.util.List; - -@DisabledOnOs(OS.WINDOWS) -public class Log4j2_17_1InstrumentationTest { - private final JUnit4TestClassWithDependencyRunner runner; - - public Log4j2_17_1InstrumentationTest() throws Exception { - List dependencies = List.of( - "org.apache.logging.log4j:log4j-core:2.17.1", - "org.apache.logging.log4j:log4j-api:2.17.1", - "co.elastic.logging:log4j2-ecs-layout:1.3.2" - ); - runner = new JUnit4TestClassWithDependencyRunner(dependencies, Log4j2InstrumentationTestVersions.class, Log4j2InstrumentationTest.class, - Log4j2InstrumentationTest.Log4j2LoggerFacade.class); - } - - @Test - public void testVersions() { - runner.run(); - } -} diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2_6SInstrumentationTest.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2_6SInstrumentationTest.java deleted file mode 100644 index cfb8bf7df6..0000000000 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/Log4j2_6SInstrumentationTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package co.elastic.apm.agent.log4j2; - -import co.elastic.apm.agent.testutils.JUnit4TestClassWithDependencyRunner; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; - -import java.util.List; - -@DisabledOnOs(OS.WINDOWS) -public class Log4j2_6SInstrumentationTest { - private final JUnit4TestClassWithDependencyRunner runner; - - public Log4j2_6SInstrumentationTest() throws Exception { - List dependencies = List.of( - "org.apache.logging.log4j:log4j-core:2.6", - "org.apache.logging.log4j:log4j-api:2.6", - "co.elastic.logging:log4j2-ecs-layout:1.3.2" - ); - runner = new JUnit4TestClassWithDependencyRunner(dependencies, Log4j2InstrumentationTestVersions.class, Log4j2InstrumentationTest.class, - Log4j2InstrumentationTest.Log4j2LoggerFacade.class); - } - - @Test - public void testVersions() { - runner.run(); - } -} diff --git a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2ErrorCapturingTestVersions.java b/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2ErrorCapturingTestVersions.java deleted file mode 100644 index 3c1aa79b0a..0000000000 --- a/apm-agent-plugins/apm-logging-plugin/apm-log4j2-plugin/src/test/java/co/elastic/apm/agent/log4j2/error/Log4j2ErrorCapturingTestVersions.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package co.elastic.apm.agent.log4j2.error; - -import co.elastic.apm.agent.testutils.JUnit4TestClassWithDependencyRunner; -import org.junit.Ignore; -import org.junit.Test; - -/** - * This class only delegates tests to the current-version log4j2 tests through JUnit 4, so that it can be ran using - * {@link JUnit4TestClassWithDependencyRunner} in a dedicated CL where an older log4j2 version is loaded. - * This is required because the agent is using log4j2 and in tests they retain their original packages (relocation only - * takes place during packaging). - */ -@Ignore -public class Log4j2ErrorCapturingTestVersions extends Log4j2LoggerErrorCapturingInstrumentationTest { - - @Test - @Override - public void captureErrorExceptionWithStringMessage() { - super.captureErrorExceptionWithStringMessage(); - } - - @Test - @Override - public void captureErrorExceptionWithMessageMessage() { - super.captureErrorExceptionWithMessageMessage(); - } - - @Test - @Override - public void captureFatalException() { - super.captureFatalException(); - } -} diff --git a/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/apm-logback-plugin-impl/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/apm-logback-plugin-impl/pom.xml index f7bbb07d4f..2d2050f175 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/apm-logback-plugin-impl/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/apm-logback-plugin-impl/pom.xml @@ -5,7 +5,7 @@ apm-logback-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-logback-plugin-impl diff --git a/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/apm-logback-plugin-legacy-tests/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/apm-logback-plugin-legacy-tests/pom.xml index 4fd0893477..6fc64818c9 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/apm-logback-plugin-legacy-tests/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/apm-logback-plugin-legacy-tests/pom.xml @@ -5,7 +5,7 @@ apm-logback-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-logback-plugin-legacy-tests diff --git a/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/pom.xml index c1bb26be24..68814b5d8b 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/apm-logback-plugin/pom.xml @@ -5,7 +5,7 @@ apm-logging-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-logback-plugin diff --git a/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/pom.xml index aa15887e3d..4a47e7551b 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/apm-logging-plugin-common/pom.xml @@ -5,7 +5,7 @@ apm-logging-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-logging-plugin-common diff --git a/apm-agent-plugins/apm-logging-plugin/apm-slf4j-plugin/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-slf4j-plugin/pom.xml index ef68a3977c..854c519c1c 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-slf4j-plugin/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/apm-slf4j-plugin/pom.xml @@ -5,7 +5,7 @@ apm-logging-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-slf4j-plugin diff --git a/apm-agent-plugins/apm-logging-plugin/apm-tomcat-logging-plugin/pom.xml b/apm-agent-plugins/apm-logging-plugin/apm-tomcat-logging-plugin/pom.xml index 6ab4ccfb14..6386b8d133 100644 --- a/apm-agent-plugins/apm-logging-plugin/apm-tomcat-logging-plugin/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/apm-tomcat-logging-plugin/pom.xml @@ -5,7 +5,7 @@ apm-logging-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-tomcat-logging-plugin diff --git a/apm-agent-plugins/apm-logging-plugin/pom.xml b/apm-agent-plugins/apm-logging-plugin/pom.xml index 6cded057a0..7a9743e7b8 100644 --- a/apm-agent-plugins/apm-logging-plugin/pom.xml +++ b/apm-agent-plugins/apm-logging-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-logging-plugin @@ -25,6 +25,7 @@ apm-tomcat-logging-plugin apm-slf4j-plugin apm-jul-plugin + apm-log4j2-plugin-tests diff --git a/apm-agent-plugins/apm-micrometer-plugin/pom.xml b/apm-agent-plugins/apm-micrometer-plugin/pom.xml index e30162460a..ac9be92aa9 100644 --- a/apm-agent-plugins/apm-micrometer-plugin/pom.xml +++ b/apm-agent-plugins/apm-micrometer-plugin/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 @@ -23,7 +23,7 @@ io.micrometer micrometer-core - 1.13.2 + 1.13.3 provided diff --git a/apm-agent-plugins/apm-mongodb/apm-mongodb-common/pom.xml b/apm-agent-plugins/apm-mongodb/apm-mongodb-common/pom.xml index 349cb74464..c64fd931e8 100644 --- a/apm-agent-plugins/apm-mongodb/apm-mongodb-common/pom.xml +++ b/apm-agent-plugins/apm-mongodb/apm-mongodb-common/pom.xml @@ -4,7 +4,7 @@ apm-mongodb co.elastic.apm - 1.51.0 + 1.52.0 diff --git a/apm-agent-plugins/apm-mongodb/apm-mongodb3-plugin/pom.xml b/apm-agent-plugins/apm-mongodb/apm-mongodb3-plugin/pom.xml index 7f23b23c74..93118663e2 100644 --- a/apm-agent-plugins/apm-mongodb/apm-mongodb3-plugin/pom.xml +++ b/apm-agent-plugins/apm-mongodb/apm-mongodb3-plugin/pom.xml @@ -3,7 +3,7 @@ apm-mongodb co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-mongodb/apm-mongodb4-plugin/pom.xml b/apm-agent-plugins/apm-mongodb/apm-mongodb4-plugin/pom.xml index fd0b75ed2f..f6d6864bcf 100644 --- a/apm-agent-plugins/apm-mongodb/apm-mongodb4-plugin/pom.xml +++ b/apm-agent-plugins/apm-mongodb/apm-mongodb4-plugin/pom.xml @@ -5,7 +5,7 @@ apm-mongodb co.elastic.apm - 1.51.0 + 1.52.0 apm-mongodb4-plugin diff --git a/apm-agent-plugins/apm-mongodb/pom.xml b/apm-agent-plugins/apm-mongodb/pom.xml index 87fa2da510..5bdce0eb6e 100644 --- a/apm-agent-plugins/apm-mongodb/pom.xml +++ b/apm-agent-plugins/apm-mongodb/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-mongodb diff --git a/apm-agent-plugins/apm-okhttp-plugin/pom.xml b/apm-agent-plugins/apm-okhttp-plugin/pom.xml index 752edab5f8..3a387dd968 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/pom.xml +++ b/apm-agent-plugins/apm-okhttp-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-okhttp-plugin diff --git a/apm-agent-plugins/apm-okhttp-test/pom.xml b/apm-agent-plugins/apm-okhttp-test/pom.xml index 7a78147ab3..0cb79c1c28 100644 --- a/apm-agent-plugins/apm-okhttp-test/pom.xml +++ b/apm-agent-plugins/apm-okhttp-test/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/pom.xml b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/pom.xml index 69d5af0b44..5930f06861 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/pom.xml +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-embedded-metrics-sdk/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm apm-opentelemetry - 1.51.0 + 1.52.0 apm-opentelemetry-embedded-metrics-sdk diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-common/pom.xml b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-common/pom.xml index b2a0ebe0f7..283aeaad00 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-common/pom.xml +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-common/pom.xml @@ -3,7 +3,7 @@ apm-opentelemetry-metrics-bridge-parent co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/pom.xml b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/pom.xml index bdd3d6c2d3..b63ec61ecb 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/pom.xml +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-latest/pom.xml @@ -3,7 +3,7 @@ apm-opentelemetry-metrics-bridge-parent co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-v1_14/pom.xml b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-v1_14/pom.xml index c259be6795..b908102577 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-v1_14/pom.xml +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/apm-opentelemetry-metrics-bridge-v1_14/pom.xml @@ -3,7 +3,7 @@ apm-opentelemetry-metrics-bridge-parent co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/pom.xml b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/pom.xml index a4a0ff3907..b5faf16e98 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/pom.xml +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metrics-bridge-parent/pom.xml @@ -3,7 +3,7 @@ apm-opentelemetry co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/pom.xml b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/pom.xml index 04712e4f46..94d50f8415 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/pom.xml +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-metricsdk-plugin/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm apm-opentelemetry - 1.51.0 + 1.52.0 apm-opentelemetry-metricsdk-plugin diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-plugin/pom.xml b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-plugin/pom.xml index e049ee0caa..239ecbcf3c 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-plugin/pom.xml +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-plugin/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm apm-opentelemetry - 1.51.0 + 1.52.0 apm-opentelemetry-plugin diff --git a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-test/pom.xml b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-test/pom.xml index f1ed785e52..7d4058f2ff 100644 --- a/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-test/pom.xml +++ b/apm-agent-plugins/apm-opentelemetry/apm-opentelemetry-test/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm apm-opentelemetry - 1.51.0 + 1.52.0 ${project.groupId}:${project.artifactId} diff --git a/apm-agent-plugins/apm-opentelemetry/pom.xml b/apm-agent-plugins/apm-opentelemetry/pom.xml index b1643fd8e5..b4b496f872 100644 --- a/apm-agent-plugins/apm-opentelemetry/pom.xml +++ b/apm-agent-plugins/apm-opentelemetry/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-plugins - 1.51.0 + 1.52.0 apm-opentelemetry @@ -20,7 +20,7 @@ to make sure that in the future we stay compatible with the previous version. --> 1.32.0 - 1.25.0-alpha + 1.27.0-alpha 8 8 diff --git a/apm-agent-plugins/apm-opentracing-plugin/pom.xml b/apm-agent-plugins/apm-opentracing-plugin/pom.xml index d497b04a43..e7c0db728f 100644 --- a/apm-agent-plugins/apm-opentracing-plugin/pom.xml +++ b/apm-agent-plugins/apm-opentracing-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-opentracing-plugin diff --git a/apm-agent-plugins/apm-process-plugin/pom.xml b/apm-agent-plugins/apm-process-plugin/pom.xml index 44830d677a..ebca33388e 100644 --- a/apm-agent-plugins/apm-process-plugin/pom.xml +++ b/apm-agent-plugins/apm-process-plugin/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-profiling-plugin/pom.xml b/apm-agent-plugins/apm-profiling-plugin/pom.xml index 1110191a05..74833d32f8 100644 --- a/apm-agent-plugins/apm-profiling-plugin/pom.xml +++ b/apm-agent-plugins/apm-profiling-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-profiling-plugin diff --git a/apm-agent-plugins/apm-quartz/apm-quartz-common/pom.xml b/apm-agent-plugins/apm-quartz/apm-quartz-common/pom.xml index 38c1c1f67c..f4e8da2ce7 100644 --- a/apm-agent-plugins/apm-quartz/apm-quartz-common/pom.xml +++ b/apm-agent-plugins/apm-quartz/apm-quartz-common/pom.xml @@ -3,7 +3,7 @@ apm-quartz co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-quartz/apm-quartz-plugin-1/pom.xml b/apm-agent-plugins/apm-quartz/apm-quartz-plugin-1/pom.xml index 4f1c20260c..aad2b1e555 100644 --- a/apm-agent-plugins/apm-quartz/apm-quartz-plugin-1/pom.xml +++ b/apm-agent-plugins/apm-quartz/apm-quartz-plugin-1/pom.xml @@ -3,7 +3,7 @@ apm-quartz co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-quartz/apm-quartz-plugin-2/pom.xml b/apm-agent-plugins/apm-quartz/apm-quartz-plugin-2/pom.xml index c9e49dcbfc..c4cfbddea5 100644 --- a/apm-agent-plugins/apm-quartz/apm-quartz-plugin-2/pom.xml +++ b/apm-agent-plugins/apm-quartz/apm-quartz-plugin-2/pom.xml @@ -3,7 +3,7 @@ apm-quartz co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-quartz/pom.xml b/apm-agent-plugins/apm-quartz/pom.xml index d1abbaa31f..3f2a829a15 100644 --- a/apm-agent-plugins/apm-quartz/pom.xml +++ b/apm-agent-plugins/apm-quartz/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-quartz diff --git a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-plugin/pom.xml b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-plugin/pom.xml index f8065e3f8b..2ec83509d0 100644 --- a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-plugin/pom.xml +++ b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-plugin/pom.xml @@ -5,7 +5,7 @@ apm-rabbitmq co.elastic.apm - 1.51.0 + 1.52.0 apm-rabbitmq-plugin diff --git a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/pom.xml b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/pom.xml index 11278f1746..080df4c32d 100644 --- a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/pom.xml +++ b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/pom.xml @@ -5,7 +5,7 @@ apm-rabbitmq co.elastic.apm - 1.51.0 + 1.52.0 apm-rabbitmq-spring diff --git a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-test-3/pom.xml b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-test-3/pom.xml index 277a893018..2eaec66e99 100644 --- a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-test-3/pom.xml +++ b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-test-3/pom.xml @@ -5,7 +5,7 @@ apm-rabbitmq co.elastic.apm - 1.51.0 + 1.52.0 apm-rabbitmq-test-3 diff --git a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-test-4/pom.xml b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-test-4/pom.xml index a96df80934..b789efe0b7 100644 --- a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-test-4/pom.xml +++ b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-test-4/pom.xml @@ -5,7 +5,7 @@ apm-rabbitmq co.elastic.apm - 1.51.0 + 1.52.0 apm-rabbitmq-test-4 diff --git a/apm-agent-plugins/apm-rabbitmq/pom.xml b/apm-agent-plugins/apm-rabbitmq/pom.xml index 7ee5a9bce2..142b78d458 100644 --- a/apm-agent-plugins/apm-rabbitmq/pom.xml +++ b/apm-agent-plugins/apm-rabbitmq/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-rabbitmq diff --git a/apm-agent-plugins/apm-reactor-plugin/pom.xml b/apm-agent-plugins/apm-reactor-plugin/pom.xml index a5ba1e858a..fc27ef1ccc 100644 --- a/apm-agent-plugins/apm-reactor-plugin/pom.xml +++ b/apm-agent-plugins/apm-reactor-plugin/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-plugins - 1.51.0 + 1.52.0 apm-reactor-plugin diff --git a/apm-agent-plugins/apm-redis-plugin/apm-jedis-2-tests/pom.xml b/apm-agent-plugins/apm-redis-plugin/apm-jedis-2-tests/pom.xml index a18f749aea..52efbbc288 100644 --- a/apm-agent-plugins/apm-redis-plugin/apm-jedis-2-tests/pom.xml +++ b/apm-agent-plugins/apm-redis-plugin/apm-jedis-2-tests/pom.xml @@ -5,7 +5,7 @@ apm-redis-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-jedis-2-tests diff --git a/apm-agent-plugins/apm-redis-plugin/apm-jedis-3-tests/pom.xml b/apm-agent-plugins/apm-redis-plugin/apm-jedis-3-tests/pom.xml index 884abcd4bb..efd93cb423 100644 --- a/apm-agent-plugins/apm-redis-plugin/apm-jedis-3-tests/pom.xml +++ b/apm-agent-plugins/apm-redis-plugin/apm-jedis-3-tests/pom.xml @@ -5,7 +5,7 @@ apm-redis-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-jedis-3-tests diff --git a/apm-agent-plugins/apm-redis-plugin/apm-jedis-4-plugin/pom.xml b/apm-agent-plugins/apm-redis-plugin/apm-jedis-4-plugin/pom.xml index 1a254e62b8..8ab645baf3 100644 --- a/apm-agent-plugins/apm-redis-plugin/apm-jedis-4-plugin/pom.xml +++ b/apm-agent-plugins/apm-redis-plugin/apm-jedis-4-plugin/pom.xml @@ -5,7 +5,7 @@ apm-redis-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-jedis-4-plugin diff --git a/apm-agent-plugins/apm-redis-plugin/apm-jedis-5-tests/pom.xml b/apm-agent-plugins/apm-redis-plugin/apm-jedis-5-tests/pom.xml index 1a2b55695f..ac6bd4c80b 100644 --- a/apm-agent-plugins/apm-redis-plugin/apm-jedis-5-tests/pom.xml +++ b/apm-agent-plugins/apm-redis-plugin/apm-jedis-5-tests/pom.xml @@ -5,7 +5,7 @@ apm-redis-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-jedis-5-tests @@ -46,7 +46,7 @@ redis.clients jedis - 5.1.3 + 5.1.5 test diff --git a/apm-agent-plugins/apm-redis-plugin/apm-jedis-plugin/pom.xml b/apm-agent-plugins/apm-redis-plugin/apm-jedis-plugin/pom.xml index 011369298f..6b67c54d5d 100644 --- a/apm-agent-plugins/apm-redis-plugin/apm-jedis-plugin/pom.xml +++ b/apm-agent-plugins/apm-redis-plugin/apm-jedis-plugin/pom.xml @@ -5,7 +5,7 @@ apm-redis-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-jedis-plugin diff --git a/apm-agent-plugins/apm-redis-plugin/apm-lettuce-3-tests/pom.xml b/apm-agent-plugins/apm-redis-plugin/apm-lettuce-3-tests/pom.xml index 3222af65d1..a868bf5067 100644 --- a/apm-agent-plugins/apm-redis-plugin/apm-lettuce-3-tests/pom.xml +++ b/apm-agent-plugins/apm-redis-plugin/apm-lettuce-3-tests/pom.xml @@ -3,7 +3,7 @@ apm-redis-plugin co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-redis-plugin/apm-lettuce-plugin/pom.xml b/apm-agent-plugins/apm-redis-plugin/apm-lettuce-plugin/pom.xml index 09e489d14d..e0a40d2b3b 100644 --- a/apm-agent-plugins/apm-redis-plugin/apm-lettuce-plugin/pom.xml +++ b/apm-agent-plugins/apm-redis-plugin/apm-lettuce-plugin/pom.xml @@ -3,7 +3,7 @@ apm-redis-plugin co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-redis-plugin/apm-redis-common/pom.xml b/apm-agent-plugins/apm-redis-plugin/apm-redis-common/pom.xml index 162b49ce34..91b1f66a32 100644 --- a/apm-agent-plugins/apm-redis-plugin/apm-redis-common/pom.xml +++ b/apm-agent-plugins/apm-redis-plugin/apm-redis-common/pom.xml @@ -5,7 +5,7 @@ apm-redis-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-redis-common diff --git a/apm-agent-plugins/apm-redis-plugin/apm-redisson-plugin/pom.xml b/apm-agent-plugins/apm-redis-plugin/apm-redisson-plugin/pom.xml index 3affe7abf0..797b7714af 100644 --- a/apm-agent-plugins/apm-redis-plugin/apm-redisson-plugin/pom.xml +++ b/apm-agent-plugins/apm-redis-plugin/apm-redisson-plugin/pom.xml @@ -5,7 +5,7 @@ apm-redis-plugin co.elastic.apm - 1.51.0 + 1.52.0 apm-redisson-plugin diff --git a/apm-agent-plugins/apm-redis-plugin/pom.xml b/apm-agent-plugins/apm-redis-plugin/pom.xml index d0ddda2d60..6048ec5714 100644 --- a/apm-agent-plugins/apm-redis-plugin/pom.xml +++ b/apm-agent-plugins/apm-redis-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-redis-plugin diff --git a/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml b/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml index 5082c7b807..2c0227853f 100644 --- a/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml +++ b/apm-agent-plugins/apm-scala-concurrent-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-scala-concurrent-plugin diff --git a/apm-agent-plugins/apm-scheduled-annotation-plugin-jakartaee-test/pom.xml b/apm-agent-plugins/apm-scheduled-annotation-plugin-jakartaee-test/pom.xml index 518805891c..4d941f97f0 100644 --- a/apm-agent-plugins/apm-scheduled-annotation-plugin-jakartaee-test/pom.xml +++ b/apm-agent-plugins/apm-scheduled-annotation-plugin-jakartaee-test/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-scheduled-annotation-plugin/pom.xml b/apm-agent-plugins/apm-scheduled-annotation-plugin/pom.xml index 33aee9d478..71a7431f6f 100644 --- a/apm-agent-plugins/apm-scheduled-annotation-plugin/pom.xml +++ b/apm-agent-plugins/apm-scheduled-annotation-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-scheduled-annotation-plugin diff --git a/apm-agent-plugins/apm-scheduled-annotation-plugin/src/test/java/co/elastic/apm/agent/scheduled/TimerTaskInstrumentationTest.java b/apm-agent-plugins/apm-scheduled-annotation-plugin/src/test/java/co/elastic/apm/agent/scheduled/TimerTaskInstrumentationTest.java index 49c8a4e1a0..d5a8a1163c 100644 --- a/apm-agent-plugins/apm-scheduled-annotation-plugin/src/test/java/co/elastic/apm/agent/scheduled/TimerTaskInstrumentationTest.java +++ b/apm-agent-plugins/apm-scheduled-annotation-plugin/src/test/java/co/elastic/apm/agent/scheduled/TimerTaskInstrumentationTest.java @@ -80,7 +80,7 @@ public void run() { }, 1); reporter.awaitTransactionCount(1); - assertThat(reporter.getTransactions().get(0).getNameAsString()).isEqualTo("1#run"); + assertThat(reporter.getTransactions().get(0).getNameAsString()).isEqualTo("TimerTaskInstrumentationTest$1#run"); } public static class TestTimerTask extends TimerTask { diff --git a/apm-agent-plugins/apm-servlet-jakarta-test/pom.xml b/apm-agent-plugins/apm-servlet-jakarta-test/pom.xml index 8d9f648693..63b9bf7c72 100644 --- a/apm-agent-plugins/apm-servlet-jakarta-test/pom.xml +++ b/apm-agent-plugins/apm-servlet-jakarta-test/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-servlet-jakarta-test diff --git a/apm-agent-plugins/apm-servlet-plugin/pom.xml b/apm-agent-plugins/apm-servlet-plugin/pom.xml index 951f02fc31..6d0208a5ef 100644 --- a/apm-agent-plugins/apm-servlet-plugin/pom.xml +++ b/apm-agent-plugins/apm-servlet-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-servlet-plugin diff --git a/apm-agent-plugins/apm-sparkjava-plugin/pom.xml b/apm-agent-plugins/apm-sparkjava-plugin/pom.xml index d153339d75..2213b1c9ac 100644 --- a/apm-agent-plugins/apm-sparkjava-plugin/pom.xml +++ b/apm-agent-plugins/apm-sparkjava-plugin/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-spring-resttemplate/apm-spring-restclient-test/pom.xml b/apm-agent-plugins/apm-spring-resttemplate/apm-spring-restclient-test/pom.xml index 913eaa4f1a..1f58112775 100644 --- a/apm-agent-plugins/apm-spring-resttemplate/apm-spring-restclient-test/pom.xml +++ b/apm-agent-plugins/apm-spring-resttemplate/apm-spring-restclient-test/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-spring-resttemplate - 1.51.0 + 1.52.0 apm-spring-restclient-test diff --git a/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/pom.xml b/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/pom.xml index 8cad64b281..be0d910972 100644 --- a/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/pom.xml +++ b/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-spring-resttemplate - 1.51.0 + 1.52.0 apm-spring-resttemplate-plugin @@ -71,6 +71,12 @@ ${project.version} test + + ${project.groupId} + apm-urlconnection-plugin + ${project.version} + test + ${project.groupId} apm-apache-httpclient5-plugin diff --git a/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/src/test/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentationTest.java b/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/src/test/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentationTest.java index 29bc51f423..d822e255b3 100644 --- a/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/src/test/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentationTest.java +++ b/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/src/test/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentationTest.java @@ -22,6 +22,9 @@ import co.elastic.apm.agent.httpclient.AbstractHttpClientInstrumentationTest; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; @@ -57,6 +60,16 @@ protected void performGet(String path) { Java17Code.performGet(restTemplate, path); } + @Override + protected boolean isBodyCapturingSupported() { + return Java17Code.isBodyCapturingSupported(restTemplate); + } + + @Override + protected void performPost(String path, byte[] content, String contentTypeHeader) throws Exception { + Java17Code.performPost(restTemplate, path, content, contentTypeHeader); + } + /** * The code is compiled with java 17 but potentially run with java 11. * JUnit will inspect the test class, therefore it must not contain any references to java 17 code. @@ -67,6 +80,14 @@ public static void performGet(Object restTemplate, String path) { ((RestTemplate) restTemplate).getForEntity(path, String.class); } + public static void performPost(Object restTemplateObj, String path, byte[] content, String contentTypeHeader) { + RestTemplate restTemplate = (RestTemplate) restTemplateObj; + HttpHeaders headers = new HttpHeaders(); + headers.set("Content-Type", contentTypeHeader); + HttpEntity entity = new HttpEntity(content, headers); + restTemplate.exchange(path, HttpMethod.POST, entity, String.class); + } + public static Iterable> getRestTemplateFactories() { return Stream.>of( SimpleClientHttpRequestFactory::new, @@ -75,5 +96,15 @@ public static Iterable> getRestTemplateFactories() { .map(fac -> (Supplier) (() -> new RestTemplate(fac.get()))) .collect(Collectors.toList()); } + + public static boolean isBodyCapturingSupported(Object restTemplateObj) { + RestTemplate restTemplate = (RestTemplate) restTemplateObj; + if (restTemplate.getRequestFactory() instanceof OkHttp3ClientHttpRequestFactory) { + // We do not support body capturing for OkHttp yet + return false; + } + return true; + } + } } diff --git a/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-test/pom.xml b/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-test/pom.xml index 01d9ddd8bb..c9583915eb 100644 --- a/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-test/pom.xml +++ b/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-test/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-spring-resttemplate - 1.51.0 + 1.52.0 apm-spring-resttemplate-test diff --git a/apm-agent-plugins/apm-spring-resttemplate/pom.xml b/apm-agent-plugins/apm-spring-resttemplate/pom.xml index e4fca8d718..4e02157fb1 100644 --- a/apm-agent-plugins/apm-spring-resttemplate/pom.xml +++ b/apm-agent-plugins/apm-spring-resttemplate/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-plugins - 1.51.0 + 1.52.0 apm-spring-resttemplate diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/pom.xml index e0b395c222..e8451f623d 100755 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/pom.xml +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-spring-webflux - 1.51.0 + 1.52.0 apm-spring-webclient-plugin diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/BodyCaptureRegistry.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/BodyCaptureRegistry.java new file mode 100644 index 0000000000..6ef7dae3c6 --- /dev/null +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/BodyCaptureRegistry.java @@ -0,0 +1,30 @@ +package co.elastic.apm.agent.springwebclient; + +import co.elastic.apm.agent.httpclient.RequestBodyRecordingHelper; +import co.elastic.apm.agent.sdk.weakconcurrent.WeakConcurrent; +import co.elastic.apm.agent.sdk.weakconcurrent.WeakMap; +import co.elastic.apm.agent.tracer.AbstractSpan; +import co.elastic.apm.agent.tracer.Span; +import org.springframework.http.client.reactive.ClientHttpRequest; + +import javax.annotation.Nullable; + +public class BodyCaptureRegistry { + + private static final WeakMap PENDING_RECORDINGS = WeakConcurrent.buildMap(); + + public static void maybeCaptureBodyFor(AbstractSpan abstractSpan, ClientHttpRequest request) { + if (!(abstractSpan instanceof Span)) { + return; + } + Span span = (Span) abstractSpan; + if (span.getContext().getHttp().getRequestBody().startCapture()) { + PENDING_RECORDINGS.put(request, new RequestBodyRecordingHelper(span)); + } + } + + @Nullable + public static RequestBodyRecordingHelper activateRecording(ClientHttpRequest request) { + return PENDING_RECORDINGS.remove(request); + } +} diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/ClientHttpConnectorInstrumentation.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/ClientHttpConnectorInstrumentation.java new file mode 100644 index 0000000000..ceeec4be33 --- /dev/null +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/ClientHttpConnectorInstrumentation.java @@ -0,0 +1,102 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.springwebclient; + +import co.elastic.apm.agent.sdk.ElasticApmInstrumentation; +import co.elastic.apm.agent.tracer.GlobalTracer; +import co.elastic.apm.agent.tracer.TraceState; +import co.elastic.apm.agent.tracer.Tracer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.NamedElement; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ClientHttpRequest; +import reactor.core.publisher.Mono; + +import javax.annotation.Nullable; +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Function; + +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.nameContains; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +/** + * Instruments all {@link org.springframework.http.client.reactive.ClientHttpConnector} types, to preserve the span context + * within the callback passed to {@link org.springframework.http.client.reactive.ClientHttpConnector#connect(HttpMethod, URI, Function)}. + * If the span is ready for it, this will cause the request body to be captured. + */ +public class ClientHttpConnectorInstrumentation extends ElasticApmInstrumentation { + + private static final Tracer tracer = GlobalTracer.get(); + + @Override + public ElementMatcher getTypeMatcherPreFilter() { + return nameStartsWith("org.springframework.http.") + .and(nameContains("Connector")); + } + + @Override + public ElementMatcher getTypeMatcher() { + return hasSuperType(named("org.springframework.http.client.reactive.ClientHttpConnector")) + .and(not(isInterface())); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("connect") + .and(takesArgument(2, named("java.util.function.Function"))); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("http-client", "spring-webclient"); + } + + public static class AdviceClass { + + @Nullable + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + @Advice.AssignReturned.ToArguments(@Advice.AssignReturned.ToArguments.ToArgument(2)) + public static Function> onBefore(@Advice.Argument(2) Function> requestCallback) { + TraceState context = tracer.currentContext(); + if (context.isEmpty()) { + return requestCallback; + } + return new Function>() { + @Override + public Mono apply(ClientHttpRequest clientHttpRequest) { + // Note that even though ClientHttpRequest exposes headers via the interface, those are empty + // therefore we check the span capturing pre-conditions in the WebClientExchangeFunctionInstrumentation instead + BodyCaptureRegistry.maybeCaptureBodyFor(context.getSpan(), clientHttpRequest); + return requestCallback.apply(clientHttpRequest); + } + }; + } + + } +} diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/ClientHttpRequestInstrumentation.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/ClientHttpRequestInstrumentation.java new file mode 100644 index 0000000000..2a7757fced --- /dev/null +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/ClientHttpRequestInstrumentation.java @@ -0,0 +1,136 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.apm.agent.springwebclient; + +import co.elastic.apm.agent.httpclient.RequestBodyRecordingHelper; +import co.elastic.apm.agent.sdk.ElasticApmInstrumentation; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.NamedElement; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.reactivestreams.Publisher; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.client.reactive.ClientHttpRequest; +import reactor.core.publisher.Flux; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Consumer; +import java.util.function.Function; + +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.nameContains; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +/** + * Instruments both {@link ClientHttpRequest#writeWith(Publisher)} and {@link ClientHttpRequest#writeAndFlushWith(Publisher)} + * to capture the request body. + */ +public class ClientHttpRequestInstrumentation extends ElasticApmInstrumentation { + + @Override + public ElementMatcher getTypeMatcherPreFilter() { + return nameStartsWith("org.springframework.http.client.reactive") + .and(nameContains("HttpRequest")); + } + + @Override + public ElementMatcher getTypeMatcher() { + return hasSuperType(named("org.springframework.http.client.reactive.ClientHttpRequest")) + .and(not(isInterface())); + } + + @Override + public ElementMatcher getMethodMatcher() { + return takesArgument(0, named("org.reactivestreams.Publisher")).and( + named("writeWith").or(named("writeAndFlushWith"))); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("http-client", "spring-webclient"); + } + + public static class AdviceClass { + + @Nullable + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + @Advice.AssignReturned.ToArguments(@Advice.AssignReturned.ToArguments.ToArgument(0)) + @SuppressWarnings("unchecked") + public static Publisher onBefore( + @Advice.Origin("#m") String methodName, + @Advice.This ClientHttpRequest clientRequest, + @Advice.Argument(0) Publisher bodyPublisher + ) { + RequestBodyRecordingHelper activeRecording = BodyCaptureRegistry.activateRecording(clientRequest); + // Note that activateRecording would return null on subsequent calls for the same span + // This is important because writeAndFlushWith might be built on top of writeWith (or the other way round) + // The removal helps to not double capture the body in this case. + if (activeRecording == null) { + return bodyPublisher; + } + RecordingConsumer recordingConsumer = new RecordingConsumer(activeRecording); + if (methodName.equals("writeWith")) { + Publisher actualPublisher = (Publisher) bodyPublisher; + return Flux.from(actualPublisher) + .doOnNext(recordingConsumer); + } else if (methodName.equals("writeAndFlushWith")) { + Publisher> actualPublisher + = (Publisher>) bodyPublisher; + return Flux.from(actualPublisher) + .map(new Function, Publisher>() { + @Override + public Publisher apply(Publisher publisher) { + return Flux.from(publisher) + .doOnNext(recordingConsumer); + } + }); + } else { + throw new IllegalStateException("This case should never happen"); + } + } + } + + private static class RecordingConsumer implements Consumer { + + private final RequestBodyRecordingHelper recordTo; + + private RecordingConsumer(RequestBodyRecordingHelper recordTo) { + this.recordTo = recordTo; + } + + @Override + public void accept(DataBuffer dataBuffer) { + int positionBackUp = dataBuffer.readPosition(); + while (dataBuffer.readableByteCount() > 0) { + if (!recordTo.appendToBody(dataBuffer.read())) { + break; + } + } + dataBuffer.readPosition(positionBackUp); + } + } + +} diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/ClientRequestHeaderGetter.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/ClientRequestHeaderGetter.java new file mode 100644 index 0000000000..1bc6f13f66 --- /dev/null +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/ClientRequestHeaderGetter.java @@ -0,0 +1,29 @@ +package co.elastic.apm.agent.springwebclient; + +import co.elastic.apm.agent.tracer.dispatch.TextHeaderGetter; +import org.springframework.web.reactive.function.client.ClientRequest; + +import javax.annotation.Nullable; +import java.util.List; + +public class ClientRequestHeaderGetter implements TextHeaderGetter { + + public static final ClientRequestHeaderGetter INSTANCE = new ClientRequestHeaderGetter(); + + @Nullable + @Override + public String getFirstHeader(String headerName, ClientRequest carrier) { + return carrier.headers().getFirst(headerName); + } + + @Override + public void forEach(String headerName, ClientRequest carrier, S state, HeaderConsumer consumer) { + List headerValues = carrier.headers().get(headerName); + if (headerValues == null) { + return; + } + for (String value : headerValues) { + consumer.accept(value, state); + } + } +} diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebClientExchangeFunctionInstrumentation.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebClientExchangeFunctionInstrumentation.java index 6ae015d439..aef6617f6e 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebClientExchangeFunctionInstrumentation.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/java/co/elastic/apm/agent/springwebclient/WebClientExchangeFunctionInstrumentation.java @@ -22,8 +22,8 @@ import co.elastic.apm.agent.sdk.ElasticApmInstrumentation; import co.elastic.apm.agent.tracer.GlobalTracer; import co.elastic.apm.agent.tracer.Span; -import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.TraceState; +import co.elastic.apm.agent.tracer.Tracer; import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument; import net.bytebuddy.description.NamedElement; @@ -85,6 +85,8 @@ public static Object[] onBefore(@Advice.Argument(0) ClientRequest clientRequest) span.activate(); } + HttpClientHelper.checkBodyCapturePreconditions(tracer.getActive(), clientRequest, ClientRequestHeaderGetter.INSTANCE); + TraceState toPropagate = tracer.currentContext(); if (!toPropagate.isEmpty()) { ClientRequest.Builder builder = ClientRequest.from(clientRequest); diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 808ca3da32..7e3bb40c17 100755 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -1 +1,3 @@ co.elastic.apm.agent.springwebclient.WebClientExchangeFunctionInstrumentation +co.elastic.apm.agent.springwebclient.ClientHttpConnectorInstrumentation +co.elastic.apm.agent.springwebclient.ClientHttpRequestInstrumentation diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/test/java/co/elastic/apm/agent/springwebclient/WebClientInstrumentationIT.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/test/java/co/elastic/apm/agent/springwebclient/WebClientInstrumentationIT.java index 2a2fd670f1..126a999d5d 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/test/java/co/elastic/apm/agent/springwebclient/WebClientInstrumentationIT.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/test/java/co/elastic/apm/agent/springwebclient/WebClientInstrumentationIT.java @@ -24,6 +24,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; import java.util.Arrays; @@ -82,5 +83,18 @@ public boolean isTestHttpCallWithUserInfoEnabled() { protected void performGet(String path) throws Exception { webClient.get().uri(path).exchangeToMono(response -> response.bodyToMono(String.class)).block(); } + + @Override + protected boolean isBodyCapturingSupported() { + return true; + } + + @Override + protected void performPost(String path, byte[] content, String contentTypeHeader) throws Exception { + webClient.post().uri(path) + .header("Content-Type", contentTypeHeader) + .body(Mono.just(content), byte[].class) + .exchangeToMono(response -> response.bodyToMono(String.class)).block(); + } } } diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/test/java/co/elastic/apm/agent/springwebclient/WebClientInstrumentationTest.java b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/test/java/co/elastic/apm/agent/springwebclient/WebClientInstrumentationTest.java index 006f673732..d949a8b7e0 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/test/java/co/elastic/apm/agent/springwebclient/WebClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webclient-plugin/src/test/java/co/elastic/apm/agent/springwebclient/WebClientInstrumentationTest.java @@ -26,6 +26,7 @@ import org.springframework.http.client.reactive.JettyClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; @RunWith(Parameterized.class) @@ -86,6 +87,16 @@ protected void performGet(String path) throws Exception { strategy.execute(webClient, path); } + @Override + protected boolean isBodyCapturingSupported() { + return true; + } + + @Override + protected void performPost(String path, byte[] content, String contentTypeHeader) throws Exception { + strategy.execute(webClient, path, content, contentTypeHeader); + } + /** * Client-side API variants to cover. While we know that implementation details might delegate to the same method * we have to test for it to prevent regression in a future version @@ -98,12 +109,34 @@ void execute(Object client, String uri) { ((WebClient) client).get().uri(uri).exchange() // deprecated API .block(); } + + @Override + void execute(Object client, String uri, byte[] body, String contentTypeHeader) { + ((WebClient) client).post() + .uri(uri) + .header("Content-Type", contentTypeHeader) + .body(Mono.just(body), byte[].class) + .exchange() // deprecated API + .block(); + + } }, EXCHANGE_TO_FLUX { @Override void execute(Object client, String uri) { ((WebClient) client).get().uri(uri).exchangeToFlux(response -> response.bodyToFlux(String.class)).blockLast(); } + + @Override + void execute(Object client, String uri, byte[] body, String contentTypeHeader) { + ((WebClient) client).post() + .uri(uri) + .header("Content-Type", contentTypeHeader) + .body(Mono.just(body), byte[].class) + .exchangeToFlux(response -> response.bodyToFlux(String.class)) + .blockLast(); + + } }, EXCHANGE_TO_MONO { // TODO @@ -111,15 +144,37 @@ void execute(Object client, String uri) { void execute(Object client, String uri) { ((WebClient) client).get().uri(uri).exchangeToMono(response -> response.bodyToMono(String.class)).block(); } + + @Override + void execute(Object client, String uri, byte[] body, String contentTypeHeader) { + ((WebClient) client).post() + .uri(uri) + .header("Content-Type", contentTypeHeader) + .body(Mono.just(body), byte[].class) + .exchangeToMono(response -> response.bodyToMono(String.class)).block(); + + } }, RETRIEVE { @Override void execute(Object client, String uri) { ((WebClient) client).get().uri(uri).retrieve().bodyToMono(String.class).block(); } + + @Override + void execute(Object client, String uri, byte[] body, String contentTypeHeader) { + ((WebClient) client).post() + .uri(uri) + .header("Content-Type", contentTypeHeader) + .body(Mono.just(body), byte[].class) + .retrieve().bodyToMono(String.class).block(); + + } }; abstract void execute(Object client, String uri); + + abstract void execute(Object client, String uri, byte[] body, String contentTypeHeader); } public static class Clients { diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/pom.xml index 709fab3dec..b932b662d4 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/pom.xml +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common-spring5/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm apm-spring-webflux - 1.51.0 + 1.52.0 apm-spring-webflux-common-spring5 diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/pom.xml index c74d903e43..fa11f5e4ac 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/pom.xml +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-common/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm apm-spring-webflux - 1.51.0 + 1.52.0 apm-spring-webflux-common diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/pom.xml index 84d32fc284..fc6ddbd4a6 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/pom.xml +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-plugin/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-spring-webflux - 1.51.0 + 1.52.0 apm-spring-webflux-plugin diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/pom.xml index b67b4f557f..6e5db947e2 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/pom.xml +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-spring5/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-spring-webflux - 1.51.0 + 1.52.0 apm-spring-webflux-spring5 diff --git a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/pom.xml b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/pom.xml index 3ee5822e47..f3588941ee 100644 --- a/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/pom.xml +++ b/apm-agent-plugins/apm-spring-webflux/apm-spring-webflux-testapp/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-spring-webflux - 1.51.0 + 1.52.0 apm-spring-webflux-testapp diff --git a/apm-agent-plugins/apm-spring-webflux/pom.xml b/apm-agent-plugins/apm-spring-webflux/pom.xml index 1adb5c4be6..8d3839ffb1 100644 --- a/apm-agent-plugins/apm-spring-webflux/pom.xml +++ b/apm-agent-plugins/apm-spring-webflux/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-plugins - 1.51.0 + 1.52.0 apm-spring-webflux diff --git a/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-plugin/pom.xml b/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-plugin/pom.xml index 60b3f76d98..29661bc77d 100644 --- a/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-plugin/pom.xml +++ b/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-plugin/pom.xml @@ -5,7 +5,7 @@ apm-spring-webmvc co.elastic.apm - 1.51.0 + 1.52.0 apm-spring-webmvc-plugin diff --git a/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-spring5/pom.xml b/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-spring5/pom.xml index 222383f9bc..5ef7b9c735 100644 --- a/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-spring5/pom.xml +++ b/apm-agent-plugins/apm-spring-webmvc/apm-spring-webmvc-spring5/pom.xml @@ -5,7 +5,7 @@ apm-spring-webmvc co.elastic.apm - 1.51.0 + 1.52.0 apm-spring-webmvc-spring5 diff --git a/apm-agent-plugins/apm-spring-webmvc/pom.xml b/apm-agent-plugins/apm-spring-webmvc/pom.xml index f9e77d6ec7..05097f14e3 100644 --- a/apm-agent-plugins/apm-spring-webmvc/pom.xml +++ b/apm-agent-plugins/apm-spring-webmvc/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-struts-plugin/pom.xml b/apm-agent-plugins/apm-struts-plugin/pom.xml index 980c347b79..55c20a73e0 100644 --- a/apm-agent-plugins/apm-struts-plugin/pom.xml +++ b/apm-agent-plugins/apm-struts-plugin/pom.xml @@ -3,7 +3,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/apm-agent-plugins/apm-urlconnection-plugin/pom.xml b/apm-agent-plugins/apm-urlconnection-plugin/pom.xml index 94ddfc5991..67c682c919 100644 --- a/apm-agent-plugins/apm-urlconnection-plugin/pom.xml +++ b/apm-agent-plugins/apm-urlconnection-plugin/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-urlconnection-plugin diff --git a/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentation.java b/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentation.java index a7ff424f58..2105eee66c 100644 --- a/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentation.java +++ b/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentation.java @@ -19,14 +19,15 @@ package co.elastic.apm.agent.urlconnection; import co.elastic.apm.agent.httpclient.HttpClientHelper; +import co.elastic.apm.agent.httpclient.RequestBodyRecordingOutputStream; import co.elastic.apm.agent.sdk.ElasticApmInstrumentation; import co.elastic.apm.agent.sdk.state.CallDepth; import co.elastic.apm.agent.sdk.state.GlobalState; import co.elastic.apm.agent.tracer.AbstractSpan; import co.elastic.apm.agent.tracer.GlobalTracer; -import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.reference.ReferenceCountedMap; import net.bytebuddy.asm.Advice; @@ -36,6 +37,7 @@ import net.bytebuddy.matcher.ElementMatcher; import javax.annotation.Nullable; +import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Arrays; @@ -54,6 +56,8 @@ public abstract class HttpUrlConnectionInstrumentation extends ElasticApmInstrum public static final Tracer tracer = GlobalTracer.get(); // must be public! public static final ReferenceCountedMap> inFlightSpans = tracer.newReferenceCountedMap(); + public static final ReferenceCountedMap> captureBodyForSpan = tracer.newReferenceCountedMap(); + public static final CallDepth callDepth = CallDepth.get(HttpUrlConnectionInstrumentation.class); @Override @@ -71,6 +75,80 @@ public ElementMatcher getTypeMatcher() { return hasSuperType(is(HttpURLConnection.class)).and(not(named("sun.net.www.protocol.https.HttpsURLConnectionImpl"))); } + public static class CreateSpanAdviceHelper { + + public static Span enter(HttpURLConnection thiz, boolean connectedField, int responseCodeField) { + + //With HEAD requests the connectedField stays false + boolean actuallyConnected = connectedField || responseCodeField != -1; + + boolean isNestedCall = callDepth.isNestedCallAndIncrement(); + TraceState activeContext = tracer.currentContext(); + AbstractSpan parentSpan = activeContext.getSpan(); + Span span = null; + if (parentSpan != null) { + span = inFlightSpans.get(thiz); + if (span == null && !actuallyConnected) { + final URL url = thiz.getURL(); + span = HttpClientHelper.startHttpClientSpan(activeContext, thiz.getRequestMethod(), url.toString(), url.getProtocol(), url.getHost(), url.getPort()); + } + if (!isNestedCall && span != null) { + span.activate(); + } else { + span = null; //do not deactivate this span on exit + } + } + + if (!isNestedCall && !actuallyConnected) { + tracer.currentContext().propagateContext(thiz, UrlConnectionPropertyAccessor.instance(), UrlConnectionPropertyAccessor.instance()); + } + + return span; + } + + public static void exit(HttpURLConnection thiz, @Nullable Throwable t, int responseCode, @Nullable Span spanObject) { + if (callDepth.isNestedCallAndDecrement()) { + if (responseCode != -1 || t != null) { + // Request has ended, no need to longer hold onto the span for request body capture + captureBodyForSpan.remove(thiz); + } + } + Span span = (Span) spanObject; + if (span == null) { + return; + } + try { + if (responseCode != -1) { + inFlightSpans.remove(thiz); + // if the response code is set, the connection has been established via getOutputStream + // if the response code is unset even after getOutputStream has been called, there will be an exception + // checking if "finished" to avoid multiple endings on nested calls + if (!span.isFinished()) { + span.getContext().getHttp().withStatusCode(responseCode); + span.captureException(t).end(); + } + } else if (t != null) { + inFlightSpans.remove(thiz); + + // an exception here is synonym of failure, for example with circular redirects + // checking if "finished" to avoid multiple endings on nested calls + if (!span.isFinished()) { + span.captureException(t) + .withOutcome(Outcome.FAILURE) + .end(); + } + } else { + // if connect or getOutputStream has been called we can't end the span right away + // we have to store associate it with thiz HttpURLConnection instance and end once getInputStream has been called + // note that this could happen on another thread + inFlightSpans.put(thiz, span); + } + } finally { + span.deactivate(); + } + } + + } public static class CreateSpanInstrumentation extends HttpUrlConnectionInstrumentation { public static class AdviceClass { @@ -78,86 +156,71 @@ public static class AdviceClass { @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) public static Object enter(@Advice.This HttpURLConnection thiz, @Advice.FieldValue("connected") boolean connected, - @Advice.FieldValue("responseCode") int responseCode, - @Advice.Origin String signature) { - - //With HEAD requests the connected stays false - boolean actuallyConnected = connected || responseCode != -1; - - boolean isNestedCall = callDepth.isNestedCallAndIncrement(); - TraceState activeContext = tracer.currentContext(); - AbstractSpan parentSpan = activeContext.getSpan(); - Span span = null; - if (parentSpan != null) { - span = inFlightSpans.get(thiz); - if (span == null && !actuallyConnected) { - final URL url = thiz.getURL(); - span = HttpClientHelper.startHttpClientSpan(activeContext, thiz.getRequestMethod(), url.toString(), url.getProtocol(), url.getHost(), url.getPort()); - } - if (!isNestedCall && span != null) { - span.activate(); - } else { - span = null; //do not deactivate this span on exit - } - } - - if (!isNestedCall && !actuallyConnected) { - tracer.currentContext().propagateContext(thiz, UrlConnectionPropertyAccessor.instance(), UrlConnectionPropertyAccessor.instance()); - } - - return span; + @Advice.FieldValue("responseCode") int responseCode + ) { + return CreateSpanAdviceHelper.enter(thiz, connected, responseCode); } @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false) public static void exit(@Advice.This HttpURLConnection thiz, @Advice.Thrown @Nullable Throwable t, @Advice.FieldValue("responseCode") int responseCode, - @Advice.Enter @Nullable Object spanObject, - @Advice.Origin String signature) { + @Advice.Enter @Nullable Object spanObject + ) { + CreateSpanAdviceHelper.exit(thiz, t, responseCode, (Span) spanObject); + } - callDepth.decrement(); - Span span = (Span) spanObject; - if (span == null) { - return; - } - try { - if (responseCode != -1) { - inFlightSpans.remove(thiz); - // if the response code is set, the connection has been established via getOutputStream - // if the response code is unset even after getOutputStream has been called, there will be an exception - // checking if "finished" to avoid multiple endings on nested calls - if (!span.isFinished()) { - span.getContext().getHttp().withStatusCode(responseCode); - span.captureException(t).end(); - } - } else if (t != null) { - inFlightSpans.remove(thiz); - - // an exception here is synonym of failure, for example with circular redirects - // checking if "finished" to avoid multiple endings on nested calls - if (!span.isFinished()) { - span.captureException(t) - .withOutcome(Outcome.FAILURE) - .end(); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("connect").and(takesArguments(0)) + .or(named("getInputStream").and(takesArguments(0))) + .or(named("getResponseCode").and(takesArguments(0))); + } + } + + public static class GetOutputStreamInstrumentation extends HttpUrlConnectionInstrumentation { + + public static class AdviceClass { + @Nullable + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + public static Object enter(@Advice.This HttpURLConnection thiz, + @Advice.FieldValue("connected") boolean connected, + @Advice.FieldValue("responseCode") int responseCode + ) { + return CreateSpanAdviceHelper.enter(thiz, connected, responseCode); + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false) + @Advice.AssignReturned.ToReturned + public static OutputStream exit(@Advice.This HttpURLConnection thiz, + @Advice.Return OutputStream outputStream, + @Advice.Thrown @Nullable Throwable t, + @Advice.FieldValue("responseCode") int responseCode, + @Advice.Enter @Nullable Object spanObject + ) { + if (callDepth.get() == 1 && t == null) { //only wrap on the outermost call and if no exception occurred + Span captureBodyFor = captureBodyForSpan.get(thiz); + if (captureBodyFor == null) { + AbstractSpan currentSpan = tracer.getActive(); + if (HttpClientHelper.checkAndStartRequestBodyCapture(currentSpan, thiz, UrlConnectionPropertyAccessor.instance())) { + captureBodyFor = (Span) currentSpan; + captureBodyForSpan.put(thiz, captureBodyFor); } - } else { - // if connect or getOutputStream has been called we can't end the span right away - // we have to store associate it with thiz HttpURLConnection instance and end once getInputStream has been called - // note that this could happen on another thread - inFlightSpans.put(thiz, span); } - } finally { - span.deactivate(); + if (captureBodyFor != null && !captureBodyFor.isFinished()) { + outputStream = new RequestBodyRecordingOutputStream(outputStream, captureBodyFor); + } } + CreateSpanAdviceHelper.exit(thiz, t, responseCode, (Span) spanObject); + return outputStream; } } @Override public ElementMatcher getMethodMatcher() { - return named("connect").and(takesArguments(0)) - .or(named("getOutputStream").and(takesArguments(0))) - .or(named("getInputStream").and(takesArguments(0))) - .or(named("getResponseCode").and(takesArguments(0))); + return named("getOutputStream").and(takesArguments(0)); } } @@ -172,6 +235,7 @@ public static class AdviceClass { public static void afterDisconnect(@Advice.This HttpURLConnection thiz, @Advice.Thrown @Nullable Throwable t, @Advice.FieldValue("responseCode") int responseCode) { + captureBodyForSpan.remove(thiz); Span span = inFlightSpans.remove(thiz); if (span != null) { span.captureException(t) diff --git a/apm-agent-plugins/apm-urlconnection-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-urlconnection-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 844fe66aff..66306dcc22 100644 --- a/apm-agent-plugins/apm-urlconnection-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-urlconnection-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -1,3 +1,4 @@ co.elastic.apm.agent.urlconnection.HttpUrlConnectionInstrumentation$CreateSpanInstrumentation +co.elastic.apm.agent.urlconnection.HttpUrlConnectionInstrumentation$GetOutputStreamInstrumentation co.elastic.apm.agent.urlconnection.HttpUrlConnectionInstrumentation$DisconnectInstrumentation co.elastic.apm.agent.urlconnection.SSLContextInstrumentation diff --git a/apm-agent-plugins/apm-urlconnection-plugin/src/test/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentationTest.java b/apm-agent-plugins/apm-urlconnection-plugin/src/test/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentationTest.java index 467838f64d..3a2b03a692 100644 --- a/apm-agent-plugins/apm-urlconnection-plugin/src/test/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentationTest.java +++ b/apm-agent-plugins/apm-urlconnection-plugin/src/test/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentationTest.java @@ -181,4 +181,21 @@ public void testGetInstrumentationWithErrorEvent() { assertThat(reporter.getErrors()).hasSize(1); } + @Override + protected boolean isBodyCapturingSupported() { + return true; + } + + @Override + protected void performPost(String path, byte[] content, String contentTypeHeader) throws Exception { + final HttpURLConnection urlConnection = (HttpURLConnection) new URL(path).openConnection(); + urlConnection.setDoOutput(true); + urlConnection.setRequestProperty("Content-Type", contentTypeHeader); + + //We call getOutputStream over and over again to ensure our instrumentation deals with that aswell + for (byte b : content) { + urlConnection.getOutputStream().write(b); + } + urlConnection.getResponseCode(); + } } diff --git a/apm-agent-plugins/apm-vertx/apm-vertx-common/pom.xml b/apm-agent-plugins/apm-vertx/apm-vertx-common/pom.xml index 22a47b31e2..4ee2e2d902 100644 --- a/apm-agent-plugins/apm-vertx/apm-vertx-common/pom.xml +++ b/apm-agent-plugins/apm-vertx/apm-vertx-common/pom.xml @@ -5,7 +5,7 @@ apm-vertx co.elastic.apm - 1.51.0 + 1.52.0 apm-vertx-common diff --git a/apm-agent-plugins/apm-vertx/apm-vertx3-plugin/pom.xml b/apm-agent-plugins/apm-vertx/apm-vertx3-plugin/pom.xml index e9db76719b..fa8cc2331b 100644 --- a/apm-agent-plugins/apm-vertx/apm-vertx3-plugin/pom.xml +++ b/apm-agent-plugins/apm-vertx/apm-vertx3-plugin/pom.xml @@ -5,7 +5,7 @@ apm-vertx co.elastic.apm - 1.51.0 + 1.52.0 apm-vertx3-plugin diff --git a/apm-agent-plugins/apm-vertx/apm-vertx3-test-latest/pom.xml b/apm-agent-plugins/apm-vertx/apm-vertx3-test-latest/pom.xml index 1792484614..3106d119a1 100644 --- a/apm-agent-plugins/apm-vertx/apm-vertx3-test-latest/pom.xml +++ b/apm-agent-plugins/apm-vertx/apm-vertx3-test-latest/pom.xml @@ -5,7 +5,7 @@ apm-vertx co.elastic.apm - 1.51.0 + 1.52.0 apm-vertx3-test-latest diff --git a/apm-agent-plugins/apm-vertx/apm-vertx4-plugin/pom.xml b/apm-agent-plugins/apm-vertx/apm-vertx4-plugin/pom.xml index 05e2c35acd..d99f5cdfbe 100644 --- a/apm-agent-plugins/apm-vertx/apm-vertx4-plugin/pom.xml +++ b/apm-agent-plugins/apm-vertx/apm-vertx4-plugin/pom.xml @@ -5,7 +5,7 @@ apm-vertx co.elastic.apm - 1.51.0 + 1.52.0 apm-vertx4-plugin diff --git a/apm-agent-plugins/apm-vertx/pom.xml b/apm-agent-plugins/apm-vertx/pom.xml index b448af06bd..37f10e99a8 100644 --- a/apm-agent-plugins/apm-vertx/pom.xml +++ b/apm-agent-plugins/apm-vertx/pom.xml @@ -5,7 +5,7 @@ apm-agent-plugins co.elastic.apm - 1.51.0 + 1.52.0 apm-vertx diff --git a/apm-agent-plugins/pom.xml b/apm-agent-plugins/pom.xml index a1e1c95e9a..a7f392c9bc 100644 --- a/apm-agent-plugins/pom.xml +++ b/apm-agent-plugins/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 apm-agent-plugins diff --git a/apm-agent-tracer/pom.xml b/apm-agent-tracer/pom.xml index dd1249acf9..7e5ca7a03a 100644 --- a/apm-agent-tracer/pom.xml +++ b/apm-agent-tracer/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm apm-agent-parent - 1.51.0 + 1.52.0 apm-agent-tracer diff --git a/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/Span.java b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/Span.java index 3f9a8fee7b..1eadfa9887 100644 --- a/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/Span.java +++ b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/Span.java @@ -42,4 +42,8 @@ public interface Span> extends AbstractSpan { * Action related to this span (eg: 'query', 'render' etc) */ T withAction(@Nullable String action); + + void addEndListener(SpanEndListener listener); + + void removeEndListener(SpanEndListener listener); } diff --git a/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/SpanEndListener.java b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/SpanEndListener.java new file mode 100644 index 0000000000..11bae55cfb --- /dev/null +++ b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/SpanEndListener.java @@ -0,0 +1,11 @@ +package co.elastic.apm.agent.tracer; + +public interface SpanEndListener> { + + /** + * Invoked when the span is being ended. + * + * @param span the span being ended + */ + void onEnd(T span); +} diff --git a/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/configuration/WebConfiguration.java b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/configuration/WebConfiguration.java index 2a35facaf5..d26ba047e1 100644 --- a/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/configuration/WebConfiguration.java +++ b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/configuration/WebConfiguration.java @@ -129,10 +129,12 @@ public class WebConfiguration extends ConfigurationOptionProvider { .addValidator(isInRange(0, MAX_BODY_CAPTURE_BYTES)) .key("capture_http_client_request_body_size") .configurationCategory(HTTP_CATEGORY) - .tags("added[1.50.0]", "internal") - .description("Configures how many bytes of http-client request bodies shall be captured. " + - "Note that only request bodies will be captured for content types matching the capture_body_content_types configuration. " + - " The maximum allowed value is " + MAX_BODY_CAPTURE_BYTES + " , a value of 0 disables body capturing") + .tags("added[1.52.0]", "experimental") + .description("Configures that the first n bytes of http-client request bodies shall be captured. " + + "Note that only request bodies will be captured for content types matching the <> configuration. " + + "The maximum allowed value is " + MAX_BODY_CAPTURE_BYTES + ", a value of 0 disables body capturing.\n\n" + + "Currently only support for Apache Http Client v4 and v5, HttpUrlConnection, Spring Webflux WebClient and other frameworks building on top of these (e.g. Spring RestTemplate).\n\n" + + "The body will be stored in the `labels.http_request_body_content` field on the span documents.") .dynamic(true) .buildWithDefault(0); diff --git a/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/metadata/BodyCapture.java b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/metadata/BodyCapture.java index ef8df98de0..5a2070895f 100644 --- a/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/metadata/BodyCapture.java +++ b/apm-agent-tracer/src/main/java/co/elastic/apm/agent/tracer/metadata/BodyCapture.java @@ -1,7 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.apm.agent.tracer.metadata; import javax.annotation.Nullable; +/** + * Container class for managing request body capture and associated state. + * This class collects the body as encoded bytearray and a charset with which we'll attempt to decode the body. + *

+ * A span goes through several states to perform body-capture: + *

    + *
  • A span need to be marked as eligible for body capture. This is useful so that higher + * level instrumentation (E.g. Spring RestTemplate) can mark the span that they would like + * to capture the body without having to implement the capturing itself. Lower level instrumentations + * (e.g. for HTTPUrlConnection) will attempt to capture the body for the currently active span even + * if they didn't start a span themselves when the currently active span is marked as eligible. + *
  • + *
  • Even if a span is marked eligible, the body will only be captured if the preconditions have been checked. + * The preconditions check whether based on the agent configuration and the Content-Type header the body shall be captured or not. + * E.g. if the body capturing is disabled via the config, the preconditions will fail for every span. + *
  • + *
  • If the preconditions passed, capturing may be started via {@link #startCapture()}. + * Capturing will only start exactly once, {@link #startCapture()} will return false on subsequent calls. + * This prevents nested instrumentation being capable of capturing the body (e.g. Spring WebFlux WebClient + * and async Apache Http client) of capturing every byte multiple times and therefore reporting a garbage body. + *
  • + *
+ */ public interface BodyCapture { /** @@ -15,23 +56,37 @@ public interface BodyCapture { * @return true, if {@link #markEligibleForCapturing()} was called for this span. */ boolean isEligibleForCapturing(); - + + /** + * @return true, if either {@link #markPreconditionsFailed()} or {@link #markPreconditionsPassed(String, int)} have been called. + */ + boolean havePreconditionsBeenChecked(); + + /** + * Ensures that the no body capturing will be performed for this span, e.g. because it is disabled via the agent config. + */ + void markPreconditionsFailed(); + + /** + * Marks this span so that any capable instrumentation may start the capturing procedure via {@link #startCapture()} + */ + void markPreconditionsPassed(@Nullable String requestCharset, int numBytesToCapture); + + /** * This method acts as a protection mechanism so that only one instrumentation tries to capture the body. * It returns true, if the calling instrumentation shall start adding body byte via {@link #append(byte)}. *

- * For this to happen, {@link #markEligibleForCapturing()} must have been called first. + * For this to happen, both {@link #markEligibleForCapturing()} and {@link #markPreconditionsPassed(String, int)} + * must have been called first. *

- * After {@link #startCapture(String, int)} has returned true once, subsequent calls will return false. + * After {@link #startCapture()} has returned true once, subsequent calls will return false. * So for example if instrumentation A and B are active for the same span, only the first one will actually be capturing the body, - * because {@link #startCapture(String, int)} only returns true once. - * - * @param charset the charset (if available) with which the request-body is encoded. - * @param numBytesToCapture the number of bytes to capture, to configure the limit of the internal buffer + * because {@link #startCapture()} only returns true once. * * @return true, if the calling instrumentation should be capturing the body (by calling {@link #append(byte)} */ - boolean startCapture(@Nullable String charset, int numBytesToCapture); + boolean startCapture(); void append(byte b); diff --git a/apm-opentracing/pom.xml b/apm-opentracing/pom.xml index f40310b6d5..1d4dd43ca6 100644 --- a/apm-opentracing/pom.xml +++ b/apm-opentracing/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 apm-opentracing diff --git a/cloudfoundry/index.yml b/cloudfoundry/index.yml index c78eb2f053..d93c07fbc0 100644 --- a/cloudfoundry/index.yml +++ b/cloudfoundry/index.yml @@ -63,3 +63,4 @@ 1.48.1: https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/1.48.1/elastic-apm-agent-1.48.1.jar 1.49.0: https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/1.49.0/elastic-apm-agent-1.49.0.jar 1.50.0: https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/1.50.0/elastic-apm-agent-1.50.0.jar +1.51.0: https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/1.51.0/elastic-apm-agent-1.51.0.jar diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 3d124b4b2d..6ff4717594 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -147,6 +147,7 @@ Click on a key to get more information. ** <> ** <> ** <> +** <> * <> ** <> ** <> @@ -1829,6 +1830,35 @@ Prepending an element with `(?-i)` makes the matching case sensitive. | `elastic.apm.url_groups` | `url_groups` | `ELASTIC_APM_URL_GROUPS` |============ +// This file is auto generated. Please make your changes in *Configuration.java (for example CoreConfiguration.java) and execute ConfigurationExporter +[float] +[[config-capture-http-client-request-body-size]] +==== `capture_http_client_request_body_size` (added[1.52.0] experimental) + +NOTE: This feature is currently experimental, which means it is disabled by default and it is not guaranteed to be backwards compatible in future releases. + +Configures that the first n bytes of http-client request bodies shall be captured. Note that only request bodies will be captured for content types matching the <> configuration. The maximum allowed value is 1024, a value of 0 disables body capturing. + +Currently only support for Apache Http Client v4 and v5, HttpUrlConnection, Spring Webflux WebClient and other frameworks building on top of these (e.g. Spring RestTemplate). + +The body will be stored in the `labels.http_request_body_content` field on the span documents. + +<> + + +[options="header"] +|============ +| Default | Type | Dynamic +| `0` | Integer | true +|============ + + +[options="header"] +|============ +| Java System Properties | Property file | Environment +| `elastic.apm.capture_http_client_request_body_size` | `capture_http_client_request_body_size` | `ELASTIC_APM_CAPTURE_HTTP_CLIENT_REQUEST_BODY_SIZE` +|============ + [[config-huge-traces]] === Huge Traces configuration options @@ -2623,9 +2653,7 @@ But if you must, you can use this option to increase the limit. // This file is auto generated. Please make your changes in *Configuration.java (for example CoreConfiguration.java) and execute ConfigurationExporter [float] [[config-agent-reporter-health-metrics]] -==== `agent_reporter_health_metrics` (added[1.35.0] experimental) - -NOTE: This feature is currently experimental, which means it is disabled by default and it is not guaranteed to be backwards compatible in future releases. +==== `agent_reporter_health_metrics` (added[1.35.0]) Enables metrics which capture the health state of the agent's event reporting mechanism. @@ -2648,9 +2676,7 @@ Enables metrics which capture the health state of the agent's event reporting me // This file is auto generated. Please make your changes in *Configuration.java (for example CoreConfiguration.java) and execute ConfigurationExporter [float] [[config-agent-background-overhead-metrics]] -==== `agent_background_overhead_metrics` (added[1.35.0] experimental) - -NOTE: This feature is currently experimental, which means it is disabled by default and it is not guaranteed to be backwards compatible in future releases. +==== `agent_background_overhead_metrics` (added[1.35.0]) Enables metrics which capture the resource consumption of agent background tasks. @@ -4331,6 +4357,18 @@ Example: `5ms`. # # url_groups= +# Configures that the first n bytes of http-client request bodies shall be captured. Note that only request bodies will be captured for content types matching the <> configuration. The maximum allowed value is 1024, a value of 0 disables body capturing. +# +# Currently only support for Apache Http Client v4 and v5, HttpUrlConnection, Spring Webflux WebClient and other frameworks building on top of these (e.g. Spring RestTemplate). +# +# The body will be stored in the `labels.http_request_body_content` field on the span documents. +# +# This setting can be changed at runtime +# Type: Integer +# Default value: 0 +# +# capture_http_client_request_body_size=0 + ############################################ # Huge Traces # ############################################ diff --git a/docs/metrics.asciidoc b/docs/metrics.asciidoc index 09d43629d7..75f11c59f2 100644 --- a/docs/metrics.asciidoc +++ b/docs/metrics.asciidoc @@ -663,8 +663,6 @@ Fields: [[metrics-agenthealth]] === Agent Health Metrics -experimental::[] - The agent internally uses a queue to buffer the various events (e.g. transactions, spans, metrics) before sending them to the APM server. When <> is enabled, the agent will expose several metrics regarding the health state of this queue and the network connectivity to the APM server. In addition, if <> is enabled, the agent will continuously measure the resource consumption of its own background tasks and provide the results as metrics. diff --git a/elastic-apm-agent-java8/pom.xml b/elastic-apm-agent-java8/pom.xml index 83da3b41c0..af58cbf093 100644 --- a/elastic-apm-agent-java8/pom.xml +++ b/elastic-apm-agent-java8/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-parent - 1.51.0 + 1.52.0 elastic-apm-agent-java8 diff --git a/elastic-apm-agent-premain/pom.xml b/elastic-apm-agent-premain/pom.xml index c6c8572bbe..25850879f1 100644 --- a/elastic-apm-agent-premain/pom.xml +++ b/elastic-apm-agent-premain/pom.xml @@ -3,7 +3,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/elastic-apm-agent/pom.xml b/elastic-apm-agent/pom.xml index 134ee08197..b66699bf06 100644 --- a/elastic-apm-agent/pom.xml +++ b/elastic-apm-agent/pom.xml @@ -5,7 +5,7 @@ co.elastic.apm apm-agent-parent - 1.51.0 + 1.52.0 elastic-apm-agent diff --git a/integration-tests/application-server-integration-tests/pom.xml b/integration-tests/application-server-integration-tests/pom.xml index 70bbb32a9e..94c2f7a842 100644 --- a/integration-tests/application-server-integration-tests/pom.xml +++ b/integration-tests/application-server-integration-tests/pom.xml @@ -5,7 +5,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 application-server-integration-tests diff --git a/integration-tests/aws-lambda-test/pom.xml b/integration-tests/aws-lambda-test/pom.xml index b1728f2948..fe605feb56 100644 --- a/integration-tests/aws-lambda-test/pom.xml +++ b/integration-tests/aws-lambda-test/pom.xml @@ -3,7 +3,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/cdi-app/cdi-app-dependent/pom.xml b/integration-tests/cdi-app/cdi-app-dependent/pom.xml index 4842df8962..1223f46f18 100644 --- a/integration-tests/cdi-app/cdi-app-dependent/pom.xml +++ b/integration-tests/cdi-app/cdi-app-dependent/pom.xml @@ -4,7 +4,7 @@ cdi-app co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/cdi-app/cdi-app-standalone/pom.xml b/integration-tests/cdi-app/cdi-app-standalone/pom.xml index 2f415ee34d..6e2739e956 100644 --- a/integration-tests/cdi-app/cdi-app-standalone/pom.xml +++ b/integration-tests/cdi-app/cdi-app-standalone/pom.xml @@ -4,7 +4,7 @@ cdi-app co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/cdi-app/pom.xml b/integration-tests/cdi-app/pom.xml index c245dbf868..43e3acbe22 100644 --- a/integration-tests/cdi-app/pom.xml +++ b/integration-tests/cdi-app/pom.xml @@ -4,7 +4,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/cdi-jakartaee-app/cdi-jakartaee-app-dependent/pom.xml b/integration-tests/cdi-jakartaee-app/cdi-jakartaee-app-dependent/pom.xml index b790110f6f..c6bac408bd 100644 --- a/integration-tests/cdi-jakartaee-app/cdi-jakartaee-app-dependent/pom.xml +++ b/integration-tests/cdi-jakartaee-app/cdi-jakartaee-app-dependent/pom.xml @@ -4,7 +4,7 @@ cdi-jakartaee-app co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/cdi-jakartaee-app/cdi-jakartaee-app-standalone/pom.xml b/integration-tests/cdi-jakartaee-app/cdi-jakartaee-app-standalone/pom.xml index b1baffd663..9432d9482a 100644 --- a/integration-tests/cdi-jakartaee-app/cdi-jakartaee-app-standalone/pom.xml +++ b/integration-tests/cdi-jakartaee-app/cdi-jakartaee-app-standalone/pom.xml @@ -4,7 +4,7 @@ cdi-jakartaee-app co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/cdi-jakartaee-app/pom.xml b/integration-tests/cdi-jakartaee-app/pom.xml index 695b9b2b01..7c6c43365c 100644 --- a/integration-tests/cdi-jakartaee-app/pom.xml +++ b/integration-tests/cdi-jakartaee-app/pom.xml @@ -4,7 +4,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/external-plugin-otel-test/external-plugin-otel-test-app/pom.xml b/integration-tests/external-plugin-otel-test/external-plugin-otel-test-app/pom.xml index b1fac537a6..5695c1db67 100644 --- a/integration-tests/external-plugin-otel-test/external-plugin-otel-test-app/pom.xml +++ b/integration-tests/external-plugin-otel-test/external-plugin-otel-test-app/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm external-plugin-otel-test - 1.51.0 + 1.52.0 external-plugin-otel-test-app diff --git a/integration-tests/external-plugin-otel-test/external-plugin-otel-test-plugin1/pom.xml b/integration-tests/external-plugin-otel-test/external-plugin-otel-test-plugin1/pom.xml index 4fc925fe5a..b64790d3ff 100644 --- a/integration-tests/external-plugin-otel-test/external-plugin-otel-test-plugin1/pom.xml +++ b/integration-tests/external-plugin-otel-test/external-plugin-otel-test-plugin1/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm external-plugin-otel-test - 1.51.0 + 1.52.0 external-plugin-otel-test-plugin1 diff --git a/integration-tests/external-plugin-otel-test/external-plugin-otel-test-plugin2/pom.xml b/integration-tests/external-plugin-otel-test/external-plugin-otel-test-plugin2/pom.xml index c5177b8175..c517e8b792 100644 --- a/integration-tests/external-plugin-otel-test/external-plugin-otel-test-plugin2/pom.xml +++ b/integration-tests/external-plugin-otel-test/external-plugin-otel-test-plugin2/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm external-plugin-otel-test - 1.51.0 + 1.52.0 external-plugin-otel-test-plugin2 diff --git a/integration-tests/external-plugin-otel-test/pom.xml b/integration-tests/external-plugin-otel-test/pom.xml index de48233231..6f009e231e 100644 --- a/integration-tests/external-plugin-otel-test/pom.xml +++ b/integration-tests/external-plugin-otel-test/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm integration-tests - 1.51.0 + 1.52.0 external-plugin-otel-test diff --git a/integration-tests/external-plugin-test/external-plugin-app/pom.xml b/integration-tests/external-plugin-test/external-plugin-app/pom.xml index ccc7bcc592..312e16c288 100644 --- a/integration-tests/external-plugin-test/external-plugin-app/pom.xml +++ b/integration-tests/external-plugin-test/external-plugin-app/pom.xml @@ -6,7 +6,7 @@ external-plugin-test co.elastic.apm - 1.51.0 + 1.52.0 external-plugin-app diff --git a/integration-tests/external-plugin-test/external-plugin-jakarta-app/pom.xml b/integration-tests/external-plugin-test/external-plugin-jakarta-app/pom.xml index e950b44bf6..f4bbdbaad8 100644 --- a/integration-tests/external-plugin-test/external-plugin-jakarta-app/pom.xml +++ b/integration-tests/external-plugin-test/external-plugin-jakarta-app/pom.xml @@ -6,7 +6,7 @@ external-plugin-test co.elastic.apm - 1.51.0 + 1.52.0 external-plugin-jakarta-app diff --git a/integration-tests/external-plugin-test/external-plugin/pom.xml b/integration-tests/external-plugin-test/external-plugin/pom.xml index dd8b212dfb..3226694dee 100644 --- a/integration-tests/external-plugin-test/external-plugin/pom.xml +++ b/integration-tests/external-plugin-test/external-plugin/pom.xml @@ -6,7 +6,7 @@ external-plugin-test co.elastic.apm - 1.51.0 + 1.52.0 external-plugin diff --git a/integration-tests/external-plugin-test/plugin-instrumentation-target/pom.xml b/integration-tests/external-plugin-test/plugin-instrumentation-target/pom.xml index 9d55c90014..e70a0357e0 100644 --- a/integration-tests/external-plugin-test/plugin-instrumentation-target/pom.xml +++ b/integration-tests/external-plugin-test/plugin-instrumentation-target/pom.xml @@ -6,7 +6,7 @@ external-plugin-test co.elastic.apm - 1.51.0 + 1.52.0 plugin-instrumentation-target diff --git a/integration-tests/external-plugin-test/pom.xml b/integration-tests/external-plugin-test/pom.xml index 9e581dc055..74765e1dec 100644 --- a/integration-tests/external-plugin-test/pom.xml +++ b/integration-tests/external-plugin-test/pom.xml @@ -3,7 +3,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-dependent/pom.xml b/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-dependent/pom.xml index b5d834c068..641a286f59 100644 --- a/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-dependent/pom.xml +++ b/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-dependent/pom.xml @@ -3,7 +3,7 @@ jakartaee-jsf-app co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-standalone/pom.xml b/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-standalone/pom.xml index f29235947f..e959055e00 100644 --- a/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-standalone/pom.xml +++ b/integration-tests/jakartaee-jsf-app/jakartaee-jsf-app-standalone/pom.xml @@ -3,7 +3,7 @@ jakartaee-jsf-app co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/jakartaee-jsf-app/pom.xml b/integration-tests/jakartaee-jsf-app/pom.xml index 62c48fa9d4..93c94ca4cb 100644 --- a/integration-tests/jakartaee-jsf-app/pom.xml +++ b/integration-tests/jakartaee-jsf-app/pom.xml @@ -3,7 +3,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 pom diff --git a/integration-tests/jakartaee-simple-webapp/pom.xml b/integration-tests/jakartaee-simple-webapp/pom.xml index 6ccd877da9..2640aa16f4 100644 --- a/integration-tests/jakartaee-simple-webapp/pom.xml +++ b/integration-tests/jakartaee-simple-webapp/pom.xml @@ -5,7 +5,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 jakartaee-simple-webapp diff --git a/integration-tests/jsf-app/jsf-app-dependent/pom.xml b/integration-tests/jsf-app/jsf-app-dependent/pom.xml index 32ce0be4c5..1293d46c05 100644 --- a/integration-tests/jsf-app/jsf-app-dependent/pom.xml +++ b/integration-tests/jsf-app/jsf-app-dependent/pom.xml @@ -4,7 +4,7 @@ jsf-app co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/jsf-app/jsf-app-standalone/pom.xml b/integration-tests/jsf-app/jsf-app-standalone/pom.xml index 4a7d0b9d5b..159a79b58a 100644 --- a/integration-tests/jsf-app/jsf-app-standalone/pom.xml +++ b/integration-tests/jsf-app/jsf-app-standalone/pom.xml @@ -6,7 +6,7 @@ jsf-app co.elastic.apm - 1.51.0 + 1.52.0 jsf-app-standalone diff --git a/integration-tests/jsf-app/pom.xml b/integration-tests/jsf-app/pom.xml index d1f0f11220..b69708c463 100644 --- a/integration-tests/jsf-app/pom.xml +++ b/integration-tests/jsf-app/pom.xml @@ -6,7 +6,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 jsf-app diff --git a/integration-tests/main-app-test/pom.xml b/integration-tests/main-app-test/pom.xml index 8963bd8dc7..c2bdf63d67 100644 --- a/integration-tests/main-app-test/pom.xml +++ b/integration-tests/main-app-test/pom.xml @@ -5,7 +5,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 main-app-test diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 82becf7ec0..6eb37dc803 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -5,7 +5,7 @@ apm-agent-parent co.elastic.apm - 1.51.0 + 1.52.0 integration-tests diff --git a/integration-tests/quarkus/pom.xml b/integration-tests/quarkus/pom.xml index f99d81e2a4..924f305d67 100644 --- a/integration-tests/quarkus/pom.xml +++ b/integration-tests/quarkus/pom.xml @@ -5,7 +5,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 quarkus diff --git a/integration-tests/quarkus/quarkus-jaxrs-base/pom.xml b/integration-tests/quarkus/quarkus-jaxrs-base/pom.xml index fb85b6e8b3..8609f3f6a8 100644 --- a/integration-tests/quarkus/quarkus-jaxrs-base/pom.xml +++ b/integration-tests/quarkus/quarkus-jaxrs-base/pom.xml @@ -5,7 +5,7 @@ quarkus co.elastic.apm - 1.51.0 + 1.52.0 quarkus-jaxrs-base diff --git a/integration-tests/quarkus/quarkus-jaxrs-undertow/pom.xml b/integration-tests/quarkus/quarkus-jaxrs-undertow/pom.xml index b8b1e84594..c0925248c6 100644 --- a/integration-tests/quarkus/quarkus-jaxrs-undertow/pom.xml +++ b/integration-tests/quarkus/quarkus-jaxrs-undertow/pom.xml @@ -5,7 +5,7 @@ quarkus co.elastic.apm - 1.51.0 + 1.52.0 quarkus-jaxrs-undertow diff --git a/integration-tests/quarkus/quarkus-jaxrs-vertx/pom.xml b/integration-tests/quarkus/quarkus-jaxrs-vertx/pom.xml index 84a96c200e..5e770fd7ee 100644 --- a/integration-tests/quarkus/quarkus-jaxrs-vertx/pom.xml +++ b/integration-tests/quarkus/quarkus-jaxrs-vertx/pom.xml @@ -5,7 +5,7 @@ quarkus co.elastic.apm - 1.51.0 + 1.52.0 quarkus-jaxrs-vertx diff --git a/integration-tests/runtime-attach/pom.xml b/integration-tests/runtime-attach/pom.xml index 500df790f8..f423c619b8 100644 --- a/integration-tests/runtime-attach/pom.xml +++ b/integration-tests/runtime-attach/pom.xml @@ -6,7 +6,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 runtime-attach diff --git a/integration-tests/runtime-attach/runtime-attach-app/pom.xml b/integration-tests/runtime-attach/runtime-attach-app/pom.xml index 936f4738c5..110af7d2ca 100644 --- a/integration-tests/runtime-attach/runtime-attach-app/pom.xml +++ b/integration-tests/runtime-attach/runtime-attach-app/pom.xml @@ -3,7 +3,7 @@ runtime-attach co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/runtime-attach/runtime-attach-test/pom.xml b/integration-tests/runtime-attach/runtime-attach-test/pom.xml index e14cd7b2ef..3659acbf4a 100644 --- a/integration-tests/runtime-attach/runtime-attach-test/pom.xml +++ b/integration-tests/runtime-attach/runtime-attach-test/pom.xml @@ -3,7 +3,7 @@ runtime-attach co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 @@ -31,7 +31,7 @@ org.awaitility awaitility - 4.2.1 + 4.2.2 test diff --git a/integration-tests/simple-webapp/pom.xml b/integration-tests/simple-webapp/pom.xml index 99490c2647..6b15b75cdf 100644 --- a/integration-tests/simple-webapp/pom.xml +++ b/integration-tests/simple-webapp/pom.xml @@ -6,7 +6,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 simple-webapp diff --git a/integration-tests/soap-test/pom.xml b/integration-tests/soap-test/pom.xml index a87746df9a..faab22748b 100644 --- a/integration-tests/soap-test/pom.xml +++ b/integration-tests/soap-test/pom.xml @@ -5,7 +5,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 soap-test diff --git a/integration-tests/spring-boot-1-5/pom.xml b/integration-tests/spring-boot-1-5/pom.xml index ead5000301..268c3696c2 100644 --- a/integration-tests/spring-boot-1-5/pom.xml +++ b/integration-tests/spring-boot-1-5/pom.xml @@ -5,7 +5,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 spring-boot-1-5 diff --git a/integration-tests/spring-boot-2/pom.xml b/integration-tests/spring-boot-2/pom.xml index d2359485cf..842959d228 100644 --- a/integration-tests/spring-boot-2/pom.xml +++ b/integration-tests/spring-boot-2/pom.xml @@ -5,7 +5,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 spring-boot-2 diff --git a/integration-tests/spring-boot-2/spring-boot-2-base/pom.xml b/integration-tests/spring-boot-2/spring-boot-2-base/pom.xml index 986614268d..9cbd366e3e 100644 --- a/integration-tests/spring-boot-2/spring-boot-2-base/pom.xml +++ b/integration-tests/spring-boot-2/spring-boot-2-base/pom.xml @@ -5,7 +5,7 @@ spring-boot-2 co.elastic.apm - 1.51.0 + 1.52.0 spring-boot-2-base diff --git a/integration-tests/spring-boot-2/spring-boot-2-jetty/pom.xml b/integration-tests/spring-boot-2/spring-boot-2-jetty/pom.xml index b74c5aed23..739756e032 100644 --- a/integration-tests/spring-boot-2/spring-boot-2-jetty/pom.xml +++ b/integration-tests/spring-boot-2/spring-boot-2-jetty/pom.xml @@ -5,7 +5,7 @@ spring-boot-2 co.elastic.apm - 1.51.0 + 1.52.0 spring-boot-2-jetty diff --git a/integration-tests/spring-boot-2/spring-boot-2-tomcat/pom.xml b/integration-tests/spring-boot-2/spring-boot-2-tomcat/pom.xml index 256f073736..369eb48e6b 100644 --- a/integration-tests/spring-boot-2/spring-boot-2-tomcat/pom.xml +++ b/integration-tests/spring-boot-2/spring-boot-2-tomcat/pom.xml @@ -5,7 +5,7 @@ spring-boot-2 co.elastic.apm - 1.51.0 + 1.52.0 spring-boot-2-tomcat diff --git a/integration-tests/spring-boot-2/spring-boot-2-undertow/pom.xml b/integration-tests/spring-boot-2/spring-boot-2-undertow/pom.xml index 954641719b..cac0e6b1e1 100644 --- a/integration-tests/spring-boot-2/spring-boot-2-undertow/pom.xml +++ b/integration-tests/spring-boot-2/spring-boot-2-undertow/pom.xml @@ -5,7 +5,7 @@ spring-boot-2 co.elastic.apm - 1.51.0 + 1.52.0 spring-boot-2-undertow diff --git a/integration-tests/spring-boot-3/pom.xml b/integration-tests/spring-boot-3/pom.xml index 273fbb2f71..6eadb700fe 100644 --- a/integration-tests/spring-boot-3/pom.xml +++ b/integration-tests/spring-boot-3/pom.xml @@ -5,7 +5,7 @@ integration-tests co.elastic.apm - 1.51.0 + 1.52.0 spring-boot-3 diff --git a/integration-tests/spring-boot-3/spring-boot-3-jetty/pom.xml b/integration-tests/spring-boot-3/spring-boot-3-jetty/pom.xml index 757a5a2761..e3de1c19e1 100644 --- a/integration-tests/spring-boot-3/spring-boot-3-jetty/pom.xml +++ b/integration-tests/spring-boot-3/spring-boot-3-jetty/pom.xml @@ -3,7 +3,7 @@ spring-boot-3 co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/spring-boot-3/spring-boot-3-tomcat/pom.xml b/integration-tests/spring-boot-3/spring-boot-3-tomcat/pom.xml index d54d220f5d..0f4a4a6f7b 100644 --- a/integration-tests/spring-boot-3/spring-boot-3-tomcat/pom.xml +++ b/integration-tests/spring-boot-3/spring-boot-3-tomcat/pom.xml @@ -3,7 +3,7 @@ spring-boot-3 co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/integration-tests/spring-boot-3/spring-boot-3-undertow/pom.xml b/integration-tests/spring-boot-3/spring-boot-3-undertow/pom.xml index db37828256..2377b32281 100644 --- a/integration-tests/spring-boot-3/spring-boot-3-undertow/pom.xml +++ b/integration-tests/spring-boot-3/spring-boot-3-undertow/pom.xml @@ -3,7 +3,7 @@ spring-boot-3 co.elastic.apm - 1.51.0 + 1.52.0 4.0.0 diff --git a/pom.xml b/pom.xml index 8d039d00c3..70f873f2aa 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ co.elastic.apm apm-agent-parent - 1.51.0 + 1.52.0 pom ${project.groupId}:${project.artifactId} @@ -128,18 +128,18 @@ 1.6.0 5.0.15.RELEASE 9.4.11.v20180605 - 1.5.0 + 1.5.1 - 1.14.18 + 1.15.1 9.7 5.4.0 - 5.12.0 + 5.13.0 1.17 - 1.19.8 + 1.20.1 2.38.0 @@ -814,7 +814,7 @@ org.awaitility awaitility - 4.2.1 + 4.2.2 test diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000000..36237c56f4 --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "github>elastic/renovate-config:only-chainguard" + ] +} diff --git a/update-compose.yaml b/updatecli-compose.yaml similarity index 73% rename from update-compose.yaml rename to updatecli-compose.yaml index 372d29ed58..7225bcbd08 100644 --- a/update-compose.yaml +++ b/updatecli-compose.yaml @@ -2,22 +2,22 @@ # https://www.updatecli.io/docs/core/compose/ policies: - name: Handle apm-data server specs - policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-data-spec:0.3.0@sha256:e7cdc38653afecaf9fad748356f545da6eb48bb9262665ff563cda974defb6f5 + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-data-spec:0.5.0@sha256:1307837d72174da906afb40a812b89f9f40efdbc0f6dcb4f632f886f9798577e values: - .ci/updatecli/values.d/scm.yml - .ci/updatecli/values.d/apm-data-spec.yml - name: Handle apm gherkin specs - policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-gherkin:0.3.0@sha256:b8fae37245a5fa99482b3700a53681196c4b42685153d93452f7103bbaf4a5f3 + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-gherkin:0.5.0@sha256:7166356b1bb5fb39b640dc9712a2a9f16b06b3fdb137dd362ede7d70ca5396e8 values: - .ci/updatecli/values.d/scm.yml - .ci/updatecli/values.d/apm-gherkin.yml - name: Handle apm json specs - policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-json-specs:0.3.0@sha256:f01667138f2a3a52aa23c556a174d6cdba1209f475d67a73e2aec2d3189b0bb9 + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-json-specs:0.5.0@sha256:f4065402be6459507660cb644fffa9cdc77a58303ebd7f8f0325002e206cf6a1 values: - .ci/updatecli/values.d/scm.yml - .ci/updatecli/values.d/apm-json-specs.yml - name: Update Updatecli policies - policy: ghcr.io/updatecli/policies/autodiscovery/updatecli:0.4.0@sha256:254367f5b1454fd6032b88b314450cd3b6d5e8d5b6c953eb242a6464105eb869 + policy: ghcr.io/updatecli/policies/autodiscovery/updatecli:0.8.0@sha256:99e9e61b501575c2c176c39f2275998d198b590a3f6b1fe829f7315f8d457e7f values: - .ci/updatecli/values.d/scm.yml - .ci/updatecli/values.d/update-compose.yml